blob: 79b81b66d753751cc06ae314351b24f268a6a46a [file] [log] [blame]
/*******************************************************************************
* 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;
}
}