/******************************************************************************* * Copyright (c) 2007, 2010 Ericsson 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: * Ericsson AB - Initial implementation of Test cases *******************************************************************************/ package org.eclipse.cdt.tests.dsf.gdb.tests; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.LinkedList; import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMData; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; import org.eclipse.cdt.dsf.debug.service.IRunControl.StepType; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.IMIProcesses; import org.eclipse.cdt.dsf.mi.service.IMIRunControl; import org.eclipse.cdt.dsf.mi.service.MIProcesses; import org.eclipse.cdt.dsf.mi.service.MIRunControl; import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; 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.AsyncCompletionWaitor; import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner; import org.eclipse.cdt.tests.dsf.gdb.framework.BaseTestCase; import org.eclipse.cdt.tests.dsf.gdb.framework.ServiceEventWaitor; import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil; import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests MIRunControl class for Multi-threaded application. */ @RunWith(BackgroundRunner.class) public class MIRunControlTest extends BaseTestCase { /** * The cygwin runtime/emulation spawns a thread, so even the most basic * program has two threads. The tests have to take this into consideration * since the same is not true in other environments (POSIX, MinGW). We * examine the test program and set this flag to true if it uses the cygwin * dll. */ private static boolean sProgramIsCygwin; private DsfServicesTracker fServicesTracker; private IGDBControl fGDBCtrl; private IMIRunControl fRunCtrl; private IContainerDMContext fContainerDmc; private IExecutionDMContext fThreadExecDmc; // line numbers in MultiThread.cc static final int LINE_MAIN_RETURN = 63; static final int LINE_MAIN_PRINTF = 41; /* * Path to executable */ private static final String EXEC_PATH = "data/launch/bin/"; /* * Name of the executable */ private static final String EXEC_NAME = "MultiThread.exe"; private static final String SOURCE_NAME = "MultiThread.cc"; @Before public void init() throws Exception { final DsfSession session = getGDBLaunch().getSession(); Runnable runnable = new Runnable() { public void run() { fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), session.getId()); fGDBCtrl = fServicesTracker.getService(IGDBControl.class); IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); IProcessDMContext procDmc = procService.createProcessContext(fGDBCtrl.getContext(), MIProcesses.UNIQUE_GROUP_ID); fContainerDmc = procService.createContainerContext(procDmc, MIProcesses.UNIQUE_GROUP_ID); IThreadDMContext threadDmc = procService.createThreadContext(procDmc, "1"); fThreadExecDmc = procService.createExecutionContext(fContainerDmc, threadDmc, "1"); fRunCtrl = fServicesTracker.getService(IMIRunControl.class); } }; session.getExecutor().submit(runnable).get(); } @After public void tearDown() { fServicesTracker.dispose(); } @BeforeClass public static void beforeClassMethod() { setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, EXEC_PATH + EXEC_NAME); // This is crude, but effective. We need to determine if the program was // built with cygwin. The easiest way is to scan the binary file looking // for 'cygwin1.dll'. In the real world, this wouldn't cut mustard, but // since this is just testing code, and we control the programs, it's a // no brainer. if (Platform.getOS().equals(Platform.OS_WIN32)) { // This is interesting. Our tests rely on the working directory. // That is, we specify a program path in the launch configuration // that is relative to the working directory. File file = new File(EXEC_PATH + EXEC_NAME); FileInputStream fis = null; try { fis = new FileInputStream(file); } catch (FileNotFoundException e) { Assert.fail(e.getLocalizedMessage()); return; // needed to avoid warning at fis usage below } final String MATCH = "cygwin1.dll"; final int MATCH_LEN = MATCH.length(); int i = 0; int ch = 0; while (true) { try { ch = fis.read(); } catch (IOException e) { Assert.fail("Problem inspecting file to see if it's a cygwin executable : " + e.getLocalizedMessage()); } if (ch == -1) { // EOF break; } if (ch == MATCH.charAt(i)) { if (i == MATCH_LEN - 1) { sProgramIsCygwin = true; break; // found it! } i++; } else { i = 0; } } } } /* * For Multi-threaded application - In case of one thread, Thread id should start with 1. */ @Test public void getExecutionContext() throws InterruptedException{ final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Create a request monitor */ final DataRequestMonitor<IExecutionDMContext[]> rm = new DataRequestMonitor<IExecutionDMContext[]>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); /* * Test getExecutionContexts() when only one thread exist. */ fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionContexts(containerDmc, rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); /* * Get data from the Request Monitor */ IExecutionDMContext[] ctxts = (IExecutionDMContext[])wait.getReturnInfo(); // Context can not be null Assert.assertNotNull(ctxts); Assert.assertEquals("Unexpected number of threads for a simple program", sProgramIsCygwin ? 2 : 1, ctxts.length); // The ordering of the contexts is not deterministic LinkedList<Integer> ids = new LinkedList<Integer>(Arrays.asList(new Integer[] {1})); if (sProgramIsCygwin) { ids.add(new Integer(2)); } assertTrue(ids.remove(new Integer(((IMIExecutionDMContext)ctxts[0]).getThreadId()))); if (sProgramIsCygwin) { assertTrue(ids.remove(new Integer(((IMIExecutionDMContext)ctxts[1]).getThreadId()))); } wait.waitReset(); } /* * Get Execution DMCs for a valid container DMC * Testing for two execution DMC with id 1 & 2 */ @Test public void getExecutionContexts() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Create a request monitor */ final DataRequestMonitor<IExecutionDMContext[]> rmExecutionCtxts = new DataRequestMonitor<IExecutionDMContext[]>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; // Prepare a waiter to make sure we have received the thread started event final ServiceEventWaitor<IStartedDMEvent> startedEventWaitor = new ServiceEventWaitor<IStartedDMEvent>( getGDBLaunch().getSession(), IStartedDMEvent.class); // Run past the line that creates a thread and past the sleep that // follows it. This is a bit tricky because the code that creates the // thread is conditional depending on environment. Run to the printf // before it (which is common), then do step operations over the // non-common code (but same number of lines) SyncUtil.runToLine(fContainerDmc, SOURCE_NAME, Integer.toString(LINE_MAIN_PRINTF), true); SyncUtil.step(StepType.STEP_OVER); // over the printf SyncUtil.step(StepType.STEP_OVER); // over the create-thread call SyncUtil.step(StepType.STEP_OVER, TestsPlugin.massageTimeout(2000)); // over the one second sleep // Make sure thread started event was received IStartedDMEvent startedEvent = startedEventWaitor.waitForEvent(TestsPlugin.massageTimeout(1000)); Assert.assertEquals("Thread created event is for wrong thread id", sProgramIsCygwin ? 3 : 2, ((IMIExecutionDMContext)startedEvent.getDMContext()).getThreadId()); final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); /* * Test getExecutionContexts for a valid container DMC */ fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionContexts(containerDmc, rmExecutionCtxts); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); /* * Get data */ IExecutionDMContext[] data = rmExecutionCtxts.getData(); /* * Contexts returned can not be null */ Assert.assertNotNull(data); Assert.assertEquals("Unexpected number of threads", sProgramIsCygwin ? 3 : 2, data.length); // The ordering of the contexts is not deterministic LinkedList<Integer> ids = new LinkedList<Integer>(Arrays.asList(new Integer[] {1,2})); if (sProgramIsCygwin) { ids.add(new Integer(3)); } assertTrue(ids.remove(new Integer(((IMIExecutionDMContext)data[0]).getThreadId()))); assertTrue(ids.remove(new Integer(((IMIExecutionDMContext)data[1]).getThreadId()))); if (sProgramIsCygwin) { assertTrue(ids.remove(new Integer(((IMIExecutionDMContext)data[2]).getThreadId()))); } } /* * Testing getModelData() for ExecutionDMC */ @Test public void getModelDataForThread() throws InterruptedException{ final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Create a request monitor */ final DataRequestMonitor<IExecutionDMData> rm = new DataRequestMonitor<IExecutionDMData>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); /* * Call getModelData for Execution DMC */ fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionData(((MIRunControl)fRunCtrl).createMIExecutionContext(containerDmc, 1), rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); IRunControl.IExecutionDMData data = rm.getData(); if(data == null) Assert.fail("No data returned."); else{ /* * getModelData should return StateChangeReason. */ Assert.assertEquals("Unexpected state change reason.", getExpectedMainThreadStopReason(), data.getStateChangeReason()); } } /** * Allows subclasses to override the expected reason for the stop on main. * @return */ protected StateChangeReason getExpectedMainThreadStopReason() { return StateChangeReason.USER_REQUEST; } @Test public void getModelDataForThreadWhenStep() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Run till step returns */ final MIStoppedEvent stoppedEvent = SyncUtil.step(StepType.STEP_OVER); final DataRequestMonitor<IExecutionDMData> rm = new DataRequestMonitor<IExecutionDMData>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; /* * getModelData for Execution DMC */ fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionData(stoppedEvent.getDMContext(), rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); IRunControl.IExecutionDMData data = rm.getData(); if(data == null) Assert.fail("No data Returned."); else{ /* * getModelData for Execution DMC in case Step has been performed. */ Assert.assertTrue("getModelData for ExecutionDMC in case of step should be STEP." , StateChangeReason.STEP == data.getStateChangeReason()); } } /* * getModelData() for ExecutionDMC when a breakpoint is hit */ @Test public void getModelDataForThreadWhenBreakpoint() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Add a breakpoint */ SyncUtil.addBreakpoint(SOURCE_NAME + ":" + LINE_MAIN_PRINTF, false); /* * Resume till the breakpoint is hit */ final MIStoppedEvent stoppedEvent = SyncUtil.resumeUntilStopped(); final DataRequestMonitor<IExecutionDMData> rm = new DataRequestMonitor<IExecutionDMData>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionData(stoppedEvent.getDMContext(), rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); IRunControl.IExecutionDMData data = rm.getData(); if(data == null) Assert.fail("No data Returned."); else{ /* * getModelData for ExecutionDMC in case a breakpoint is hit */ Assert.assertTrue("getModelData for an Execution DMC when a breakpoint is hit is not BREAKPOINT and is " + data.getStateChangeReason(), StateChangeReason.BREAKPOINT == data.getStateChangeReason()); } } /* * getModelData() for Container DMC */ @Test public void getModelDataForContainer() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); /* * Add a breakpoint */ SyncUtil.addBreakpoint(SOURCE_NAME + ":21", false); /* * Resume till the breakpoint is hit */ SyncUtil.resumeUntilStopped(); final DataRequestMonitor<IExecutionDMData> rm = new DataRequestMonitor<IExecutionDMData>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.getExecutionData(fContainerDmc, rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); IRunControl.IExecutionDMData data = rm.getData(); if(data == null) Assert.fail("No data returned."); else{ Assert.assertTrue(" State change reason for a normal execution should be BREAKPOINT instead of " + data.getStateChangeReason(), StateChangeReason.BREAKPOINT == data.getStateChangeReason()); } } /* * getExecutionContexts for an invalid container DMC */ @Ignore @Test public void getExecutionContextsForInvalidContainerDMC() throws InterruptedException{ final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); final DataRequestMonitor<IExecutionDMContext[]> rm = new DataRequestMonitor<IExecutionDMContext[]>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { if (isSuccess()) { wait.setReturnInfo(getData()); } wait.waitFinished(getStatus()); } }; // final IContainerDMContext ctxt = new GDBControlDMContext("-1", getClass().getName() + ":" + 1); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { // Pass an invalid dmc fRunCtrl.getExecutionContexts(fContainerDmc, rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue(wait.getMessage(), !wait.isOK()); IStatus status = rm.getStatus(); Assert.assertEquals("Error message for invalid container", IStatus.ERROR, status.getSeverity()); } /* * Cache after ContainerSuspendEvent should be re-set */ @Test public void cacheAfterContainerSuspendEvent() throws InterruptedException{ /* * Step to fire ContainerSuspendEvent */ try { SyncUtil.step(StepType.STEP_OVER); } catch (Throwable e) { Assert.fail("Exception in SyncUtil.SyncStep: " + e.getMessage()); } /* * Cache should be re-set */ //TODO TRy going to back end and fetching values instead //Assert.assertEquals(fRunCtrl.getCache().getCachedContext().size(), 0); } //Also test Cache after ContainerResumeEvent @Test public void resume() throws InterruptedException{ final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); final DataRequestMonitor<MIInfo> rm = new DataRequestMonitor<MIInfo>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { wait.waitFinished(getStatus()); //TestsPlugin.debug("handleCompleted over"); } }; final ServiceEventWaitor<IResumedDMEvent> eventWaitor = new ServiceEventWaitor<IResumedDMEvent>( getGDBLaunch().getSession(), IResumedDMEvent.class); final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.resume(containerDmc, rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); try { eventWaitor.waitForEvent(TestsPlugin.massageTimeout(5000)); } catch (Exception e) { Assert.fail("Exception raised:: " + e.getMessage()); e.printStackTrace(); return; } Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { wait.setReturnInfo(fRunCtrl.isSuspended(containerDmc)); wait.waitFinished(); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertFalse("Target is suspended. It should have been running", (Boolean)wait.getReturnInfo()); wait.waitReset(); } @Test public void resumeContainerContext() throws InterruptedException{ final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); final DataRequestMonitor<MIInfo> rm = new DataRequestMonitor<MIInfo>(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { wait.waitFinished(getStatus()); } }; final ServiceEventWaitor<IResumedDMEvent> eventWaitor = new ServiceEventWaitor<IResumedDMEvent>( getGDBLaunch().getSession(), IResumedDMEvent.class); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.resume(fContainerDmc, rm); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); try { eventWaitor.waitForEvent(TestsPlugin.massageTimeout(5000)); //TestsPlugin.debug("DsfMIRunningEvent received"); } catch (Exception e) { Assert.fail("Exception raised:: " + e.getMessage()); e.printStackTrace(); return; } Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { wait.setReturnInfo(fRunCtrl.isSuspended(containerDmc)); wait.waitFinished(); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertFalse("Target is suspended. It should have been running", (Boolean)wait.getReturnInfo()); wait.waitReset(); } @Test public void runToLine() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); ServiceEventWaitor<ISuspendedDMEvent> suspendedEventWaitor = new ServiceEventWaitor<ISuspendedDMEvent>( getGDBLaunch().getSession(), ISuspendedDMEvent.class); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.runToLine(fThreadExecDmc, SOURCE_NAME, LINE_MAIN_RETURN, true, new RequestMonitor(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { wait.waitFinished(getStatus()); } }); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(1000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); // The program takes five seconds to run. There's five iterations of a // loop that has a one second sleep in it suspendedEventWaitor.waitForEvent(TestsPlugin.massageTimeout(10000)); final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { wait.setReturnInfo(fRunCtrl.isSuspended(containerDmc)); wait.waitFinished(); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(5000)); Assert.assertTrue("Target is running. It should have been suspended", (Boolean)wait.getReturnInfo()); wait.waitReset(); } /** * Test that interrupting a running target works */ @Test public void interruptRunningTarget() throws Throwable { final AsyncCompletionWaitor wait = new AsyncCompletionWaitor(); ServiceEventWaitor<ISuspendedDMEvent> suspendedEventWaitor = new ServiceEventWaitor<ISuspendedDMEvent>( getGDBLaunch().getSession(), ISuspendedDMEvent.class); // Resume the target fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.resume(fThreadExecDmc, new RequestMonitor(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { wait.waitFinished(getStatus()); } }); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(1000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); // The program takes five seconds to run. There's five iterations of a // loop that has a one second sleep in it. Wait a second then attempt to // interrupt the target Thread.sleep(1000); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { fRunCtrl.suspend(fThreadExecDmc, new RequestMonitor(fRunCtrl.getExecutor(), null) { @Override protected void handleCompleted() { wait.waitFinished(getStatus()); } }); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(1000)); Assert.assertTrue(wait.getMessage(), wait.isOK()); wait.waitReset(); // Wait up to 2 seconds for the target to suspend. Should happen immediately. suspendedEventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Double check that the target is in the suspended state final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); fRunCtrl.getExecutor().submit(new Runnable() { public void run() { wait.setReturnInfo(fRunCtrl.isSuspended(containerDmc)); wait.waitFinished(); } }); wait.waitUntilDone(TestsPlugin.massageTimeout(2000)); Assert.assertTrue("Target is running. It should have been suspended", (Boolean)wait.getReturnInfo()); wait.waitReset(); } }