From d941a275b4174bd3733a8efbc2cfa7662312c050 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 23 Jan 2017 11:35:43 -0500 Subject: [PATCH] Add JUnit test to verify a query is answered as expected. Change-Id: I603a89822e1fcbb1e7126150fd8e4de17fd7a141 --- .../cdt/tests/dsf/gdb/framework/SyncUtil.java | 78 +++++--- .../gdb/tests/MIRunControlReverseTest.java | 179 ++++++++++++++++++ .../cdt/tests/dsf/gdb/tests/SuiteGdb.java | 1 + 3 files changed, 234 insertions(+), 24 deletions(-) create mode 100644 dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MIRunControlReverseTest.java diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/framework/SyncUtil.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/framework/SyncUtil.java index 2b61648bedc..c6a61a4b2aa 100644 --- a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/framework/SyncUtil.java +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/framework/SyncUtil.java @@ -126,20 +126,24 @@ public class SyncUtil { } public static MIStoppedEvent step(int numSteps, StepType stepType) throws Throwable { + return step(numSteps,stepType, false); + } + + public static MIStoppedEvent step(int numSteps, StepType stepType, boolean reverse) throws Throwable { MIStoppedEvent retVal = null; for (int i=0; i eventWaitor = new ServiceEventWaitor( fSession, MIStoppedEvent.class); - fRunControl.getExecutor().submit(new Runnable() { - @Override - 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()); + if (!reverse) { + fRunControl.getExecutor().submit(new Runnable() { + @Override + 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()); + } } - } - }); + }); + } else { + fRunControl.getExecutor().submit(new Runnable() { + @Override + 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.createMIExecReverseStep(dmc), null); + break; + case STEP_OVER: + fGdbControl.queueCommand(fCommandFactory.createMIExecReverseNext(dmc), null); + break; + case STEP_RETURN: + fGdbControl.queueCommand(fCommandFactory.createMIExecUncall(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(massagedTimeout); diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MIRunControlReverseTest.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MIRunControlReverseTest.java new file mode 100644 index 00000000000..b5c7a406434 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MIRunControlReverseTest.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + *******************************************************************************/ +package org.eclipse.cdt.tests.dsf.gdb.tests; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.Query; +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.IRunControl.StepType; +import org.eclipse.cdt.dsf.debug.service.command.IEventListener; +import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl; +import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; +import org.eclipse.cdt.dsf.mi.service.IMIRunControl; +import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; +import org.eclipse.cdt.dsf.mi.service.command.output.MILogStreamOutput; +import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord; +import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; +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.SyncUtil; +import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +/** + * Tests MIRunControl class for some reverse debugging scenarios. + */ +@RunWith(Parameterized.class) +public class MIRunControlReverseTest extends BaseParametrizedTestCase { + + private DsfServicesTracker fServicesTracker; + private DsfSession fSession; + private IGDBControl fGDBCtrl; + private IMIRunControl fRunCtrl; + private IExpressions fExpressions; + + /* + * Name of the executable + */ + private static final String EXEC_NAME = "ExpressionTestApp.exe"; + + @Override + public void doBeforeTest() throws Exception { + assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_0); + super.doBeforeTest(); + + fSession = getGDBLaunch().getSession(); + + Runnable runnable = new Runnable() { + @Override + public void run() { + fServicesTracker = + new DsfServicesTracker(TestsPlugin.getBundleContext(), + fSession.getId()); + fGDBCtrl = fServicesTracker.getService(IGDBControl.class); + fRunCtrl = fServicesTracker.getService(IMIRunControl.class); + fExpressions = fServicesTracker.getService(IExpressions.class); + } + }; + fSession.getExecutor().submit(runnable).get(); + } + + + @Override + public void doAfterTest() throws Exception { + super.doAfterTest(); + + if (fServicesTracker != null) { + fServicesTracker.dispose(); + } + } + + @Override + protected void setLaunchAttributes() { + super.setLaunchAttributes(); + + setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, + EXEC_PATH + EXEC_NAME); + } + + /** + * This test verifies that we properly handle a query sent from GDB. + * When running the basic console (GDB < 7.12) GDB answer's the query itself + * and so we should not answer again. When using the full Console, GDB does + * not answer so we must answer ourselves. When using the full console + * we have both an MI and a CLI channel open; this test verifies how + * we react to getting query on the MI-only channel, which is actually + * not something GDB should do, but at least in 7.12, it does happen. + */ + @Test + public void testQueryHandling() throws Throwable { + SyncUtil.runToLocation("testLocals"); + + assertTrue("Reverse debugging is not supported", fRunCtrl instanceof IReverseRunControl); + final IReverseRunControl reverseService = (IReverseRunControl)fRunCtrl; + + Query query = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + reverseService.enableReverseMode(fGDBCtrl.getContext(), true, + new ImmediateRequestMonitor(rm) { + @Override + protected void handleSuccess() { + reverseService.isReverseModeEnabled(fGDBCtrl.getContext(), rm); + } + }); + } + }; + + fSession.getExecutor().execute(query); + Boolean enabled = query.get(TestsPlugin.massageTimeout(500), TimeUnit.MILLISECONDS); + assertTrue("Reverse debugging should be enabled", enabled); + + // Step forward a couple of times + SyncUtil.step(2, StepType.STEP_OVER); + // Step back once to start using the record buffer + MIStoppedEvent stoppedEvent = SyncUtil.step(1, StepType.STEP_OVER, true); + + IExpressionDMContext expr = SyncUtil.createExpression(stoppedEvent.getDMContext(), "lIntVar"); + + // Register an event listener to check that we don't send out a 'y' + // command when we don't need to. + boolean yesCommandSent[] = new boolean[1]; + yesCommandSent[0] = false; + fGDBCtrl.addEventListener(new IEventListener() { + + @Override + public void eventReceived(Object output) { + for (MIOOBRecord oobr : ((MIOutput)output).getMIOOBRecords()) { + if (oobr instanceof MILogStreamOutput) { + MILogStreamOutput stream = (MILogStreamOutput) oobr; + if (stream.getCString().indexOf("Undefined command: \"y") != -1) { + yesCommandSent[0] = true; + } + } + } + } + }); + + String newValue = "8989"; + // Now modify a variable to trigger the query as we are modifying a recorded value + Query writeQuery = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + fExpressions.writeExpression(expr, newValue, IFormattedValues.DECIMAL_FORMAT, rm); + } + }; + fSession.getExecutor().execute(writeQuery); + try { + writeQuery.get(TestsPlugin.massageTimeout(2000), TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + assert false : "Timed-out waiting to write to a variable, probably because of a query."; + } + + // Now verify that the write work and that GDB is answering + String value = SyncUtil.getExpressionValue(expr, IFormattedValues.DECIMAL_FORMAT); + assertEquals("Value was not writtent to variable", newValue, value); + + assertTrue("Sent a 'y' command unnecessarily", !yesCommandSent[0]); //$NON-NLS-1$ + } +} diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java index a944491cd4f..1a0c5fd533c 100644 --- a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java @@ -43,6 +43,7 @@ import org.junit.runners.Suite; OperationsWhileTargetIsRunningTest.class, MIRunControlTest.class, MIRunControlTargetAvailableTest.class, + MIRunControlReverseTest.class, GDBPatternMatchingExpressionsTest.class, GDBMultiNonStopRunControlTest.class, GDBConsoleBreakpointsTest.class,