From 0248aa4474165d5e39624971be5307212f9e5e2f Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 10 Jan 2013 14:34:14 -0500 Subject: [PATCH] Bug 397715 - [memory][expressions] Make use of GDB 7.6 new =memory-changed MI event Change-Id: I98c3cd273e185dfb1782527d7abd553351a163d9 Reviewed-on: https://git.eclipse.org/r/9662 Reviewed-by: Marc Khouzam IP-Clean: Marc Khouzam Tested-by: Marc Khouzam --- .../cdt/dsf/gdb/service/GDBMemory_7_6.java | 165 +++++++++ .../gdb/service/GdbDebugServicesFactory.java | 9 +- .../eclipse/cdt/dsf/mi/service/MIMemory.java | 49 ++- .../data/launch/src/ConsoleSyncTestApp.cc | 15 + .../GDBConsoleSynchronizingTest_7_6.java | 330 ++++++++++++++++++ .../dsf/gdb/tests/tests_7_6/Suite_7_6.java | 1 + .../gdb/tests/tests_7_6/Suite_Remote_7_6.java | 1 + 7 files changed, 551 insertions(+), 19 deletions(-) create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBMemory_7_6.java create mode 100644 dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/ConsoleSyncTestApp.cc create mode 100644 dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/GDBConsoleSynchronizingTest_7_6.java diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBMemory_7_6.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBMemory_7_6.java new file mode 100644 index 00000000000..3c4dd5475e2 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBMemory_7_6.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2013 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.dsf.gdb.service; + +import java.util.Hashtable; + +import org.eclipse.cdt.core.IAddress; +import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; +import org.eclipse.cdt.dsf.datamodel.DMContexts; +import org.eclipse.cdt.dsf.debug.service.IMemory; +import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; +import org.eclipse.cdt.dsf.debug.service.command.IEventListener; +import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; +import org.eclipse.cdt.dsf.mi.service.IMIProcesses; +import org.eclipse.cdt.dsf.mi.service.MIMemory; +import org.eclipse.cdt.dsf.mi.service.command.output.MIConst; +import org.eclipse.cdt.dsf.mi.service.command.output.MINotifyAsyncOutput; +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.mi.service.command.output.MIResult; +import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; +import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.cdt.utils.Addr64; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * Memory service that uses the enhancements from GDB 7.6: + * =memory-changed MI event + * + * @since 4.2 + */ +public class GDBMemory_7_6 extends GDBMemory_7_0 implements IEventListener { + + private ICommandControlService fConnection; + + public GDBMemory_7_6(DsfSession session) { + super(session); + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( + new ImmediateRequestMonitor(requestMonitor) { + @Override + public void handleSuccess() { + doInitialize(requestMonitor); + }}); + } + + private void doInitialize(final RequestMonitor requestMonitor) { + register(new String[] { MIMemory.class.getName(), + IMemory.class.getName(), + GDBMemory_7_0.class.getName(), + GDBMemory_7_6.class.getName()}, + new Hashtable()); + + fConnection = getServicesTracker().getService(ICommandControlService.class); + if (fConnection == null) { + requestMonitor.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "CommandControl Service is not available")); //$NON-NLS-1$ + return; + } + fConnection.addEventListener(this); + + requestMonitor.done(); + } + + @Override + public void shutdown(final RequestMonitor requestMonitor) { + fConnection.removeEventListener(this); + unregister(); + super.shutdown(requestMonitor); + } + + @Override + public void eventReceived(Object output) { + if (output instanceof MIOutput) { + MIOOBRecord[] records = ((MIOutput)output).getMIOOBRecords(); + for (MIOOBRecord r : records) { + if (r instanceof MINotifyAsyncOutput) { + MINotifyAsyncOutput notifyOutput = (MINotifyAsyncOutput)r; + String asyncClass = notifyOutput.getAsyncClass(); + // These events have been added with GDB 7.6 + if ("memory-changed".equals(asyncClass)) { //$NON-NLS-1$ + String groupId = null; + String addr = null; + int length = 0; + + MIResult[] results = notifyOutput.getMIResults(); + for (int i = 0; i < results.length; i++) { + String var = results[i].getVariable(); + MIValue val = results[i].getMIValue(); + if (var.equals("thread-group")) { //$NON-NLS-1$ + if (val instanceof MIConst) { + groupId = ((MIConst)val).getString(); + } + } else if (var.equals("addr")) { //$NON-NLS-1$ + if (val instanceof MIConst) { + addr = ((MIConst)val).getString(); + } + } else if (var.equals("len")) { //$NON-NLS-1$ + if (val instanceof MIConst) { + try { + String lenStr = ((MIConst)val).getString().trim(); + if (lenStr.startsWith("0x")) { //$NON-NLS-1$ + length = Integer.parseInt(lenStr.substring(2), 16); + } else { + length = Integer.parseInt(lenStr); + } + } catch (NumberFormatException e) { + assert false; + } + } + } else if (var.equals("type")) { //$NON-NLS-1$ + if (val instanceof MIConst) { + if ("code".equals(((MIConst)val).getString())) { //$NON-NLS-1$ + } + } + } + } + + IMIProcesses procService = getServicesTracker().getService(IMIProcesses.class); + if (procService != null && groupId != null && addr != null && length > 0) { + IContainerDMContext containerDmc = + procService.createContainerContextFromGroupId(fConnection.getContext(), groupId); + + // Now refresh our memory cache, it case it contained this address. Don't have + // it send the potential IMemoryChangedEvent as we will send it ourselves (see below). + final IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(containerDmc, IMemoryDMContext.class); + final IAddress address = new Addr64(addr); + // The length returned by GDB is in bytes, while the memory cache expects + // a count of number of addresses of 8 bytes. + int count = length/8 + 1; + getMemoryCache(memoryDMC).refreshMemory(memoryDMC, address, 0, 1, count, false, + new RequestMonitor(getExecutor(), null) { + @Override + protected void handleCompleted() { + // Only once the memory cache is updated, we send the IMemoryChangedEvent. If we were to do it + // earlier, the memory view may not show the updated value. + // + // We must always send this event when GDB reports a memory change because it can mean that + // an expression or register has changed, and therefore we must notify the different views + // and services of it. We cannot rely on this event to be sent by the memory cache after being + // refreshed, because if the memory cache does not contain this address, it will not send + // the event. + getSession().dispatchEvent(new MemoryChangedEvent(memoryDMC, new IAddress[] { address }), getProperties()); + } + }); + } + } + } + } + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java index c1a2647d4c0..0a62d53fb65 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2012 Ericsson and others. + * Copyright (c) 2008, 2013 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 @@ -12,6 +12,7 @@ * Marc Khouzam (Ericsson) - Support for GDB 7.4 (Bug 367788) * Marc Khouzam (Ericsson) - Include IGDBHardware service for the multicore visualizer (Bug 335027) * Vladimir Prus (Mentor Graphics) - Support for OS resources. + * Marc Khouzam (Ericsson) - Support for GDB 7.6 memory service *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.service; @@ -67,6 +68,8 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory { public static final String GDB_7_4_VERSION = "7.4"; //$NON-NLS-1$ /** @since 4.2*/ public static final String GDB_7_5_VERSION = "7.5"; //$NON-NLS-1$ + /** @since 4.2*/ + public static final String GDB_7_6_VERSION = "7.5.50"; //$NON-NLS-1$ private final String fVersion; @@ -172,6 +175,10 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory { @Override protected IMemory createMemoryService(DsfSession session) { + if (GDB_7_6_VERSION.compareTo(fVersion) <= 0) { + return new GDBMemory_7_6(session); + } + if (GDB_7_0_VERSION.compareTo(fVersion) <= 0) { return new GDBMemory_7_0(session); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java index f75beaa1628..67cc4f3da5f 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIMemory.java @@ -34,6 +34,7 @@ import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.ICachingService; import org.eclipse.cdt.dsf.debug.service.IExpressions; +import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionChangedDMEvent; 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.IMemory; @@ -92,7 +93,8 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer // Map of memory caches private Map fMemoryCaches; - private MIMemoryCache getMemoryCache(IMemoryDMContext memoryDMC) { + /** @since 4.2 */ + protected MIMemoryCache getMemoryCache(IMemoryDMContext memoryDMC) { MIMemoryCache cache = fMemoryCaches.get(memoryDMC); if (cache == null) { cache = new MIMemoryCache(); @@ -490,12 +492,19 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer } } - /** - * @nooverride This method is not intended to be re-implemented or extended by clients. - * @noreference This method is not intended to be referenced by clients. - */ - @DsfServiceEventHandler + /** + * @deprecated Replaced by the generic {@link #eventDispatched(IExpressionChangedDMEvent)} + */ + @Deprecated public void eventDispatched(ExpressionChangedEvent e) { + } + + /** + * @noreference This method is not intended to be referenced by clients. + * @since 4.2 + */ + @DsfServiceEventHandler + public void eventDispatched(IExpressionChangedDMEvent e) { // Get the context and expression service handle final IExpressionDMContext context = e.getDMContext(); @@ -518,7 +527,7 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer address = new Addr64(expAddress.getValue()); final IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(context, IMemoryDMContext.class); - getMemoryCache(memoryDMC).refreshMemory(memoryDMC, address, 0, 1, count, + getMemoryCache(memoryDMC).refreshMemory(memoryDMC, address, 0, 1, count, true, new RequestMonitor(getExecutor(), null)); } }); @@ -629,7 +638,8 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer // MIMemoryCache /////////////////////////////////////////////////////////////////////////// - private class MIMemoryCache { + /** @since 4.2 */ + protected class MIMemoryCache { // The memory cache data structure private SortedMemoryBlockList fMemoryBlockList; @@ -965,10 +975,12 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer * @param offset * @param word_size * @param count + * @param sendMemoryEvent Indicates if a IMemoryChangedEvent should be sent if the memory cache has changed. * @param rm */ public void refreshMemory(final IMemoryDMContext memoryDMC, final IAddress address, - final long offset, final int word_size, final int count, final RequestMonitor rm) + final long offset, final int word_size, final int count, final boolean sendMemoryEvent, + final RequestMonitor rm) { // Check if we already cache part of this memory area (which means it // is used by a memory service client that will have to be updated) @@ -983,16 +995,10 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer rm.done(); return; } - - // Prepare the data for the MemoryChangedEvent - final IAddress[] addresses = new IAddress[count]; - for (int i = 0; i < count; i++) { - addresses[i] = address.add(i); - } // Read the corresponding memory block fCommandCache.reset(); - readMemoryBlock(memoryDMC, address, 0, 1, count, + readMemoryBlock(memoryDMC, address, offset, word_size, count, new DataRequestMonitor(getExecutor(), rm) { @Override protected void handleSuccess() { @@ -1006,8 +1012,15 @@ public class MIMemory extends AbstractDsfService implements IMemory, ICachingSer } } if (blocksDiffer) { - updateMemoryCache(address, count, newBlock); - getSession().dispatchEvent(new MemoryChangedEvent(memoryDMC, addresses), getProperties()); + updateMemoryCache(address.add(offset), count, newBlock); + if (sendMemoryEvent) { + // Send the MemoryChangedEvent + final IAddress[] addresses = new IAddress[count]; + for (int i = 0; i < count; i++) { + addresses[i] = address.add(offset + i); + } + getSession().dispatchEvent(new MemoryChangedEvent(memoryDMC, addresses), getProperties()); + } } rm.done(); } diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/ConsoleSyncTestApp.cc b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/ConsoleSyncTestApp.cc new file mode 100644 index 00000000000..2e858e5dd12 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/ConsoleSyncTestApp.cc @@ -0,0 +1,15 @@ +#include + +int testMemoryChanges() { + int i = 8; + + return i; +} + +int main() { + printf("Running ConsoleSyncTestApp\n"); + + testMemoryChanges(); + + return 0; +} diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/GDBConsoleSynchronizingTest_7_6.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/GDBConsoleSynchronizingTest_7_6.java new file mode 100644 index 00000000000..c8299d57783 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/GDBConsoleSynchronizingTest_7_6.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * Copyright (c) 2013 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.tests_7_6; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.eclipse.cdt.core.IAddress; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +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.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.IStack.IFrameDMContext; +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.BackgroundRunner; +import org.eclipse.cdt.tests.dsf.gdb.framework.BaseTestCase; +import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil; +import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; +import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants; +import org.eclipse.debug.core.model.MemoryByte; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * This test case verifies that different commands issued from the + * GDB console cause proper updating within the CDT views. + */ +@RunWith(BackgroundRunner.class) +public class GDBConsoleSynchronizingTest_7_6 extends BaseTestCase { + + final static private int DEFAULT_TIMEOUT = 1000; + final static private TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS; + + private DsfSession fSession; + private DsfServicesTracker fServicesTracker; + private IGDBControl fCommandControl; + private IMemory fMemoryService; + private IExpressions fExprService; + + private List> fEventsReceived = new ArrayList>(); + + @Override + protected void setGdbVersion() { + setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_6); + } + + @Override + protected void setLaunchAttributes() { + super.setLaunchAttributes(); + + setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, "data/launch/bin/ConsoleSyncTestApp.exe"); + } + + @Override + public void doBeforeTest() throws Exception { + 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); + + // Register to breakpoint events + fSession.addServiceEventListener(GDBConsoleSynchronizingTest_7_6.this, null); + } + }; + fSession.getExecutor().submit(runnable).get(); + } + + @Override + public void doAfterTest() throws Exception { + Runnable runnable = new Runnable() { + @Override + public void run() { + fSession.removeServiceEventListener(GDBConsoleSynchronizingTest_7_6.this); + } + }; + fSession.getExecutor().submit(runnable).get(); + fEventsReceived.clear(); + 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 query = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + fExprService.getExpressionAddressData(exprDmc, rm); + } + }; + + fSession.getExecutor().execute(query); + IExpressionDMAddress data = query.get(); + + IMemoryDMContext memoryDmc = DMContexts.getAncestorOfType(frameDmc, IMemoryDMContext.class); + readMemory(memoryDmc, data.getAddress(), data.getSize()); + + fEventsReceived.clear(); + queueConsoleCommand("set variable i=100"); + + IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); + assertEquals(1, memoryEvent.getAddresses().length); + assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); + } + + /** + * 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 { + 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 query = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + fExprService.getExpressionAddressData(exprDmc, rm); + } + }; + + fSession.getExecutor().execute(query); + IExpressionDMAddress data = query.get(); + + fEventsReceived.clear(); + queueConsoleCommand("set variable i=100"); + + IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); + assertEquals(1, memoryEvent.getAddresses().length); + assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); + } + + /** + * 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 { + 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 query = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + fExprService.getExpressionAddressData(exprDmc, rm); + } + }; + + fSession.getExecutor().execute(query); + IExpressionDMAddress data = query.get(); + + fEventsReceived.clear(); + queueConsoleCommand("print i=100"); + + IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); + assertEquals(1, memoryEvent.getAddresses().length); + assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); + } + + /** + * 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 query = new Query() { + @Override + protected void execute(final DataRequestMonitor rm) { + fExprService.getExpressionAddressData(exprDmc, rm); + } + }; + + fSession.getExecutor().execute(query); + IExpressionDMAddress data = query.get(); + + fEventsReceived.clear(); + queueConsoleCommand("set {int}&i=100"); + + IMemoryChangedEvent memoryEvent = waitForEvent(IMemoryChangedEvent.class); + assertEquals(1, memoryEvent.getAddresses().length); + assertEquals(data.getAddress(), memoryEvent.getAddresses()[0]); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // End of tests + ////////////////////////////////////////////////////////////////////////////////////// + + @DsfServiceEventHandler + public void eventDispatched(IDMEvent e) { + synchronized(this) { + fEventsReceived.add(e); + notifyAll(); + } + } + + private MemoryByte[] readMemory(final IMemoryDMContext dmc, final IAddress address, final int count) + throws Throwable + { + Query query = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + fMemoryService.getMemory(dmc, address, 0, 1, count, rm); + } + }; + fSession.getExecutor().execute(query); + return query.get(DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); + } + + 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 query = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + fCommandControl.queueCommand( + fCommandControl.getCommandFactory().createMIInterpreterExecConsole( + fCommandControl.getContext(), + command), + rm); + } + }; + fSession.getExecutor().execute(query); + query.get(timeout, unit); + } + + private > V waitForEvent(Class eventType) throws Exception { + return waitForEvent(eventType, DEFAULT_TIMEOUT); + } + + @SuppressWarnings("unchecked") + private > V waitForEvent(Class 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 getEvent(Class eventType) { + for (IDMEvent e : fEventsReceived) { + if (eventType.isAssignableFrom(e.getClass())) { + fEventsReceived.remove(e); + return (V)e; + } + } + return null; + } + +} diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_7_6.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_7_6.java index 983dc915d30..0e364d79885 100644 --- a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_7_6.java +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_7_6.java @@ -48,6 +48,7 @@ import org.junit.runners.Suite; GDBMultiNonStopRunControlTest_7_6.class, Suite_Sessionless_Tests.class, GDBConsoleBreakpointsTest_7_6.class, + GDBConsoleSynchronizingTest_7_6.class, /* Add your test class here */ }) diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_Remote_7_6.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_Remote_7_6.java index aa3edd8421e..fc54dbfa84f 100644 --- a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_Remote_7_6.java +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/tests_7_6/Suite_Remote_7_6.java @@ -49,6 +49,7 @@ import org.junit.runners.Suite; Suite_Sessionless_Tests.class, GDBConsoleBreakpointsTest_7_6.class, TraceFileTest_7_6.class, + GDBConsoleSynchronizingTest_7_6.class, /* Add your test class here */ })