/******************************************************************************* * 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.framework; import static org.junit.Assert.fail; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import junit.framework.Assert; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.Query; import org.eclipse.cdt.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IExpressions; import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext; import org.eclipse.cdt.dsf.debug.service.IFormattedValues; import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext; import org.eclipse.cdt.dsf.debug.service.IFormattedValues.IFormattedDataDMContext; 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.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.StepType; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMData; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.IMIRunControl; import org.eclipse.cdt.dsf.mi.service.MIStack; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MISignalEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakListInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint; 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.SyncUtil.DefaultTimeouts.ETimeout; import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; /** * Timeout wait values are in milliseconds, or WAIT_FOREVER. */ public class SyncUtil { private static IGDBControl fGdbControl; private static IMIRunControl fRunControl; private static MIStack fStack; private static IExpressions fExpressions; private static DsfSession fSession; private static CommandFactory fCommandFactory; private static IGDBProcesses fProcessesService; // Initialize some common things, once the session has been established public static void initialize(DsfSession session) throws Exception { fSession = session; Runnable runnable = new Runnable() { public void run() { DsfServicesTracker tracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId()); fGdbControl = tracker.getService(IGDBControl.class); fRunControl = tracker.getService(IMIRunControl.class); fStack = tracker.getService(MIStack.class); fExpressions = tracker.getService(IExpressions.class); fProcessesService = tracker.getService(IGDBProcesses.class); fCommandFactory = fGdbControl.getCommandFactory(); tracker.dispose(); } }; fSession.getExecutor().submit(runnable).get(); } public static MIStoppedEvent step(int numSteps, StepType stepType) throws Throwable { MIStoppedEvent retVal = null; for (int i=0; i<numSteps; i++) { retVal = step(stepType, DefaultTimeouts.get(ETimeout.step)); } return retVal; } public static MIStoppedEvent step(final StepType stepType, int numSteps, int timeout) throws Throwable { MIStoppedEvent retVal = null; for (int i=0; i<numSteps; i++) { retVal = step(stepType, timeout); } return retVal; } public static MIStoppedEvent step(StepType stepType) throws Throwable { return step(stepType, DefaultTimeouts.get(ETimeout.step)); } public static MIStoppedEvent step(StepType stepType, int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); return step(containerDmc, stepType, timeout); } public static MIStoppedEvent step(IExecutionDMContext dmc, StepType stepType) throws Throwable { return step(dmc, stepType, DefaultTimeouts.get(ETimeout.step)); } public static MIStoppedEvent step(final IExecutionDMContext dmc, final StepType stepType, int timeout) throws Throwable { final ServiceEventWaitor<MIStoppedEvent> eventWaitor = new ServiceEventWaitor<MIStoppedEvent>( fSession, MIStoppedEvent.class); fRunControl.getExecutor().submit(new Runnable() { public void run() { // No need for a RequestMonitor since we will wait for the // ServiceEvent telling us the program has been suspended again switch(stepType) { case STEP_INTO: fGdbControl.queueCommand(fCommandFactory.createMIExecStep(dmc), null); break; case STEP_OVER: fGdbControl.queueCommand(fCommandFactory.createMIExecNext(dmc), null); break; case STEP_RETURN: fGdbControl.queueCommand(fCommandFactory.createMIExecFinish(fStack.createFrameDMContext(dmc, 0)), null); break; default: fail("Unsupported step type; " + stepType.toString()); } } }); // Wait for the execution to suspend after the step return eventWaitor.waitForEvent(timeout); } public static MIStoppedEvent runToLine(IExecutionDMContext dmc, String fileName, String lineNo, boolean skipBreakpoints) throws Throwable { return runToLine(dmc, fileName, lineNo, skipBreakpoints, DefaultTimeouts.get(ETimeout.runToLine)); } public static MIStoppedEvent runToLine(final IExecutionDMContext dmc, final String fileName, final String lineNo, boolean skipBreakpoints, int timeout) throws Throwable { final ServiceEventWaitor<MIStoppedEvent> eventWaitor = new ServiceEventWaitor<MIStoppedEvent>( fSession, MIStoppedEvent.class); fRunControl.getExecutor().submit(new Runnable() { public void run() { // No need for a RequestMonitor since we will wait for the // ServiceEvent telling us the program has been suspended again fGdbControl.queueCommand( fCommandFactory.createMIExecUntil(dmc, fileName + ":" + lineNo), //$NON-NLS-1$ null); } }); // Wait for the execution to suspend after the step return eventWaitor.waitForEvent(timeout); } public static MIStoppedEvent runToLine(String fileName, String lineNo, boolean skipBreakpoints) throws Throwable { return runToLine(fileName, lineNo, skipBreakpoints, DefaultTimeouts.get(ETimeout.runToLine)); } public static MIStoppedEvent runToLine(String fileName, String lineNo, boolean skipBreakpoints, int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); return runToLine(containerDmc, fileName, lineNo, skipBreakpoints, timeout); } public static MIStoppedEvent runToLine(String fileName, String lineNo) throws Throwable { return runToLine(fileName, lineNo, DefaultTimeouts.get(ETimeout.runToLine)); } public static MIStoppedEvent runToLine(String fileName, String lineNo, int timeout) throws Throwable { return runToLine(fileName, lineNo, false, timeout); } public static int addBreakpoint(String location) throws Throwable { return addBreakpoint(location, DefaultTimeouts.get(ETimeout.addBreakpoint)); } public static int addBreakpoint(String location, int timeout) throws Throwable { return addBreakpoint(location, true, timeout); } public static int addBreakpoint(String location, boolean temporary) throws Throwable { return addBreakpoint(location, temporary, DefaultTimeouts.get(ETimeout.addBreakpoint)); } public static int addBreakpoint(final String location, final boolean temporary, int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); final IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(containerDmc, IBreakpointsTargetDMContext.class); Query<MIBreakInsertInfo> query = new Query<MIBreakInsertInfo>() { @Override protected void execute(DataRequestMonitor<MIBreakInsertInfo> rm) { fGdbControl.queueCommand( fCommandFactory.createMIBreakInsert(bpTargetDmc, temporary, false, null, 0, location, 0), rm); } }; fGdbControl.getExecutor().execute(query); MIBreakInsertInfo info = query.get(timeout, TimeUnit.MILLISECONDS); return info.getMIBreakpoints()[0].getNumber(); } public static int[] getBreakpointList(int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); final IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(containerDmc, IBreakpointsTargetDMContext.class); Query<MIBreakListInfo> query = new Query<MIBreakListInfo>() { @Override protected void execute(DataRequestMonitor<MIBreakListInfo> rm) { fGdbControl.queueCommand(fCommandFactory.createMIBreakList(bpTargetDmc), rm); } }; fGdbControl.getExecutor().execute(query); MIBreakListInfo info = query.get(timeout, TimeUnit.MILLISECONDS); MIBreakpoint[] breakpoints = info.getMIBreakpoints(); int[] result = new int[breakpoints.length]; for (int i = 0; i < breakpoints.length; i++) { result[i] = breakpoints[i].getNumber(); } return result; } public static void deleteBreakpoint(int breakpointIndex, int timeout) throws Throwable { deleteBreakpoint(new int[] {breakpointIndex}, timeout); } public static void deleteBreakpoint(final int[] breakpointIndices, int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); final IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(containerDmc, IBreakpointsTargetDMContext.class); Query<MIInfo> query = new Query<MIInfo>() { @Override protected void execute(DataRequestMonitor<MIInfo> rm) { fGdbControl.queueCommand( fCommandFactory.createMIBreakDelete(bpTargetDmc, breakpointIndices), rm); } }; fGdbControl.getExecutor().execute(query); query.get(timeout, TimeUnit.MILLISECONDS); } public static MIStoppedEvent resumeUntilStopped(final IExecutionDMContext dmc, int timeout) throws Throwable { final ServiceEventWaitor<MIStoppedEvent> eventWaitor = new ServiceEventWaitor<MIStoppedEvent>( fSession, MIStoppedEvent.class); fRunControl.getExecutor().submit(new Runnable() { public void run() { // No need for a RequestMonitor since we will wait for the // ServiceEvent telling us the program has been suspended again fGdbControl.queueCommand( fCommandFactory.createMIExecContinue(dmc), null); } }); // Wait for the execution to suspend after the step return eventWaitor.waitForEvent(timeout); } public static MIStoppedEvent resumeUntilStopped() throws Throwable { return resumeUntilStopped(DefaultTimeouts.get(ETimeout.resumeUntilStopped)); } public static MIStoppedEvent resumeUntilStopped(int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); return resumeUntilStopped(containerDmc, timeout); } public static MIRunningEvent resume(final IExecutionDMContext dmc, int timeout) throws Throwable { final ServiceEventWaitor<MIRunningEvent> eventWaitor = new ServiceEventWaitor<MIRunningEvent>( fSession, MIRunningEvent.class); fRunControl.getExecutor().submit(new Runnable() { public void run() { // No need for a RequestMonitor since we will wait for the // ServiceEvent telling us the program has been resumed fGdbControl.queueCommand( fCommandFactory.createMIExecContinue(dmc), null); } }); // Wait for the execution to start after the step return eventWaitor.waitForEvent(timeout); } public static MIRunningEvent resume() throws Throwable { return resume(DefaultTimeouts.get(ETimeout.resume)); } public static MIRunningEvent resume(int timeout) throws Throwable { IContainerDMContext containerDmc = SyncUtil.getContainerContext(); return resume(containerDmc, timeout); } public static MIStoppedEvent waitForStop() throws Throwable { return waitForStop(DefaultTimeouts.get(ETimeout.waitForStop)); } // This method is risky. If the command to resume/step execution // is sent and the stopped event is received before we call this method // here, then we will miss the stopped event. // Normally, one shoudl initialize the ServiveEventWaitor before // triggering the resume to make sure not to miss the stopped event. // However, in some case this method will still work, for instance // if there is a sleep in the code between the resume and the time // it stops; this will give us plenty of time to call this method. public static MIStoppedEvent waitForStop(int timeout) throws Throwable { final ServiceEventWaitor<MIStoppedEvent> eventWaitor = new ServiceEventWaitor<MIStoppedEvent>( fSession, MIStoppedEvent.class); // Wait for the execution to suspend return eventWaitor.waitForEvent(timeout); } public static MIStoppedEvent runToLocation(String location) throws Throwable { return runToLocation(location, DefaultTimeouts.get(ETimeout.runToLocation)); } public static MIStoppedEvent runToLocation(String location, int timeout) throws Throwable { // Set a temporary breakpoint and run to it. // Note that if there were other breakpoints set ahead of this one, // they will stop execution earlier than planned addBreakpoint(location, true, timeout); return resumeUntilStopped(timeout); } public static IFrameDMContext getStackFrame(final IExecutionDMContext execCtx, final int level) throws Throwable { Query<IFrameDMContext> query = new Query<IFrameDMContext>() { @Override protected void execute(final DataRequestMonitor<IFrameDMContext> rm) { fStack.getFrames(execCtx, new DataRequestMonitor<IFrameDMContext[]>(ImmediateExecutor.getInstance(), rm) { @Override protected void handleSuccess() { if (getData().length > level) { rm.setData(getData()[level]); } else { rm.setStatus(new Status(IStatus.ERROR, TestsPlugin.PLUGIN_ID, "Frame not available")); } rm.done(); } }); } }; fSession.getExecutor().execute(query); return query.get(500, TimeUnit.MILLISECONDS); } public static IFrameDMData getFrameData(final IExecutionDMContext execCtx, final int level) throws Throwable { Query<IFrameDMData> query = new Query<IFrameDMData>() { @Override protected void execute(final DataRequestMonitor<IFrameDMData> rm) { fStack.getFrames(execCtx, level, level, new DataRequestMonitor<IFrameDMContext[]>(ImmediateExecutor.getInstance(), rm) { @Override protected void handleSuccess() { IFrameDMContext[] frameDmcs = getData(); assert frameDmcs != null; assert frameDmcs.length == 1; fStack.getFrameData(frameDmcs[0], rm); } }); } }; fSession.getExecutor().execute(query); return query.get(500, TimeUnit.MILLISECONDS); } public static IExpressionDMContext createExpression(final IDMContext parentCtx, final String expression) throws Throwable { Callable<IExpressionDMContext> callable = new Callable<IExpressionDMContext>() { public IExpressionDMContext call() throws Exception { return fExpressions.createExpression(parentCtx, expression); } }; return fSession.getExecutor().submit(callable).get(); } public static FormattedValueDMContext getFormattedValue( final IFormattedValues service, final IFormattedDataDMContext dmc, final String formatId) throws Throwable { Callable<FormattedValueDMContext> callable = new Callable<FormattedValueDMContext>() { public FormattedValueDMContext call() throws Exception { return service.getFormattedValueContext(dmc, formatId); } }; return fSession.getExecutor().submit(callable).get(); } public static IMIExecutionDMContext createExecutionContext(final IContainerDMContext parentCtx, final int threadId) throws Throwable { Callable<IMIExecutionDMContext> callable = new Callable<IMIExecutionDMContext>() { public IMIExecutionDMContext call() throws Exception { String threadIdStr = Integer.toString(threadId); IProcessDMContext processDmc = DMContexts.getAncestorOfType(parentCtx, IProcessDMContext.class); IThreadDMContext threadDmc = fProcessesService.createThreadContext(processDmc, threadIdStr); return fProcessesService.createExecutionContext(parentCtx, threadDmc, threadIdStr); } }; return fSession.getExecutor().submit(callable).get(); } static class DefaultTimeouts { /** * Overridable default timeout values. An override is specified using a * system property that is "dsf.gdb.tests.timeout.default." plus the * name of the enum below. */ enum ETimeout { addBreakpoint, deleteBreakpoint, getBreakpointList, createExecutionContext, createExpression, getFormattedValue, getStackFrame, resume, resumeUntilStopped, runToLine, runToLocation, step, waitForStop } /** * Map of timeout enums to their <b>harcoded</b> default value )in * milliseconds). These can be individually overridden with a system * property. * * <p> * In practice, these operations are either very quick or the amount of * time is hard to predict (depends on what the test is doing). For ones * that are quick, we allot 1 second, which is ample. For the unknowns * we allows 10 seconds, which is probably ample in most cases. Tests * can provide larger values as needed in specific SyncUtil calls. */ private static Map<ETimeout,Integer> sTimeouts = new HashMap<ETimeout, Integer>(); static { sTimeouts.put(ETimeout.addBreakpoint, 1000); sTimeouts.put(ETimeout.deleteBreakpoint, 1000); sTimeouts.put(ETimeout.getBreakpointList, 1000); sTimeouts.put(ETimeout.createExecutionContext, 1000); sTimeouts.put(ETimeout.createExpression, 1000); sTimeouts.put(ETimeout.getFormattedValue, 1000); sTimeouts.put(ETimeout.getStackFrame, 1000); sTimeouts.put(ETimeout.resume, 1000); sTimeouts.put(ETimeout.resumeUntilStopped, 10000); // 10 seconds sTimeouts.put(ETimeout.runToLine, 10000); // 10 seconds sTimeouts.put(ETimeout.runToLocation, 10000); // 10 seconds sTimeouts.put(ETimeout.step, 1000); sTimeouts.put(ETimeout.waitForStop, 10000); // 10 seconds } /** * Get the default timeout to use when the caller of a SyncUtil method * doesn't specify one. We honor overrides specified via system * properties, as well as apply the multiplier that can also be * specified via a system property. * * @param timeout * the timeout enum * @return the default value */ static int get(ETimeout timeout) { int value = -1; final String propname = "dsf.gdb.tests.timeout.default." + timeout.toString(); final String prop = System.getProperty(propname); if (prop != null) { try { value = Integer.valueOf(value); if (value < 0) { TestsPlugin.log(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "\"" + propname + "\" property incorrectly specified. Should be an integer value or not specified at all.")); //$NON-NLS-1$ value = -1; } } catch (NumberFormatException exc) { TestsPlugin.log(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "\"" + propname + "\" property incorrectly specified. Should be an integer value or not specified at all.")); //$NON-NLS-1$ value = -1; } } if (value == -1) { value = sTimeouts.get(timeout); } assert value >= 0; return TestsPlugin.massageTimeout(value); } } /** * Utility method to return the container DM context. This can be used only by * tests that deal with a single heavyweight process. If more than one * process is available, this method will fail. * * <p> * This must NOT be called from the DSF executor. * * @return the process context * @throws InterruptedException */ @ThreadSafeAndProhibitedFromDsfExecutor("fSession.getExecutor()") public static IContainerDMContext getContainerContext() throws InterruptedException { assert !fProcessesService.getExecutor().isInExecutorThread(); Query<IContainerDMContext> query = new Query<IContainerDMContext>() { @Override protected void execute(final DataRequestMonitor<IContainerDMContext> rm) { fProcessesService.getProcessesBeingDebugged( fGdbControl.getContext(), new DataRequestMonitor<IDMContext[]>(ImmediateExecutor.getInstance(), null) { @Override protected void handleCompleted() { if (isSuccess()) { IDMContext[] contexts = getData(); Assert.assertNotNull("invalid return value from service", contexts); Assert.assertEquals("unexpected number of processes", 1, contexts.length); IDMContext context = contexts[0]; Assert.assertNotNull("unexpected process context type ", context); rm.done((IContainerDMContext)context); } else { rm.done(getStatus()); } } }); } }; fGdbControl.getExecutor().execute(query); try { return query.get(TestsPlugin.massageTimeout(2000), TimeUnit.MILLISECONDS); } catch (Exception e) { fail(e.getMessage()); } return null; } /** * Utility method to return the execution DM context. */ @ThreadSafeAndProhibitedFromDsfExecutor("fSession.getExecutor()") public static IMIExecutionDMContext getExecutionContext(final int threadIndex) throws InterruptedException { assert !fProcessesService.getExecutor().isInExecutorThread(); final IContainerDMContext containerDmc = SyncUtil.getContainerContext(); Query<IMIExecutionDMContext> query = new Query<IMIExecutionDMContext>() { @Override protected void execute(final DataRequestMonitor<IMIExecutionDMContext> rm) { fProcessesService.getProcessesBeingDebugged( containerDmc, new DataRequestMonitor<IDMContext[]>(ImmediateExecutor.getInstance(), null) { @Override protected void handleCompleted() { if (isSuccess()) { IDMContext[] threads = getData(); Assert.assertNotNull("invalid return value from service", threads); Assert.assertTrue("unexpected number of threads", threadIndex < threads.length); IDMContext thread = threads[threadIndex]; Assert.assertNotNull("unexpected thread context type ", thread); rm.setData((IMIExecutionDMContext)thread); } else { rm.setStatus(getStatus()); } rm.done(); } }); } }; fGdbControl.getExecutor().execute(query); try { return query.get(TestsPlugin.massageTimeout(2000), TimeUnit.MILLISECONDS); } catch (Exception e) { fail(e.getMessage()); } return null; } /** * Restart the program. */ public static MIStoppedEvent restart(final GdbLaunch launch) throws Throwable { final IContainerDMContext containerDmc = getContainerContext(); // Check if restart is allowed Query<Boolean> query = new Query<Boolean>() { @Override protected void execute(final DataRequestMonitor<Boolean> rm) { fProcessesService.canRestart( containerDmc, new DataRequestMonitor<Boolean>(ImmediateExecutor.getInstance(), rm) { @Override protected void handleSuccess() { rm.setData(getData()); rm.done(); } }); } }; fGdbControl.getExecutor().execute(query); boolean canRestart = query.get(500, TimeUnit.MILLISECONDS); if (!canRestart) { throw new CoreException(new Status(IStatus.ERROR, TestsPlugin.PLUGIN_ID, "Unable to restart")); } // Now wait for the stopped event of the restart final ServiceEventWaitor<MIStoppedEvent> eventWaitor = new ServiceEventWaitor<MIStoppedEvent>( fSession, MIStoppedEvent.class); // Perform the restart Query<IContainerDMContext> query2 = new Query<IContainerDMContext>() { @SuppressWarnings("unchecked") @Override protected void execute(final DataRequestMonitor<IContainerDMContext> rm) { Map<String, Object> attributes = null; try { attributes = launch.getLaunchConfiguration().getAttributes(); } catch (CoreException e) {} fProcessesService.restart(containerDmc, attributes, rm); } }; fGdbControl.getExecutor().execute(query2); query2.get(500, TimeUnit.MILLISECONDS); MIStoppedEvent event = eventWaitor.waitForEvent(DefaultTimeouts.get(ETimeout.waitForStop)); if (event instanceof MISignalEvent) { // This is not the stopped event we were waiting for. Get the next one. event = eventWaitor.waitForEvent(DefaultTimeouts.get(ETimeout.waitForStop)); } return event; } }