/*******************************************************************************
* Copyright (c) 2012, 2016 Mentor Graphics and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Mentor Graphics - Initial API and implementation
* Marc Khouzam (Ericsson) - Update tests to use long timeouts (Bug 439926)
*******************************************************************************/
package org.eclipse.cdt.tests.dsf.gdb.tests;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.command.commands.MITargetSelect;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.tests.dsf.gdb.framework.BaseParametrizedTestCase;
import org.eclipse.cdt.tests.dsf.gdb.framework.ServiceEventWaitor;
import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class CommandTimeoutTest extends BaseParametrizedTestCase {
private static boolean fgTimeoutEnabled = false;
private static int fgTimeout = IGdbDebugPreferenceConstants.COMMAND_TIMEOUT_VALUE_DEFAULT;
private static boolean fgAutoTerminate;
@BeforeClass
public static void doBeforeClass() throws Exception {
// Save the original values of the timeout-related preferences
fgTimeoutEnabled = Platform.getPreferencesService().getBoolean(
GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT,
false,
null );
fgTimeout = Platform.getPreferencesService().getInt(
GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT_VALUE,
IGdbDebugPreferenceConstants.COMMAND_TIMEOUT_VALUE_DEFAULT,
null );
fgAutoTerminate = Platform.getPreferencesService().getBoolean(
GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB,
true,
null );
}
@Override
public void doBeforeTest() throws Exception {
removeTeminatedLaunchesBeforeTest();
setLaunchAttributes();
// Can't run the launch right away because each test needs to first set some
// parameters. The individual tests will be responsible for starting the launch.
}
@Override
public void doAfterTest() throws Exception {
// Don't call super here, as each test needs to deal with the launch in its own way
// Restore the different preferences we might have changed
IEclipsePreferences node = InstanceScope.INSTANCE.getNode( GdbPlugin.PLUGIN_ID );
node.putBoolean( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT, fgTimeoutEnabled );
node.putInt( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT_VALUE, fgTimeout );
node.putBoolean( IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB, fgAutoTerminate );
}
/**
* Sends a command to which GDB will take a long time to reply, so as to generate a timeout.
* This is done after the launch has completed and while the debug session is ongoing.
*/
@Test
public void commandTimedOutDuringSession() throws Exception {
// Enable timeout
IEclipsePreferences node = InstanceScope.INSTANCE.getNode( GdbPlugin.PLUGIN_ID );
node.putBoolean( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT, true );
node.putInt( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT_VALUE, 2000 );
// Note that sending a "target-select" command when a program is running will kill the program.
// If that triggers us to kill GDB, then our testcase won't have time to timeout.
// Therefore we set the preference to keep GDB alive even if the program is no longer running
node.putBoolean( IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB, false );
doLaunch();
final DsfSession session = getGDBLaunch().getSession();
ServiceEventWaitor<ICommandControlShutdownDMEvent> shutdownEventWaitor = new ServiceEventWaitor<ICommandControlShutdownDMEvent>(
session,
ICommandControlShutdownDMEvent.class);
// Send the command that will timeout
Query<MIInfo> query = new Query<MIInfo>() {
@Override
protected void execute( DataRequestMonitor<MIInfo> rm ) {
DsfServicesTracker tracker = new DsfServicesTracker( TestsPlugin.getBundleContext(), session.getId() );
ICommandControlService commandService = tracker.getService( ICommandControlService.class );
tracker.dispose();
commandService.queueCommand( new MITargetSelect( commandService.getContext(), "localhost", "1", false ), rm );
}
};
try {
session.getExecutor().execute( query );
query.get();
// Cleanup in case the query does not throw the expected exception
super.doAfterTest();
Assert.fail( "Command is expected to timeout" );
}
catch( Exception e ) {
processException( e );
}
// Make sure we receive a shutdown event to confirm we have aborted the session
shutdownEventWaitor.waitForEvent(TestsPlugin.massageTimeout(5000));
// It can take a moment from when the shutdown event is received to when
// the launch is actually terminated. Make sure that the launch does
// terminate itself.
assertLaunchTerminates();
}
/**
* Sends a command to which GDB will take a long time to reply, so as to generate a timeout.
* This is done during the launch to verify that we properly handle that case.
*/
@Test
public void commandTimedOutDuringLaunch() {
// Enable timeout
IEclipsePreferences node = InstanceScope.INSTANCE.getNode( GdbPlugin.PLUGIN_ID );
node.putBoolean( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT, true );
// Timeout must be shorter than the launch's timeout of 2 seconds (see BaseTestCase.doLaunch())
node.putInt( IGdbDebugPreferenceConstants.PREF_COMMAND_TIMEOUT_VALUE, 1000 );
// Setup a remote launch so that it sends a "-target-remote" as part of the
// launch steps.
setLaunchAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE,
IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE );
// We won't start gdbserver, so the command will timeout
setLaunchAttribute( ITestConstants.LAUNCH_GDB_SERVER, false);
try {
doLaunch();
// Cleanup in case the launch does not throw the expected exception
super.doAfterTest();
Assert.fail( "Launch is expected to fail" );
}
catch( Exception e ) {
processException( e );
}
}
/**
* Checks whether the given exception is an instance of {@link CoreException}
* with the status code 20100 which indicates that a gdb command has been timed out.
* 20100 comes from GDBControl.STATUS_CODE_COMMAND_TIMED_OUT which is private
*/
private void processException( Exception e ) {
Throwable t = getExceptionCause( e );
if (t instanceof CoreException && ((CoreException)t).getStatus().getCode() == 20100) {
// this is the exception we are looking for
return;
}
throw new AssertionError("Unexpected exception", e);
}
private Throwable getExceptionCause(Throwable e) {
Throwable current = e;
while ( true ) {
Throwable t = (current).getCause();
if ( t == null )
break;
current = t;
}
return current;
}
}