| /******************************************************************************* |
| * Copyright (c) 2013, 2015 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: |
| * Marc Khouzam (Ericsson) - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.tests.dsf.gdb.tests; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| 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.datamodel.DMContexts; |
| import org.eclipse.cdt.dsf.datamodel.IDMContext; |
| import org.eclipse.cdt.dsf.datamodel.IDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IExpressions; |
| import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMAddress; |
| import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IFormattedValues; |
| import org.eclipse.cdt.dsf.debug.service.IMemory; |
| import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryChangedEvent; |
| import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl; |
| import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; |
| import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl; |
| import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl.IReverseModeChangedDMEvent; |
| import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; |
| 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.DsfServiceEventHandler; |
| 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.eclipse.debug.core.model.MemoryByte; |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| /** |
| * This test case verifies that different commands issued from the |
| * GDB console cause proper updating within the CDT views. |
| */ |
| @RunWith(Parameterized.class) |
| public class GDBConsoleSynchronizingTest extends BaseParametrizedTestCase { |
| final static private String EXEC_NAME = "ConsoleSyncTestApp.exe"; |
| |
| final static private int DEFAULT_TIMEOUT = TestsPlugin.massageTimeout(1000); |
| final static private TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS; |
| final static private String NEW_VAR_VALUE = "0x12345678"; |
| final static private int NEW_VAR_SIZE = 4; // The number of bytes of NEW_VAR_VALUE |
| final static private byte[] NEW_MEM = { 0x12, 0x34, 0x56, 0x78 }; // The individual bytes of NEW_VAR_VALUE |
| |
| private DsfSession fSession; |
| private DsfServicesTracker fServicesTracker; |
| private IGDBControl fCommandControl; |
| private IMemory fMemoryService; |
| private IExpressions fExprService; |
| private IRunControl fRunControl; |
| |
| private List<IDMEvent<? extends IDMContext>> fEventsReceived = new ArrayList<IDMEvent<? extends IDMContext>>(); |
| |
| @Override |
| protected void setLaunchAttributes() { |
| super.setLaunchAttributes(); |
| setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, EXEC_PATH + EXEC_NAME); |
| } |
| |
| @Override |
| public void doBeforeTest() throws Exception { |
| assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_6); |
| super.doBeforeTest(); |
| |
| fSession = getGDBLaunch().getSession(); |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId()); |
| Assert.assertTrue(fServicesTracker != null); |
| |
| fCommandControl = fServicesTracker.getService(IGDBControl.class); |
| Assert.assertTrue(fCommandControl != null); |
| |
| fMemoryService = fServicesTracker.getService(IMemory.class); |
| Assert.assertTrue(fMemoryService != null); |
| |
| fExprService = fServicesTracker.getService(IExpressions.class); |
| Assert.assertTrue(fExprService != null); |
| |
| fRunControl = fServicesTracker.getService(IRunControl.class); |
| Assert.assertTrue(fRunControl != null); |
| |
| // Register to breakpoint events |
| fSession.addServiceEventListener(GDBConsoleSynchronizingTest.this, null); |
| } |
| }; |
| fSession.getExecutor().submit(runnable).get(); |
| } |
| |
| @Override |
| public void doAfterTest() throws Exception { |
| if (fSession != null) { |
| fSession.getExecutor().submit(() -> fSession.removeServiceEventListener(GDBConsoleSynchronizingTest.this)) |
| .get(); |
| } |
| fEventsReceived.clear(); |
| if (fServicesTracker!=null) fServicesTracker.dispose(); |
| super.doAfterTest(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////// |
| // Start of tests |
| ////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * This test verifies that setting a variable from the console |
| * using the set command will properly trigger a DSF event to |
| * indicate the change. This test makes sure the value that |
| * changes is in the memory cache also. |
| */ |
| @Test |
| public void testSettingVariableWithSetWithMemory() throws Throwable { |
| MIStoppedEvent stoppedEvent = SyncUtil.runToLocation("testMemoryChanges"); |
| |
| final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0); |
| final IExpressionDMContext exprDmc = SyncUtil.createExpression(frameDmc, "i"); |
| |
| // Read the memory that will change first, or else there will be no event for it |
| Query<IExpressionDMAddress> query = new Query<IExpressionDMAddress>() { |
| @Override |
| protected void execute(final DataRequestMonitor<IExpressionDMAddress> rm) { |
| fExprService.getExpressionAddressData(exprDmc, rm); |
| } |
| }; |
| |
| fSession.getExecutor().execute(query); |
| IExpressionDMAddress data = query.get(); |
| |
| IMemoryDMContext memoryDmc = DMContexts.getAncestorOfType(frameDmc, IMemoryDMContext.class); |
| SyncUtil.readMemory(memoryDmc, data.getAddress(), 0, 1, NEW_VAR_SIZE); |
| |
| fEventsReceived.clear(); |
| |
| String newValue = NEW_VAR_VALUE; |
| queueConsoleCommand("set variable i=" + newValue); |
| |
| IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); |
| assertEquals(1, memoryEvent.getAddresses().length); |
| assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); |
| |
| // Now verify the memory service knows the new memory value |
| MemoryByte[] memory = SyncUtil.readMemory(memoryDmc, data.getAddress(), 0, 1, NEW_VAR_SIZE); |
| assertEquals(NEW_VAR_SIZE, memory.length); |
| for (int i=0; i<NEW_VAR_SIZE; i++) { |
| if (memory[0].isBigEndian()) { |
| assertEquals(NEW_MEM[i], memory[i].getValue()); |
| } else { |
| assertEquals(NEW_MEM[i], memory[NEW_VAR_SIZE-1-i].getValue()); |
| } |
| } |
| |
| // Now verify the expressions service knows the new value |
| String exprValue = SyncUtil.getExpressionValue(exprDmc, IFormattedValues.HEX_FORMAT); |
| assertEquals(newValue, exprValue); |
| } |
| |
| private void testSettingVariableWithCommon(String commandPrefix) throws Throwable { |
| MIStoppedEvent stoppedEvent = SyncUtil.runToLocation("testMemoryChanges"); |
| |
| final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0); |
| final IExpressionDMContext exprDmc = SyncUtil.createExpression(frameDmc, "i"); |
| |
| // Read the memory that will change first, or else there will be no event for it |
| Query<IExpressionDMAddress> query = new Query<IExpressionDMAddress>() { |
| @Override |
| protected void execute(final DataRequestMonitor<IExpressionDMAddress> rm) { |
| fExprService.getExpressionAddressData(exprDmc, rm); |
| } |
| }; |
| |
| fSession.getExecutor().execute(query); |
| IExpressionDMAddress data = query.get(); |
| |
| fEventsReceived.clear(); |
| |
| final String newValue = NEW_VAR_VALUE; |
| queueConsoleCommand(commandPrefix + " = " + newValue); |
| |
| IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); |
| assertEquals(1, memoryEvent.getAddresses().length); |
| assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); |
| |
| // Now verify the memory service knows the new memory value |
| IMemoryDMContext memoryDmc = DMContexts.getAncestorOfType(frameDmc, IMemoryDMContext.class); |
| MemoryByte[] memory = SyncUtil.readMemory(memoryDmc, data.getAddress(), 0, 1, NEW_VAR_SIZE); |
| assertEquals(NEW_VAR_SIZE, memory.length); |
| for (int i=0; i<NEW_VAR_SIZE; i++) { |
| if (memory[0].isBigEndian()) { |
| assertEquals(NEW_MEM[i], memory[i].getValue()); |
| } else { |
| assertEquals(NEW_MEM[i], memory[NEW_VAR_SIZE-1-i].getValue()); |
| } |
| } |
| |
| // Now verify the expressions service knows the new value |
| String exprValue = SyncUtil.getExpressionValue(exprDmc, IFormattedValues.HEX_FORMAT); |
| assertEquals(newValue, exprValue); |
| } |
| |
| /** |
| * This test verifies that setting a variable from the console |
| * using the set command will properly trigger a DSF event to |
| * indicate the change, when the address is not in the memory cache. |
| */ |
| @Test |
| public void testSettingVariableWithSet() throws Throwable { |
| testSettingVariableWithCommon("set variable i"); |
| } |
| |
| /** |
| * This test verifies that setting a variable from the console |
| * using the print command will properly trigger a DSF event |
| * to indicate the change. |
| */ |
| @Test |
| public void testSettingVariableWithPrint() throws Throwable { |
| testSettingVariableWithCommon("print i"); |
| } |
| |
| /** |
| * This test verifies that setting a memory location from the |
| * console will properly trigger a DSF event to indicate the change. |
| */ |
| @Test |
| public void testSettingMemory() throws Throwable { |
| MIStoppedEvent stoppedEvent = SyncUtil.runToLocation("testMemoryChanges"); |
| |
| final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0); |
| final IExpressionDMContext exprDmc = SyncUtil.createExpression(frameDmc, "i"); |
| |
| // Read the memory that will change first, or else there will be no event for it |
| Query<IExpressionDMAddress> query = new Query<IExpressionDMAddress>() { |
| @Override |
| protected void execute(final DataRequestMonitor<IExpressionDMAddress> rm) { |
| fExprService.getExpressionAddressData(exprDmc, rm); |
| } |
| }; |
| |
| fSession.getExecutor().execute(query); |
| IExpressionDMAddress data = query.get(); |
| |
| fEventsReceived.clear(); |
| |
| final String newValue = NEW_VAR_VALUE; |
| queueConsoleCommand("set {int}&i=" + newValue); |
| |
| IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); |
| assertEquals(1, memoryEvent.getAddresses().length); |
| assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); |
| |
| // Now verify the memory service knows the new memory value |
| IMemoryDMContext memoryDmc = DMContexts.getAncestorOfType(frameDmc, IMemoryDMContext.class); |
| MemoryByte[] memory = SyncUtil.readMemory(memoryDmc, data.getAddress(), 0, 1, NEW_VAR_SIZE); |
| assertEquals(NEW_VAR_SIZE, memory.length); |
| for (int i=0; i<NEW_VAR_SIZE; i++) { |
| if (memory[0].isBigEndian()) { |
| assertEquals(NEW_MEM[i], memory[i].getValue()); |
| } else { |
| assertEquals(NEW_MEM[i], memory[NEW_VAR_SIZE-1-i].getValue()); |
| } |
| } |
| |
| // Now verify the expressions service knows the new value |
| String exprValue = SyncUtil.getExpressionValue(exprDmc, IFormattedValues.HEX_FORMAT); |
| assertEquals(newValue, exprValue); |
| } |
| |
| /** |
| * This test verifies that enabling reverse debugging from the |
| * console will properly trigger a DSF event to indicate the change and |
| * will be processed by the service. |
| */ |
| @Test |
| public void testEnableRecord() throws Throwable { |
| assertTrue("Reverse debugging is not supported", fRunControl instanceof IReverseRunControl); |
| final IReverseRunControl reverseService = (IReverseRunControl)fRunControl; |
| |
| SyncUtil.runToLocation("testMemoryChanges"); |
| |
| // check starting state |
| Query<Boolean> query = new Query<Boolean>() { |
| @Override |
| protected void execute(final DataRequestMonitor<Boolean> rm) { |
| reverseService.isReverseModeEnabled(fCommandControl.getContext(), rm); |
| } |
| }; |
| |
| fSession.getExecutor().execute(query); |
| Boolean enabled = query.get(); |
| assertTrue("Reverse debugging should not be enabled", !enabled); |
| |
| fEventsReceived.clear(); |
| |
| queueConsoleCommand("record"); |
| |
| // Wait for the event |
| IReverseModeChangedDMEvent event = waitForEvent(IReverseModeChangedDMEvent.class); |
| assertEquals(true, event.isReverseModeEnabled()); |
| |
| // Check the service |
| query = new Query<Boolean>() { |
| @Override |
| protected void execute(final DataRequestMonitor<Boolean> rm) { |
| reverseService.isReverseModeEnabled(fCommandControl.getContext(), rm); |
| } |
| }; |
| fSession.getExecutor().execute(query); |
| enabled = query.get(); |
| assertTrue("Reverse debugging should be enabled", enabled); |
| } |
| |
| /** |
| * This test verifies that disabling reverse debugging from the |
| * console will properly trigger a DSF event to indicate the change and |
| * will be processed by the service. |
| */ |
| @Test |
| public void testDisableRecord() throws Throwable { |
| assertTrue("Reverse debugging is not supported", fRunControl instanceof IReverseRunControl); |
| final IReverseRunControl reverseService = (IReverseRunControl)fRunControl; |
| |
| SyncUtil.runToLocation("testMemoryChanges"); |
| |
| fEventsReceived.clear(); |
| |
| // check starting state |
| Query<Boolean> query = new Query<Boolean>() { |
| @Override |
| protected void execute(final DataRequestMonitor<Boolean> rm) { |
| reverseService.enableReverseMode(fCommandControl.getContext(), true, |
| new ImmediateRequestMonitor(rm) { |
| @Override |
| protected void handleSuccess() { |
| reverseService.isReverseModeEnabled(fCommandControl.getContext(), rm); |
| } |
| }); |
| } |
| }; |
| |
| fSession.getExecutor().execute(query); |
| Boolean enabled = query.get(); |
| assertTrue("Reverse debugging should be enabled", enabled); |
| |
| // Wait for the event to avoid confusing it with the next one |
| IReverseModeChangedDMEvent event = waitForEvent(IReverseModeChangedDMEvent.class); |
| assertEquals(true, event.isReverseModeEnabled()); |
| fEventsReceived.clear(); |
| |
| queueConsoleCommand("record stop"); |
| |
| // Wait for the event |
| event = waitForEvent(IReverseModeChangedDMEvent.class); |
| assertEquals(false, event.isReverseModeEnabled()); |
| |
| // Check the service |
| query = new Query<Boolean>() { |
| @Override |
| protected void execute(final DataRequestMonitor<Boolean> rm) { |
| reverseService.isReverseModeEnabled(fCommandControl.getContext(), rm); |
| } |
| }; |
| fSession.getExecutor().execute(query); |
| enabled = query.get(); |
| assertTrue("Reverse debugging should not be enabled", !enabled); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////// |
| // End of tests |
| ////////////////////////////////////////////////////////////////////////////////////// |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(IDMEvent<? extends IDMContext> e) { |
| synchronized(this) { |
| fEventsReceived.add(e); |
| notifyAll(); |
| } |
| } |
| |
| private void queueConsoleCommand(String command) throws Throwable { |
| queueConsoleCommand(command, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); |
| } |
| |
| private void queueConsoleCommand(final String command, int timeout, TimeUnit unit) throws Throwable { |
| Query<MIInfo> query = new Query<MIInfo>() { |
| @Override |
| protected void execute(DataRequestMonitor<MIInfo> rm) { |
| fCommandControl.queueCommand( |
| fCommandControl.getCommandFactory().createMIInterpreterExecConsole( |
| fCommandControl.getContext(), |
| command), |
| rm); |
| } |
| }; |
| fSession.getExecutor().execute(query); |
| query.get(timeout, unit); |
| } |
| |
| private <V extends IDMEvent<? extends IDMContext>> V waitForEvent(Class<V> eventType) throws Exception { |
| return waitForEvent(eventType, DEFAULT_TIMEOUT); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <V extends IDMEvent<? extends IDMContext>> V waitForEvent(Class<V> eventType, int timeout) throws Exception { |
| IDMEvent<?> event = getEvent(eventType); |
| if (event == null) { |
| synchronized(this) { |
| try { |
| wait(timeout); |
| } |
| catch (InterruptedException ex) { |
| } |
| } |
| event = getEvent(eventType); |
| if (event == null) { |
| throw new Exception(String.format("Timed out waiting for '%s' to occur.", eventType.getName())); |
| } |
| } |
| return (V)event; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private synchronized <V extends IDMEvent<? extends IDMContext>> V getEvent(Class<V> eventType) { |
| for (IDMEvent<?> e : fEventsReceived) { |
| if (eventType.isAssignableFrom(e.getClass())) { |
| fEventsReceived.remove(e); |
| return (V)e; |
| } |
| } |
| return null; |
| } |
| |
| } |