From cba02974993d9c14f544662c53db95c507ff60ee Mon Sep 17 00:00:00 2001 From: Pawel Piech Date: Wed, 25 Jan 2012 21:21:48 -0800 Subject: Bug 349998 - [test] JUnit tests for debug UI (Added initial breakpoints tests) --- .../.settings/org.eclipse.jdt.core.prefs | 87 ++- .../META-INF/MANIFEST.MF | 5 +- .../eclipse/tcf/debug/test/AbstractTcfUITest.java | 223 +++++- .../tcf/debug/test/BreakpointsListener.java | 111 +++ .../eclipse/tcf/debug/test/BreakpointsTest.java | 166 ++-- .../src/org/eclipse/tcf/debug/test/CacheTests.java | 832 +++++++++++++++++++++ .../src/org/eclipse/tcf/debug/test/QueryTests.java | 255 +++++++ .../src/org/eclipse/tcf/debug/test/SampleTest.java | 292 ++------ .../eclipse/tcf/debug/test/TransactionTests.java | 167 +++++ .../tcf/debug/test/ViewerUpdatesListener.java | 2 +- .../tcf/debug/test/services/BreakpointsCM.java | 18 +- .../tcf/debug/test/services/LineNumbersCM.java | 232 ++++++ .../eclipse/tcf/debug/test/util/AbstractCache.java | 70 +- .../org/eclipse/tcf/debug/test/util/ICache.java | 18 +- .../eclipse/tcf/debug/test/util/Transaction.java | 78 +- .../tcf/debug/test/util/TransactionCache.java | 111 +++ 16 files changed, 2287 insertions(+), 380 deletions(-) create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsListener.java create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/CacheTests.java create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/QueryTests.java create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TransactionTests.java create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/LineNumbersCM.java create mode 100644 tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/TransactionCache.java (limited to 'tests') diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs b/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs index 9d7f7d27a..3b0982c01 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs +++ b/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,93 @@ -#Thu May 26 13:10:48 PDT 2011 eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=ignore +org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.source=1.5 diff --git a/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF b/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF index bbb668758..8b7ff52b9 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF +++ b/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF @@ -10,8 +10,9 @@ Require-Bundle: org.eclipse.ui, org.eclipse.tcf.debug.ui, org.eclipse.tcf.core, org.junit;bundle-version="3.8.2", - org.eclipse.debug.ui;bundle-version="3.7.0", - org.eclipse.cdt.debug.core;bundle-version="7.2.0" + org.eclipse.debug.ui, + org.eclipse.cdt.debug.core;bundle-version="7.2.0", + org.eclipse.tcf.cdt.ui;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: org.eclipse.debug.internal.ui.viewers.model diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java index 587682467..46405ec44 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java @@ -1,11 +1,14 @@ package org.eclipse.tcf.debug.test; import java.io.IOException; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; @@ -13,24 +16,32 @@ import java.util.regex.Pattern; import junit.framework.TestCase; +import org.eclipse.core.resources.IWorkspaceRunnable; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IDisconnect; import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext; import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem; import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer; import org.eclipse.debug.ui.IDebugUIConstants; +import org.eclipse.jface.viewers.TreePath; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.tcf.core.TransientPeer; import org.eclipse.tcf.debug.test.services.BreakpointsCM; import org.eclipse.tcf.debug.test.services.DiagnosticsCM; +import org.eclipse.tcf.debug.test.services.LineNumbersCM; import org.eclipse.tcf.debug.test.services.RunControlCM; import org.eclipse.tcf.debug.test.services.RunControlCM.ContextState; import org.eclipse.tcf.debug.test.services.StackTraceCM; @@ -43,12 +54,15 @@ import org.eclipse.tcf.debug.test.util.ICache; import org.eclipse.tcf.debug.test.util.Query; import org.eclipse.tcf.debug.test.util.Task; import org.eclipse.tcf.debug.test.util.Transaction; +import org.eclipse.tcf.debug.ui.ITCFObject; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.services.IBreakpoints; import org.eclipse.tcf.services.IDiagnostics; +import org.eclipse.tcf.services.IDiagnostics.ISymbol; import org.eclipse.tcf.services.IExpressions; +import org.eclipse.tcf.services.ILineNumbers; import org.eclipse.tcf.services.IMemoryMap; import org.eclipse.tcf.services.IRunControl; import org.eclipse.tcf.services.IRunControl.RunControlContext; @@ -89,13 +103,21 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat protected IRunControl rc; protected IBreakpoints bp; protected IMemoryMap fMemoryMap; + protected ILineNumbers fLineNumbers; protected RunControlCM fRunControlCM; protected DiagnosticsCM fDiagnosticsCM; protected BreakpointsCM fBreakpointsCM; protected StackTraceCM fStackTraceCM; protected SymbolsCM fSymbolsCM; + protected LineNumbersCM fLineNumbersCM; + protected String fTestId; + protected RunControlContext fTestCtx; + protected String fProcessId = ""; + protected String fThreadId = ""; + protected RunControlContext fThreadCtx; + private static class RemotePeer extends TransientPeer { private final ArrayList> attrs; @@ -137,7 +159,6 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat } protected void setUp() throws Exception { - fTestRunKey = new Object(); createDebugViewViewer(); @@ -194,6 +215,7 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat }.get(); validateTestAvailable(); + clearBreakpoints(); } @Override @@ -227,6 +249,7 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat fBreakpointsCM = new BreakpointsCM(bp); fStackTraceCM = new StackTraceCM(stk, rc); fSymbolsCM = new SymbolsCM(syms, fRunControlCM, fMemoryMap); + fLineNumbersCM = new LineNumbersCM(fLineNumbers, fMemoryMap, fRunControlCM); } protected void tearDownServiceListeners() throws Exception{ @@ -235,6 +258,7 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat fStackTraceCM.dispose(); fRunControlCM.dispose(); fDiagnosticsCM.dispose(); + fLineNumbersCM.dispose(); } private void createDebugViewViewer() { @@ -278,11 +302,23 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat fDebugViewListener.reset(); fDebugViewListener.setDelayContentUntilProxyInstall(true); fLaunch = lcWc.launch("debug", new NullProgressMonitor()); - fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | MODEL_PROXIES_INSTALLED | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); Assert.assertTrue( fLaunch instanceof IDisconnect ); - VirtualItem launchItem = fDebugViewListener.findElement(new Pattern[] { Pattern.compile(".*" + fLaunch.getLaunchConfiguration().getName() + ".*") } ); - Assert.assertTrue( launchItem != null && fLaunch.equals(launchItem.getData()) ); + // The launch element may or may not have been populated into the viewer. It's label + // also may or may not have been updated. Check viewer item for launch, then wait if needed. + TreePath launchPath = new TreePath(new Object[] { fLaunch }); + fDebugViewListener.addLabelUpdate(launchPath); + VirtualItem launchItem = fDebugViewViewer.findItem(launchPath); + fDebugViewListener.findElement(new Pattern[] { Pattern.compile(".*" + fLaunch.getLaunchConfiguration().getName() + ".*") } ); + if (launchItem == null || launchItem.getData() == null || launchItem.getData(VirtualItem.LABEL_KEY) == null) { + fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | MODEL_PROXIES_INSTALLED | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES); + launchItem = fDebugViewViewer.findItem(launchPath); + } + + Assert.assertTrue( launchItem != null ); + String[] launchItemLabel = (String[])launchItem.getData(VirtualItem.LABEL_KEY); + Assert.assertTrue( launchItemLabel[0].contains(fLaunch.getLaunchConfiguration().getName()) ); + Assert.assertEquals(fLaunch, launchItem.getData()); } private void terminateLaunch() throws DebugException, InterruptedException, ExecutionException { @@ -328,6 +364,7 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat rc = channels[0].getRemoteService(IRunControl.class); bp = channels[0].getRemoteService(IBreakpoints.class); fMemoryMap = channels[0].getRemoteService(IMemoryMap.class); + fLineNumbers = channels[0].getRemoteService(ILineNumbers.class); }; }); } @@ -473,6 +510,120 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat Assert.assertTrue("Required test not supported", i != testList.length); } + protected void clearBreakpoints() throws InterruptedException, ExecutionException, CoreException { + + // Delete eclipse breakpoints. + IWorkspaceRunnable wr = new IWorkspaceRunnable() { + public void run(IProgressMonitor monitor) throws CoreException { + IBreakpointManager mgr = DebugPlugin.getDefault().getBreakpointManager(); + IBreakpoint[] bps = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(); + mgr.removeBreakpoints(bps, true); + } + }; + ResourcesPlugin.getWorkspace().run(wr, null, 0, null); + + // Delete TCF breakpoints + new Transaction() { + @Override + protected Object process() throws InvalidCacheException, ExecutionException { + // Initialize the event cache for breakpoint status + @SuppressWarnings("unchecked") + Map[] bps = (Map[])new Map[] { }; + validate( fBreakpointsCM.set(bps, this) ); + return null; + } + }.get(); + } + + private void startProcess() throws InterruptedException, ExecutionException { + new Transaction() { + protected Object process() throws Transaction.InvalidCacheException ,ExecutionException { + fTestId = validate( fDiagnosticsCM.runTest(getDiagnosticsTestName(), this) ); + fTestCtx = validate( fRunControlCM.getContext(fTestId) ); + fProcessId = fTestCtx.getProcessID(); + // Create the cache to listen for exceptions. + fRunControlCM.waitForContextException(fTestId, fTestRunKey); + + if (!fProcessId.equals(fTestId)) { + fThreadId = fTestId; + } else { + String[] threads = validate( fRunControlCM.getChildren(fProcessId) ); + fThreadId = threads[0]; + } + fThreadCtx = validate( fRunControlCM.getContext(fThreadId) ); + + Assert.assertTrue("Invalid thread context", fThreadCtx.hasState()); + return new Object(); + }; + }.get(); + } + + private boolean runToTestEntry(final String testFunc) throws InterruptedException, ExecutionException { + return new Transaction() { + Object fWaitForSuspendKey = new Object(); + boolean fSuspendEventReceived = false; + protected Boolean process() throws Transaction.InvalidCacheException ,ExecutionException { + ISymbol sym_func0 = validate( fDiagnosticsCM.getSymbol(fProcessId, testFunc) ); + String sym_func0_value = sym_func0.getValue().toString(); + ContextState state = validate (fRunControlCM.getState(fThreadId)); + if (state.suspended) { + if ( !new BigInteger(state.pc).equals(new BigInteger(sym_func0_value)) ) { + fSuspendEventReceived = true; + // We are not at test entry. Create a new suspend wait cache. + fWaitForSuspendKey = new Object(); + fRunControlCM.waitForContextSuspended(fThreadId, fWaitForSuspendKey); + // Run to entry point. + validate( fRunControlCM.resume(fThreadCtx, fWaitForSuspendKey, IRunControl.RM_RESUME, 1) ); + } + } else { + // Wait until we suspend. + validate( fRunControlCM.waitForContextSuspended(fThreadId, fWaitForSuspendKey) ); + } + + return fSuspendEventReceived; + } + }.get(); + } + + protected void initProcessModel(String testFunc) throws Exception { + String bpId = "entryPointBreakpoint"; + createBreakpoint(bpId, testFunc); + fDebugViewListener.reset(); + + ITCFObject processTCFContext = new ITCFObject() { + public String getID() { return fProcessId; } + public IChannel getChannel() { return channels[0]; } + }; + ITCFObject threadTCFContext = new ITCFObject() { + public String getID() { return fThreadId; } + public IChannel getChannel() { return channels[0]; } + }; + + fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext })); + fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext, threadTCFContext })); + + startProcess(); + runToTestEntry(testFunc); + removeBreakpoint(bpId); + + final String topFrameId = new Transaction() { + @Override + protected String process() throws InvalidCacheException, ExecutionException { + String[] frameIds = validate( fStackTraceCM.getChildren(fThreadId) ); + Assert.assertTrue("No stack frames" , frameIds.length != 0); + return frameIds[frameIds.length - 1]; + } + }.get(); + + ITCFObject frameTCFContext = new ITCFObject() { + public String getID() { return topFrameId; } + public IChannel getChannel() { return channels[0]; } + }; + fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext, threadTCFContext, frameTCFContext })); + + fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES); + } + protected ContextState resumeAndWaitForSuspend(final RunControlContext context, final int mode) throws InterruptedException, ExecutionException { return new Transaction() { @Override @@ -484,5 +635,67 @@ public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdat } }.get(); } - + + protected void createBreakpoint(final String bpId, final String testFunc) throws InterruptedException, ExecutionException { + new Transaction() { + private Map fBp; + { + fBp = new TreeMap(); + fBp.put(IBreakpoints.PROP_ID, bpId); + fBp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + fBp.put(IBreakpoints.PROP_LOCATION, testFunc); + } + + @Override + protected Object process() throws InvalidCacheException, ExecutionException { + // Prime event wait caches + fBreakpointsCM.waitContextAdded(this); + + validate( fBreakpointsCM.add(fBp, this) ); + validate( fBreakpointsCM.waitContextAdded(this)); + + // Wait for breakpoint status event and validate it. + Map status = validate(fBreakpointsCM.getStatus(bpId)); + String s = (String)status.get(IBreakpoints.STATUS_ERROR); + if (s != null) { + Assert.fail("Invalid BP status: " + s); + } + @SuppressWarnings("unchecked") + Collection> list = (Collection>)status.get(IBreakpoints.STATUS_INSTANCES); + if (list != null) { + String err = null; + for (Map map : list) { + String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT); + if (fProcessId.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) + err = (String)map.get(IBreakpoints.INSTANCE_ERROR); + } + if (err != null) { + Assert.fail("Invalid BP status: " + s); + } + } + return null; + } + }.get(); + } + + protected void removeBreakpoint(final String bpId) throws InterruptedException, ExecutionException { + new Transaction() { + + @Override + protected Object process() throws InvalidCacheException, ExecutionException { + + // Prime event wait caches + fBreakpointsCM.waitContextRemoved(this); + + // Remove + validate( fBreakpointsCM.remove(new String[] { bpId }, this) ); + + // Verify removed event + String[] removedIds = validate( fBreakpointsCM.waitContextRemoved(this)); + Assert.assertTrue(Arrays.asList(removedIds).contains(bpId)); + return null; + } + }.get(); + } + } diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsListener.java new file mode 100644 index 000000000..3d432f26b --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsListener.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2012 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.IBreakpointsListener; +import org.eclipse.debug.core.model.IBreakpoint; + +/** + * + */ +public class BreakpointsListener implements IBreakpointsListener { + + public enum EventType { ADDED, REMOVED, CHANGED }; + + public interface EventTester { + boolean checkEvent(EventType type, IBreakpoint bp, Map deltaAttributes); + } + + private static final EventTester ANY_EVENT_TESTER = new EventTester() { + public boolean checkEvent(EventType type, IBreakpoint bp, Map deltaAttributes) { + return true; + } + }; + + private EventTester fTester = ANY_EVENT_TESTER; + private boolean fWaiting = false; + + + public BreakpointsListener() { + DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this); + } + + public void dispose() { + DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this); + } + + public void startWaiting() { + fWaiting = true; + } + public void setTester(EventTester tester) { + fTester = tester; + startWaiting(); + } + + public void breakpointsAdded(IBreakpoint[] bps) { + Map emptyAttrs = Collections.emptyMap(); + for (IBreakpoint bp : bps) { + if (fTester.checkEvent(EventType.ADDED, bp, emptyAttrs)) { + synchronized(this) { + fWaiting = false; + notifyAll(); + } + return; + } + } + } + + public void breakpointsRemoved(IBreakpoint[] bps, IMarkerDelta[] deltas) { + for (int i =0; i < bps.length; i++) { + Map attributes = Collections.emptyMap(); + if (deltas[i] != null) { + attributes = deltas[i].getAttributes(); + } + if (fTester.checkEvent(EventType.REMOVED, bps[i], attributes)) { + synchronized(this) { + fWaiting = false; + notifyAll(); + } + return; + } + } + } + + public void breakpointsChanged(IBreakpoint[] bps, IMarkerDelta[] deltas) { + for (int i =0; i < bps.length; i++) { + Map attributes = Collections.emptyMap(); + if (deltas[i] != null) { + attributes = deltas[i].getAttributes(); + } + if (fTester.checkEvent(EventType.CHANGED, bps[i], attributes)) { + synchronized(this) { + fWaiting = false; + notifyAll(); + } + return; + } + } + } + + + public void waitForEvent() throws InterruptedException { + synchronized(this) { + while(fWaiting) { + wait(); + } + } + } +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsTest.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsTest.java index 9594baad5..58818af0e 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsTest.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/BreakpointsTest.java @@ -1,115 +1,101 @@ package org.eclipse.tcf.debug.test; -import java.util.Collection; +import java.math.BigInteger; import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.ExecutionException; import org.eclipse.cdt.debug.core.CDIDebugModel; +import org.eclipse.cdt.debug.core.model.ICBreakpoint; +import org.eclipse.cdt.debug.core.model.ICBreakpointType; +import org.eclipse.cdt.debug.core.model.ICLineBreakpoint; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.model.IBreakpoint; +import org.eclipse.tcf.debug.test.BreakpointsListener.EventTester; +import org.eclipse.tcf.debug.test.BreakpointsListener.EventType; +import org.eclipse.tcf.debug.test.services.RunControlCM.ContextState; import org.eclipse.tcf.debug.test.util.Transaction; -import org.eclipse.tcf.services.IBreakpoints; -import org.eclipse.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tcf.internal.debug.ui.launch.TCFLaunchContext; +import org.eclipse.tcf.protocol.Protocol; +import org.eclipse.tcf.services.ILineNumbers.CodeArea; +import org.eclipse.tcf.services.ISymbols.Symbol; import org.junit.Assert; -@SuppressWarnings("restriction") public class BreakpointsTest extends AbstractTcfUITest { - private String fTestId; - private RunControlContext fTestCtx; - private String fProcessId = ""; - private String fThreadId = ""; - private RunControlContext fThreadCtx; + private BreakpointsListener fBpListener; + + @Override + protected void setUp() throws Exception { + super.setUp(); + fBpListener = new BreakpointsListener(); + + // CDT Breakpoint integration depends on the TCF-CDT breakpoint + // integration to be active. This is normally triggered by selecting + // a stack frame in the UI. Here force activation of the plugin + // artificially. None of the cdt integration packages are exported, so + // use the TCF Launch Context extension point indirectly to force the + // plugin to load. + TCFLaunchContext.getLaunchContext(null); + } - private void createBreakpoint(final String bpId, final String testFunc) throws InterruptedException, ExecutionException { - new Transaction() { - private Map fBp; - - { - fBp = new TreeMap(); - fBp.put(IBreakpoints.PROP_ID, bpId); - fBp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); - fBp.put(IBreakpoints.PROP_LOCATION, testFunc); - } - - @Override - protected Object process() throws InvalidCacheException, ExecutionException { - @SuppressWarnings("unchecked") - Map[] bps = (Map[])new Map[] { fBp }; - validate( fBreakpointsCM.set(bps, this) ); - - return null; - } - }.get(); + @Override + protected void tearDown() throws Exception { + fBpListener.dispose(); + super.tearDown(); } - - private void checkBreakpointForErrors(final String bpId, final String processId) throws InterruptedException, ExecutionException { - new Transaction() { + + private CodeArea getFunctionCodeArea(String functionName) throws Exception { + return new Transaction() { @Override - protected Object process() throws InvalidCacheException, ExecutionException { - // Wait for breakpoint status event and validate it. - Map status = validate( fBreakpointsCM.getStatus(bpId) ); - String s = (String)status.get(IBreakpoints.STATUS_ERROR); - if (s != null) { - Assert.fail("Invalid BP status: " + s); - } - @SuppressWarnings("unchecked") - Collection> list = (Collection>)status.get(IBreakpoints.STATUS_INSTANCES); - if (list != null) { - String err = null; - for (Map map : list) { - String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT); - if (processId.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) - err = (String)map.get(IBreakpoints.INSTANCE_ERROR); - } - if (err != null) { - Assert.fail("Invalid BP status: " + s); - } - } - return null; + protected CodeArea process() throws InvalidCacheException, ExecutionException { + ContextState state = validate ( fRunControlCM.getState(fThreadId) ); + String symId = validate ( fSymbolsCM.find(fProcessId, new BigInteger(state.pc), "tcf_test_func0") ); + Symbol sym = validate ( fSymbolsCM.getContext(symId) ); + CodeArea[] area = validate ( fLineNumbersCM.mapToSource( + fProcessId, + sym.getAddress(), + new BigInteger(sym.getAddress().toString()).add(BigInteger.valueOf(1))) ); + return area[0]; } - }.get(); - } - - private void startProcess() throws InterruptedException, ExecutionException { - new Transaction() { - protected Object process() throws Transaction.InvalidCacheException ,ExecutionException { - fTestId = validate( fDiagnosticsCM.runTest(getDiagnosticsTestName(), this) ); - fTestCtx = validate( fRunControlCM.getContext(fTestId) ); - fProcessId = fTestCtx.getProcessID(); - // Create the cache to listen for exceptions. - fRunControlCM.waitForContextException(fTestId, fTestRunKey); - - if (!fProcessId.equals(fTestId)) { - fThreadId = fTestId; - } else { - String[] threads = validate( fRunControlCM.getChildren(fProcessId) ); - fThreadId = threads[0]; - } - fThreadCtx = validate( fRunControlCM.getContext(fThreadId) ); - - Assert.assertTrue("Invalid thread context", fThreadCtx.hasState()); - return new Object(); - }; }.get(); } - - private void initProcessModel(String bpId, String testFunc) throws Exception { - createBreakpoint(bpId, testFunc); - fDebugViewListener.reset(); + private ICLineBreakpoint createLineBreakpoint(String file, int line) throws CoreException, ExecutionException, InterruptedException { + // Initiate wait for the context changed event. + final Object contextChangedWaitKey = new Object(); + Protocol.invokeAndWait(new Runnable() { public void run() { + fBreakpointsCM.waitContextAdded(contextChangedWaitKey); + }}); - startProcess(); - fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES); - } - - public void testCreateBreakpoint() throws Exception { - String bpId = "TestStepBP"; - initProcessModel(bpId, "tcf_test_func0"); + final ICLineBreakpoint bp = CDIDebugModel.createLineBreakpoint(file, ResourcesPlugin.getWorkspace().getRoot(), ICBreakpointType.REGULAR, line, true, 0, "", true); + + Map[] addedBps = new Transaction[]>() { + protected Map[] process() throws InvalidCacheException ,ExecutionException { + return validate(fBreakpointsCM.waitContextAdded(contextChangedWaitKey)); + } + + }.get(); + + fBpListener.setTester(new EventTester() { + public boolean checkEvent(EventType type, IBreakpoint testBp, Map deltaAttributes) { + return (type == EventType.CHANGED && bp == testBp); + } + }); + fBpListener.waitForEvent(); + Assert.assertEquals(1, addedBps.length); + Assert.assertEquals(1, bp.getMarker().getAttribute(ICBreakpoint.INSTALL_COUNT, -1)); - //CDIDebugModel.createFunctionBreakpoint(); + return bp; + } + + public void testContextAddedOnLineBrakpointCreate() throws Exception { + initProcessModel("tcf_test_func0"); - checkBreakpointForErrors(bpId, fProcessId); + CodeArea bpCodeArea = getFunctionCodeArea("tcf_test_func0"); + ICLineBreakpoint bp = createLineBreakpoint(bpCodeArea.file, bpCodeArea.start_line); } + } diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/CacheTests.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/CacheTests.java new file mode 100644 index 000000000..5021e4983 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/CacheTests.java @@ -0,0 +1,832 @@ +/***********************************************s******************************** + * Copyright (c) 2006 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.eclipse.tcf.debug.test.util.Callback; +import org.eclipse.tcf.debug.test.util.CallbackCache; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.Query; +import org.eclipse.tcf.protocol.Protocol; + +/** + * Tests that exercise the DataCache object. + */ +public class CacheTests extends TestCase { + + TestCache fTestCache; + DataCallback fRetrieveRm; + + class TestCache extends CallbackCache { + + @Override + protected void retrieve(DataCallback rm) { + synchronized(CacheTests.this) { + fRetrieveRm = rm; + CacheTests.this.notifyAll(); + } + } + + @Override + protected void handleCompleted(Integer data, Throwable error, boolean canceled) { + // TODO Auto-generated method stub + super.handleCompleted(data, error, canceled); + } + + } + + class TestQuery extends Query { + @Override + protected void execute(final DataCallback rm) { + fTestCache.update(new DataCallback(rm) { + @Override + protected void handleSuccess() { + rm.setData(fTestCache.getData()); + rm.done(); + } + }); + } + } + + /** + * There's no rule on how quickly the cache has to start data retrieval + * after it has been requested. It could do it immediately, or it could + * wait a dispatch cycle, etc.. + */ + private void waitForRetrieveRm() { + synchronized(this) { + while (fRetrieveRm == null) { + try { + wait(); + } catch (InterruptedException e) { + return; + } + } + } + } + + public void setUp() throws ExecutionException, InterruptedException { + fTestCache = new TestCache(); + } + + public void tearDown() throws ExecutionException, InterruptedException { + fRetrieveRm = null; + fTestCache = null; + } + + private void assertCacheValidWithData(Object data) { + Assert.assertTrue(fTestCache.isValid()); + Assert.assertEquals(data, fTestCache.getData()); + Assert.assertNull(fTestCache.getError()); + } + + private void assertCacheResetWithoutData() { + Assert.assertFalse(fTestCache.isValid()); + try { + fTestCache.getData(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + try { + fTestCache.getError(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + } + + private void assertCacheValidWithoutData() { + Assert.assertTrue(fTestCache.isValid()); + Assert.assertEquals(null, fTestCache.getData()); + Assert.assertNotNull(fTestCache.getError()); + Assert.assertEquals(fTestCache.getError(), ERROR_TARGET_RUNNING); + } + + private void assertCacheWaiting() { + Assert.assertFalse(fTestCache.isValid()); + try { + fTestCache.getData(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + try { + fTestCache.getError(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + Assert.assertFalse(fRetrieveRm.isCanceled()); + } + + private void assertCacheInvalidAndWithCanceledRM() { + Assert.assertFalse(fTestCache.isValid()); + try { + fTestCache.getData(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + try { + fTestCache.getError(); + Assert.fail("Expected an IllegalStateException"); + } catch (IllegalStateException e) {} + Assert.assertTrue(fRetrieveRm.isCanceled()); + } + + public void testGet() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + + // Check initial state + Assert.assertFalse(fTestCache.isValid()); + + q.invoke(); + + // Wait until the cache requests the data. + waitForRetrieveRm(); + + // Check state while waiting for data + Assert.assertFalse(fTestCache.isValid()); + + // Complete the cache's retrieve data request. + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + + // Check that the data is available in the cache immediately + // (in the same dispatch cycle). + Assert.assertEquals(1, (int)fTestCache.getData()); + Assert.assertTrue(fTestCache.isValid()); + } + }); + + Assert.assertEquals(1, (int)q.get()); + + // Re-check final state + assertCacheValidWithData(1); + } + + public void testGetWithCompletionDelay() throws InterruptedException, ExecutionException { + // Check initial state + Assert.assertFalse(fTestCache.isValid()); + + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Check state while waiting for data + Assert.assertFalse(fTestCache.isValid()); + + // Set the data to the callback + Protocol.invokeLater( + 100, + new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + + } + }); + + Assert.assertEquals(1, (int)q.get()); + + // Check final state + assertCacheValidWithData(1); + } + + public void testGetWithTwoClients() throws InterruptedException, ExecutionException { + // Check initial state + Assert.assertFalse(fTestCache.isValid()); + + // Request data from cache + Query q1 = new TestQuery(); + q1.invoke(); + + // Request data from cache again + Query q2 = new TestQuery(); + q2.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Check state while waiting for data + Assert.assertFalse(fTestCache.isValid()); + + // Set the data to the callback + Protocol.invokeLater(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + + } + }); + + Assert.assertEquals(1, (int)q1.get()); + Assert.assertEquals(1, (int)q2.get()); + + // Check final state + assertCacheValidWithData(1); + } + + public void testGetWithManyClients() throws InterruptedException, ExecutionException { + // Check initial state + Assert.assertFalse(fTestCache.isValid()); + + // Request data from cache + List> qList = new ArrayList>(); + for (int i = 0; i < 10; i++) { + Query q = new TestQuery(); + q.invoke(); + qList.add(q); + } + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Check state while waiting for data + Assert.assertFalse(fTestCache.isValid()); + + // Set the data to the callback + Protocol.invokeLater(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + + } + }); + + for (Query q : qList) { + Assert.assertEquals(1, (int)q.get()); + } + + // Check final state + assertCacheValidWithData(1); + } + + private static final Exception ERROR_TARGET_RUNNING = new Exception("Target is running"); + + // DISABLE TESTS + // + // We say a cache is "disabled" when its most recent attempt to update from + // the source failed. Also, a cache may make itself disabled as a reaction + // to a state change notification from its source (e.g., the target + // resumed). In either case, the cache is in the valid state but it has no + // data and the status reflects an error. Keep in mind that the 'valid' + // state is not a reflection of the quality of the data, but merely whether + // the cache object's representation of the data is stale or + // not. A transaction that uses a "disabled" cache object will simply fail; + // it will not ask the cache to update its data from the source. Only a + // change in the source's state would cause the cache to put itself back in + // the invalid state, thus opening the door to another update. + + /** + * Test behavior when a cache object is asked to update itself after it has + * become "disabled". Since a "disabled" cache is in the valid state, a + * request for it to update from the source should be ignored. However, the + * client callback is not completed until next state change in cache. + */ + public void testDisableBeforeRequest() throws InterruptedException, ExecutionException { + // Disable the cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.set(null, ERROR_TARGET_RUNNING, true); + } + }); + + assertCacheValidWithoutData(); + + // Try to request data from cache + Query q = new TestQuery(); + q.invoke(); + + Thread.sleep(100); + + // Retrieval should never have been made. + Assert.assertEquals(null, fRetrieveRm); + + // Disable the cache. This should trigger the qery to complete. + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.set(null, new Throwable("Cache invalid"), false); + } + }); + + // The cache has no data so the query should have failed + try { + q.get(); + Assert.fail("expected an exeption"); + } catch (ExecutionException e) { + // expected the exception + } + } + + /** + * Test behavior when a cache object goes into the "disabled" state while an + * update request is ongoing. The subsequent completion of the request should + * have no effect on the cache + */ + public void testDisableWhilePending() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Disable the cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.set(null, ERROR_TARGET_RUNNING, true); + } + }); + + assertCacheValidWithoutData(); + + // Complete the retrieve RM. Note that the disabling of the cache above + // disassociates it from its retrieval RM. Thus regardless of how that + // request completes, it does not affect the cache. + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Validate that cache is still disabled without data. + assertCacheValidWithoutData(); + } + + /** + * Test behavior when a cache object goes into the "disabled" state while + * it's in the valid state. The cache remains in the valid state but it + * loses its data and obtains an error status. + */ + public void testDisableWhileValid() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Complete the request + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + Assert.assertEquals(Integer.valueOf(1), q.get()); + + // Disable the cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.set(null, ERROR_TARGET_RUNNING, true); + } + }); + + // Check final state + assertCacheValidWithoutData(); + } + + public void testSetWithValue() throws InterruptedException, ExecutionException { + // Disable the cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.set(2, null, true); + } + }); + + // Validate that cache is disabled without data. + assertCacheValidWithData(2); + } + + + public void testCancelWhilePending() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel the client request + q.cancel(true); + try { + q.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + + assertCacheInvalidAndWithCanceledRM(); + + // Simulate the retrieval completing successfully despite the cancel + // request. Perhaps the retrieval logic isn't checking the RM status. + // Even if it is checking, it may have gotten passed its last checkpoint + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Validate that cache didn't accept the result after its RM was canceled + assertCacheInvalidAndWithCanceledRM(); + } + + public void testCancelWhilePending2() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel the client request + q.cancel(true); + try { + q.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + + assertCacheInvalidAndWithCanceledRM(); + + // Simulate retrieval logic that is regularly checking the RM's cancel + // status and has discovered that the request has been canceled. It + // technically does not need to explicitly set a cancel status object in + // the RM, thanks to RequestMonitor.getStatus() automatically returning + // Status.CANCEL_STATUS when its in the cancel state. So here we + // simulate the retrieval logic just aborting its operations and + // completing the RM. Note that it hasn't provided the data to the + // cache. + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.done(); + } + }); + + assertCacheInvalidAndWithCanceledRM(); + } + + public void testCancelWhilePending3() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel the client request + q.cancel(true); + try { + q.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + + assertCacheInvalidAndWithCanceledRM(); + + // Simulate retrieval logic that is regularly checking the RM's cancel + // status and has discovered that the request has been canceled. It + // aborts its processing, sets STATUS.CANCEL_STATUS in the RM and + // completes it. + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setError(new CancellationException()); + fRetrieveRm.done(); + } + }); + + // Validate that cache didn't accept the result after its RM was canceled + assertCacheInvalidAndWithCanceledRM(); + } + + public void testCancelWhilePendingWithoutClientNotification() throws InterruptedException, ExecutionException { + final boolean canceledCalled[] = new boolean[] { false }; + + fTestCache = new TestCache() { + protected synchronized void canceled() { + canceledCalled[0] = true; + }; + }; + + // Request data from cache + Query q = new Query() { + @Override + protected void execute(final DataCallback rm) { + + fTestCache.update(new Callback(rm) { + @Override + public synchronized void addCancelListener(ICanceledListener listener) { + // Do not add the cancel listener so that the cancel request is not + // propagated to the cache. + } + + @Override + protected void handleSuccess() { + rm.setData(fTestCache.getData()); + rm.done(); + } + }); + } + }; + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel the client request + q.cancel(true); + + assertCacheInvalidAndWithCanceledRM(); + + // AbstractCache.canceled() should be called after isCanceled() + // discovers that the client has canceled its request. The canceled() method is + // called in a separate dispatch cycle, so we have to wait one cycle of the executor + // after is canceled is called. + fRetrieveRm.isCanceled(); + Protocol.invokeAndWait(new Runnable() { public void run() {} }); + Assert.assertTrue(canceledCalled[0]); + + try { + q.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + + + // Completed the retrieve RM + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Validate that cache didn't accept the result after its RM was canceled + assertCacheInvalidAndWithCanceledRM(); + } + + /** + * This test forces a race condition where a client that requested data + * cancels. While shortly after a second client starts a new request. + * The first request's cancel should not interfere with the second + * request. + */ + public void testCancelAfterCompletedRaceCondition() throws InterruptedException, ExecutionException { + + // Create a client request with a badly behaved cancel implementation. + final Callback[] rmBad = new Callback[1] ; + final boolean qBadCanceled[] = new boolean[] { false }; + Query qBad = new Query() { + @Override + protected void execute(final DataCallback rm) { + rmBad[0] = new Callback(rm) { + @Override + public synchronized void removeCancelListener(ICanceledListener listener) { + // Do not add the cancel listener so that the cancel request is not + // propagated to the cache. + } + + @Override + public void cancel() { + if (qBadCanceled[0]) { + super.cancel(); + } + } + + @Override + public synchronized boolean isCanceled() { + return qBadCanceled[0]; + } + + @Override + public synchronized void done() { + // Avoid clearing cancel listeners list + }; + + @Override + protected void handleSuccess() { + rm.setData(fTestCache.getData()); + rm.done(); + }; + }; + + fTestCache.update(rmBad[0]); + } + }; + qBad.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Reset the cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm = null; + fTestCache.set(null, null, true); + fTestCache.reset(); + } + }); + + Query qGood = new TestQuery(); + qGood.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + qBadCanceled[0] = true; + rmBad[0].cancel(); + + Assert.assertFalse(fRetrieveRm.isCanceled()); + + // Completed the retrieve RM + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + qGood.get(); + + assertCacheValidWithData(1); + } + + public void testCancelWhilePendingWithTwoClients() throws InterruptedException, ExecutionException { + + // Request data from cache. Use an additional invokeAndWait to + // ensure both update requests are initiated before we wait + // for retrieval to start + Query q1 = new TestQuery(); + q1.invoke(); + Protocol.invokeAndWait(new Runnable() { public void run() {} }); + + // Request data from cache again + Query q2 = new TestQuery(); + q2.invoke(); + Protocol.invokeAndWait(new Runnable() { public void run() {} }); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel the first client request + q1.cancel(true); + try { + q1.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + assertCacheWaiting(); + + // Cancel the second request + q2.cancel(true); + try { + q2.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + + assertCacheInvalidAndWithCanceledRM(); + + // Completed the retrieve RM + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Validate that cache didn't accept the result after its RM was canceled + assertCacheInvalidAndWithCanceledRM(); + } + + public void testCancelWhilePendingWithManyClients() throws InterruptedException, ExecutionException { + // Request data from cache + List> qList = new ArrayList>(); + for (int i = 0; i < 10; i++) { + Query q = new TestQuery(); + q.invoke(); + Protocol.invokeAndWait(new Runnable() { public void run() {} }); + qList.add(q); + } + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + // Cancel some client requests + int[] toCancel = new int[] { 0, 2, 5, 9}; + for (int i = 0; i < toCancel.length; i++) { + + // Cancel request and verify that its canceled + Query q = qList.get(toCancel[i]); + q.cancel(true); + try { + q.get(); + Assert.fail("Expected a cancellation exception"); + } catch (CancellationException e) {} // Expected exception; + qList.set(toCancel[i], null); + + assertCacheWaiting(); + } + + // Replace canceled requests with new ones + for (int i = 0; i < toCancel.length; i++) { + Query q = new TestQuery(); + q.invoke(); + Protocol.invokeAndWait(new Runnable() { public void run() {} }); + qList.set(toCancel[i], q); + assertCacheWaiting(); + } + + // Now cancel all requests + for (int i = 0; i < (qList.size() - 1); i++) { + // Validate that cache is still waiting and is not canceled + assertCacheWaiting(); + qList.get(i).cancel(true); + } + qList.get(qList.size() - 1).cancel(true); + assertCacheInvalidAndWithCanceledRM(); + + // Completed the retrieve RM + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Validate that cache didn't accept the result after its RM was canceled + assertCacheInvalidAndWithCanceledRM(); + } + + public void testResetWhileValid() throws InterruptedException, ExecutionException { + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + q.get(); + + // Disable cache + Protocol.invokeAndWait(new Runnable() { + public void run() { + fTestCache.reset(); + } + }); + + // Check final state + assertCacheResetWithoutData(); + } + + public void testSetAndReset() throws InterruptedException, ExecutionException { + fTestCache = new TestCache() { + @Override + protected void handleCompleted(Integer data, Throwable error, boolean canceled) { + if (!canceled) { + // USE 'false' for valid argument. Cache should be left in + // invalid state. + set(data, error, false); + } + } + }; + + // Request data from cache + Query q = new TestQuery(); + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(); + + Protocol.invokeAndWait(new Runnable() { + public void run() { + fRetrieveRm.setData(1); + fRetrieveRm.done(); + } + }); + + // Query should complete with the data from request monitor. + try { + q.get(); + Assert.fail("Expected InvalidCacheException"); + } catch(ExecutionException e) {} + + // No need to disable cache, it should already be disabled. + + // Check final state + assertCacheResetWithoutData(); + } + +} \ No newline at end of file diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/QueryTests.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/QueryTests.java new file mode 100644 index 000000000..1c790c7fa --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/QueryTests.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.eclipse.tcf.debug.test.util.Callback; +import org.eclipse.tcf.debug.test.util.Callback.ICanceledListener; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.Query; +import org.eclipse.tcf.protocol.Protocol; +import org.junit.Test; + +/** + * Tests that exercise the Query object. + */ +public class QueryTests extends TestCase{ + + public void testSimpleGet() throws InterruptedException, ExecutionException { + Query q = new Query() { + @Override + protected void execute(DataCallback rm) { + rm.setData(1); + rm.done(); + } + }; + // Check initial state + Assert.assertTrue(!q.isDone()); + Assert.assertTrue(!q.isCancelled()); + + q.invoke(); + Assert.assertEquals(1, (int)q.get()); + + // Check final state + Assert.assertTrue(q.isDone()); + Assert.assertTrue(!q.isCancelled()); + + } + + public void testGetError() throws InterruptedException, ExecutionException { + final String error_message = "Test Error"; + + Query q = new Query() { + @Override + protected void execute(DataCallback rm) { + rm.setError(new Throwable(error_message)); //$NON-NLS-1$ + rm.done(); + } + }; + + // Check initial state + Assert.assertTrue(!q.isDone()); + Assert.assertTrue(!q.isCancelled()); + + q.invoke(); + + try { + q.get(); + Assert.fail("Expected exception"); + } catch (ExecutionException e) { + Assert.assertEquals(e.getCause().getMessage(), error_message); + } + + // Check final state + Assert.assertTrue(q.isDone()); + Assert.assertTrue(!q.isCancelled()); + + } + + public void testGetWithMultipleDispatches() throws InterruptedException, ExecutionException { + Query q = new Query() { + @Override + protected void execute(final DataCallback rm) { + Protocol.invokeLater(new Runnable() { + public void run() { + rm.setData(1); + rm.done(); + } + @Override + public String toString() { return super.toString() + "\n getWithMultipleDispatchesTest() second runnable"; } //$NON-NLS-1$ + }); + } + @Override + public String toString() { return super.toString() + "\n getWithMultipleDispatchesTest() first runnable (query)"; } //$NON-NLS-1$ + }; + q.invoke(); + Assert.assertEquals(1, (int)q.get()); + } + + public void testExceptionOnGet() throws InterruptedException, ExecutionException { + Query q = new Query() { + @Override + protected void execute(final DataCallback rm) { + rm.setError(new Throwable("")); //$NON-NLS-1$ + rm.done(); + } + }; + + q.invoke(); + + try { + q.get(); + Assert.fail("Excpected ExecutionExeption"); + } catch (ExecutionException e) { + } finally { + Assert.assertTrue(q.isDone()); + Assert.assertTrue(!q.isCancelled()); + } + } + + public void testCancelBeforeWaiting() throws InterruptedException, ExecutionException { + final Query q = new Query() { + @Override protected void execute(final DataCallback rm) { + Assert.fail("Query was cancelled, it should not be called."); //$NON-NLS-1$ + rm.done(); + } + }; + + // Cancel before invoking the query. + q.cancel(false); + + Assert.assertTrue(q.isDone()); + Assert.assertTrue(q.isCancelled()); + + // Start the query. + q.invoke(); + + + + // Block to retrieve data + try { + q.get(); + } catch (CancellationException e) { + return; // Success + } finally { + Assert.assertTrue(q.isDone()); + Assert.assertTrue(q.isCancelled()); + } + Assert.assertTrue("CancellationException should have been thrown", false); //$NON-NLS-1$ + } + + public void testCancelWhileWaiting() throws InterruptedException, ExecutionException { + final DataCallback[] rmHolder = new DataCallback[1]; + final Boolean[] cancelCalled = new Boolean[] { Boolean.FALSE }; + + final Query q = new Query() { + @Override protected void execute(final DataCallback rm) { + synchronized (rmHolder) { + rmHolder[0] = rm; + rmHolder.notifyAll(); + } + } + }; + + // Start the query. + q.invoke(); + + // Wait until the query is started + synchronized (rmHolder) { + while(rmHolder[0] == null) { + rmHolder.wait(); + } + } + + // Add a cancel listener to the query RM + rmHolder[0].addCancelListener(new ICanceledListener() { + + public void requestCanceled(Callback rm) { + cancelCalled[0] = Boolean.TRUE; + } + }); + + // Cancel running request. + q.cancel(false); + + Assert.assertTrue(cancelCalled[0]); + Assert.assertTrue(rmHolder[0].isCanceled()); + Assert.assertTrue(q.isCancelled()); + Assert.assertTrue(q.isDone()); + + // Retrieve data + try { + q.get(); + } catch (CancellationException e) { + return; // Success + } finally { + Assert.assertTrue(q.isDone()); + Assert.assertTrue(q.isCancelled()); + } + + // Complete rm and query. + @SuppressWarnings("unchecked") + DataCallback drm = (DataCallback)rmHolder[0]; + drm.setData(new Integer(1)); + rmHolder[0].done(); + + // Try to retrieve data again, it should still result in + // cancellation exception. + try { + q.get(); + } catch (CancellationException e) { + return; // Success + } finally { + Assert.assertTrue(q.isDone()); + Assert.assertTrue(q.isCancelled()); + } + + + Assert.assertTrue("CancellationException should have been thrown", false); //$NON-NLS-1$ + } + + + public void testGetTimeout() throws InterruptedException, ExecutionException { + final Query q = new Query() { + @Override + protected void execute(final DataCallback rm) { + // Call done with a delay of 1 second, to avoid stalling the tests. + Protocol.invokeLater( + 60000, + new Runnable() { + public void run() { rm.done(); } + }); + } + }; + + q.invoke(); + + // Note: no point in checking isDone() and isCancelled() here, because + // the value could change on timing. + + try { + q.get(1, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + return; // Success + } finally { + Assert.assertFalse("Query should not be done yet, it should have timed out first.", q.isDone()); //$NON-NLS-1$ + } + Assert.assertTrue("TimeoutException should have been thrown", false); //$NON-NLS-1$ + } + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java index 7173f262a..4c49aa480 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java @@ -3,23 +3,15 @@ package org.eclipse.tcf.debug.test; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem; -import org.eclipse.jface.viewers.TreePath; import org.eclipse.tcf.debug.test.services.IWaitForEventCache; import org.eclipse.tcf.debug.test.services.RunControlCM; -import org.eclipse.tcf.debug.test.services.RunControlCM.ContextState; import org.eclipse.tcf.debug.test.util.Transaction; -import org.eclipse.tcf.debug.ui.ITCFObject; -import org.eclipse.tcf.protocol.IChannel; -import org.eclipse.tcf.services.IBreakpoints; -import org.eclipse.tcf.services.IDiagnostics.ISymbol; +import org.eclipse.tcf.services.ILineNumbers.CodeArea; import org.eclipse.tcf.services.IRunControl; import org.eclipse.tcf.services.IRunControl.RunControlContext; import org.eclipse.tcf.services.ISymbols; @@ -27,194 +19,11 @@ import org.eclipse.tcf.services.ISymbols.Symbol; import org.junit.Assert; @SuppressWarnings("restriction") -public class SampleTest extends AbstractTcfUITest -{ - private String fTestId; - private RunControlContext fTestCtx; - private String fProcessId = ""; - private String fThreadId = ""; - private RunControlContext fThreadCtx; +public class SampleTest extends AbstractTcfUITest { - @Override - protected void setUp() throws Exception { - super.setUp(); - clearBreakpoints(); - } - - private void clearBreakpoints() throws InterruptedException, ExecutionException { - new Transaction() { - @Override - protected Object process() throws InvalidCacheException, ExecutionException { - // Initialize the event cache for breakpoint status - @SuppressWarnings("unchecked") - Map[] bps = (Map[])new Map[] { }; - validate( fBreakpointsCM.set(bps, this) ); - return null; - } - }.get(); - } - - private void createBreakpoint(final String bpId, final String testFunc) throws InterruptedException, ExecutionException { - new Transaction() { - private Map fBp; - - { - fBp = new TreeMap(); - fBp.put(IBreakpoints.PROP_ID, bpId); - fBp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); - fBp.put(IBreakpoints.PROP_LOCATION, testFunc); - } - - @Override - protected Object process() throws InvalidCacheException, ExecutionException { - - // Initialize the event cache for breakpoint status -// ICache> waitStatusCache = fBreakpointsCM.waitStatusChanged(bpId, fTestRunKey); - - validate( fBreakpointsCM.add(fBp, this) ); - -// // Wait for breakpoint status event and validate it. -// Map status = validate(waitStatusCache); -// String s = (String)status.get(IBreakpoints.STATUS_ERROR); -// if (s != null) { -// Assert.fail("Invalid BP status: " + s); -// } -// @SuppressWarnings("unchecked") -// Collection> list = (Collection>)status.get(IBreakpoints.STATUS_INSTANCES); -// if (list != null) { -// String err = null; -// for (Map map : list) { -// String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT); -// if (processId.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) -// err = (String)map.get(IBreakpoints.INSTANCE_ERROR); -// } -// if (err != null) { -// Assert.fail("Invalid BP status: " + s); -// } -// } - return null; - } - }.get(); - } - - private void checkBreakpointForErrors(final String bpId, final String processId) throws InterruptedException, ExecutionException { - new Transaction() { - @Override - protected Object process() throws InvalidCacheException, ExecutionException { - // Wait for breakpoint status event and validate it. - Map status = validate( fBreakpointsCM.getStatus(bpId) ); - String s = (String)status.get(IBreakpoints.STATUS_ERROR); - if (s != null) { - Assert.fail("Invalid BP status: " + s); - } - @SuppressWarnings("unchecked") - Collection> list = (Collection>)status.get(IBreakpoints.STATUS_INSTANCES); - if (list != null) { - String err = null; - for (Map map : list) { - String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT); - if (processId.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) - err = (String)map.get(IBreakpoints.INSTANCE_ERROR); - } - if (err != null) { - Assert.fail("Invalid BP status: " + s); - } - } - return null; - } - }.get(); - } - - private void startProcess() throws InterruptedException, ExecutionException { - new Transaction() { - protected Object process() throws Transaction.InvalidCacheException ,ExecutionException { - fTestId = validate( fDiagnosticsCM.runTest(getDiagnosticsTestName(), this) ); - fTestCtx = validate( fRunControlCM.getContext(fTestId) ); - fProcessId = fTestCtx.getProcessID(); - // Create the cache to listen for exceptions. - fRunControlCM.waitForContextException(fTestId, fTestRunKey); - - if (!fProcessId.equals(fTestId)) { - fThreadId = fTestId; - } else { - String[] threads = validate( fRunControlCM.getChildren(fProcessId) ); - fThreadId = threads[0]; - } - fThreadCtx = validate( fRunControlCM.getContext(fThreadId) ); - - Assert.assertTrue("Invalid thread context", fThreadCtx.hasState()); - return new Object(); - }; - }.get(); - } - - private boolean runToTestEntry(final String testFunc) throws InterruptedException, ExecutionException { - return new Transaction() { - Object fWaitForSuspendKey = new Object(); - boolean fSuspendEventReceived = false; - protected Boolean process() throws Transaction.InvalidCacheException ,ExecutionException { - ISymbol sym_func0 = validate( fDiagnosticsCM.getSymbol(fProcessId, testFunc) ); - String sym_func0_value = sym_func0.getValue().toString(); - ContextState state = validate (fRunControlCM.getState(fThreadId)); - if (state.suspended) { - if ( !new BigInteger(state.pc).equals(new BigInteger(sym_func0_value)) ) { - fSuspendEventReceived = true; - // We are not at test entry. Create a new suspend wait cache. - fWaitForSuspendKey = new Object(); - fRunControlCM.waitForContextSuspended(fThreadId, fWaitForSuspendKey); - // Run to entry point. - validate( fRunControlCM.resume(fThreadCtx, fWaitForSuspendKey, IRunControl.RM_RESUME, 1) ); - } - } else { - // Wait until we suspend. - validate( fRunControlCM.waitForContextSuspended(fThreadId, fWaitForSuspendKey) ); - } - - return fSuspendEventReceived; - } - }.get(); - } - - private void initProcessModel(String bpId, String testFunc) throws Exception { - createBreakpoint(bpId, testFunc); - fDebugViewListener.reset(); - - ITCFObject processTCFContext = new ITCFObject() { - public String getID() { return fProcessId; } - public IChannel getChannel() { return channels[0]; } - }; - ITCFObject threadTCFContext = new ITCFObject() { - public String getID() { return fThreadId; } - public IChannel getChannel() { return channels[0]; } - }; - - fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext })); - fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext, threadTCFContext })); - - startProcess(); - runToTestEntry(testFunc); - - final String topFrameId = new Transaction() { - @Override - protected String process() throws InvalidCacheException, ExecutionException { - String[] frameIds = validate( fStackTraceCM.getChildren(fThreadId) ); - Assert.assertTrue("No stack frames" , frameIds.length != 0); - return frameIds[frameIds.length - 1]; - } - }.get(); - - ITCFObject frameTCFContext = new ITCFObject() { - public String getID() { return topFrameId; } - public IChannel getChannel() { return channels[0]; } - }; - fDebugViewListener.addLabelUpdate(new TreePath(new Object[] { fLaunch, processTCFContext, threadTCFContext, frameTCFContext })); - - fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES); - } public void testDebugViewContent() throws Exception { - String bpId = "TestStepBP"; - initProcessModel(bpId, "tcf_test_func0"); + initProcessModel("tcf_test_func0"); VirtualItem launchItem = fDebugViewListener.findElement(new Pattern[] { Pattern.compile(".*" + fLaunch.getLaunchConfiguration().getName() + ".*") } ); Assert.assertTrue(launchItem != null); @@ -227,13 +36,10 @@ public class SampleTest extends AbstractTcfUITest VirtualItem frameItem = fDebugViewListener.findElement(threadItem, new Pattern[] { Pattern.compile(".*tcf_test_func0.*")}); Assert.assertTrue(frameItem != null); - - checkBreakpointForErrors(bpId, fProcessId); } public void testSteppingDebugViewOnly() throws Exception { - String bpId = "TestStepBP"; - initProcessModel(bpId, "tcf_test_func0"); + initProcessModel("tcf_test_func0"); // Execute step loop String previousThreadLabel = null; @@ -250,15 +56,13 @@ public class SampleTest extends AbstractTcfUITest Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel)); previousThreadLabel = topFrameLabel; } - - checkBreakpointForErrors(bpId, fProcessId); } public void testSteppingWithVariablesAndRegisters() throws Exception { fVariablesViewViewer.setActive(true); fRegistersViewViewer.setActive(true); - initProcessModel("TestStepBP", "tcf_test_func0"); + initProcessModel("tcf_test_func0"); // Execute step loop String previousThreadLabel = null; @@ -279,14 +83,10 @@ public class SampleTest extends AbstractTcfUITest Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel)); previousThreadLabel = topFrameLabel; } - - checkBreakpointForErrors("TestStepBP", fProcessId); } public void testSymbolsCMResetOnContextRemove() throws Exception { - createBreakpoint("TestStepBP", "tcf_test_func0"); - startProcess(); - runToTestEntry("tcf_test_func0"); + initProcessModel("tcf_test_func0"); // Retrieve the current PC for use later final String pc = new Transaction() { @@ -343,10 +143,75 @@ public class SampleTest extends AbstractTcfUITest }.get(); } + public void testLineNumbersCMResetOnContextRemove() throws Exception { + initProcessModel("tcf_test_func0"); + + // Retrieve the current PC for use later + final String pc = new Transaction() { + @Override + protected String process() throws InvalidCacheException, ExecutionException { + return validate(fRunControlCM.getState(fThreadId)).pc; + } + }.get(); + + final BigInteger pcNumber = new BigInteger(pc); + final BigInteger pcNumberPlusOne = pcNumber.add(BigInteger.valueOf(1)); + + // Retrieve the line number for current PC. + final CodeArea[] pcCodeAreas = new Transaction() { + @Override + protected CodeArea[] process() throws InvalidCacheException, ExecutionException { + CodeArea[] areas = validate(fLineNumbersCM.mapToSource(fProcessId, pcNumber, pcNumberPlusOne)); + Assert.assertNotNull(areas); + Assert.assertTrue(areas.length != 0); + + areas = validate(fLineNumbersCM.mapToSource(fThreadId, pcNumber, pcNumberPlusOne)); + Assert.assertNotNull(areas); + Assert.assertTrue(areas.length != 0); + + CodeArea[] areas2 = validate(fLineNumbersCM.mapToMemory(fProcessId, areas[0].file, areas[0].start_line, areas[0].start_column)); + Assert.assertNotNull(areas2); + Assert.assertTrue(areas2.length != 0); + + areas2 = validate(fLineNumbersCM.mapToMemory(fThreadId, areas[0].file, areas[0].start_line, areas[0].start_column)); + Assert.assertNotNull(areas2); + Assert.assertTrue(areas2.length != 0); + + return areas; + } + }.get(); + + // End test, check that all caches were reset and now return an error. + new Transaction() { + @Override + protected String process() throws InvalidCacheException, ExecutionException { + validate( fDiagnosticsCM.cancelTest(fTestId, this) ); + validate( fRunControlCM.waitForContextRemoved(fProcessId, this) ); + try { + validate(fLineNumbersCM.mapToSource(fProcessId, pcNumber, pcNumberPlusOne)); + Assert.fail("Expected error"); + } catch (ExecutionException e) {} + try { + validate(fLineNumbersCM.mapToSource(fThreadId, pcNumber, pcNumberPlusOne)); + Assert.fail("Expected error"); + } catch (ExecutionException e) {} + try { + CodeArea[] areas3 = validate(fLineNumbersCM.mapToMemory(fProcessId, pcCodeAreas[0].file, pcCodeAreas[0].start_line, pcCodeAreas[0].start_column)); + Assert.fail("Expected error"); + } catch (ExecutionException e) {} + try { + validate(fLineNumbersCM.mapToMemory(fThreadId, pcCodeAreas[0].file, pcCodeAreas[0].start_line, pcCodeAreas[0].start_column)); + Assert.fail("Expected error"); + } catch (ExecutionException e) {} + + return null; + } + }.get(); + } + + public void testSymbolsCMResetOnContextStateChange() throws Exception { - createBreakpoint("TestStepBP", "tcf_test_func2"); - startProcess(); - runToTestEntry("tcf_test_func2"); + initProcessModel("tcf_test_func2"); // Retrieve the current PC and top frame for use later final String pc = new Transaction() { @@ -406,10 +271,10 @@ public class SampleTest extends AbstractTcfUITest } public void testRunControlCMChildrenInvalidation() throws Exception { - createBreakpoint("BP1", "tcf_test_func0"); - startProcess(); - runToTestEntry("tcf_test_func0"); + initProcessModel("tcf_test_func0"); + createBreakpoint("testRunControlCMChildrenInvalidation", "tcf_test_func0"); + // Wait for each threads to start. final String[] threads = new Transaction() { List fThreads = new ArrayList(); @@ -497,6 +362,9 @@ public class SampleTest extends AbstractTcfUITest return null; } }.get(); + + removeBreakpoint("testRunControlCMChildrenInvalidation"); + } } diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TransactionTests.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TransactionTests.java new file mode 100644 index 000000000..f1d0d1932 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TransactionTests.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2006 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test; + +import java.util.Arrays; +import java.util.concurrent.ExecutionException; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.eclipse.tcf.debug.test.util.CallbackCache; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.Query; +import org.eclipse.tcf.debug.test.util.Transaction; +import org.eclipse.tcf.protocol.Protocol; + +/** + * Tests that exercise the Transaction object. + */ +public class TransactionTests extends TestCase { + final static private int NUM_CACHES = 5; + + TestCache[] fTestCaches = new TestCache[NUM_CACHES]; + DataCallback[] fRetrieveRms = new DataCallback[NUM_CACHES]; + + class TestCache extends CallbackCache { + + final private int fIndex; + + public TestCache(int index) { + fIndex = index; + } + + @Override + protected void retrieve(DataCallback rm) { + synchronized(TransactionTests.this) { + fRetrieveRms[fIndex] = rm; + TransactionTests.this.notifyAll(); + } + } + + } + + class TestSingleTransaction extends Transaction { + + @Override + protected Integer process() throws InvalidCacheException, ExecutionException { + validate(fTestCaches[0]); + return fTestCaches[0].getData(); + } + } + + class TestSumTransaction extends Transaction { + @Override + protected Integer process() throws InvalidCacheException, ExecutionException { + validate(fTestCaches); + + int sum = 0; + for (CallbackCache cache : fTestCaches) { + sum += cache.getData(); + } + return sum; + } + } + + /** + * There's no rule on how quickly the cache has to start data retrieval + * after it has been requested. It could do it immediately, or it could + * wait a dispatch cycle, etc.. + */ + private void waitForRetrieveRm(boolean all) { + synchronized(this) { + if (all) { + while (Arrays.asList(fRetrieveRms).contains(null)) { + try { + wait(); + } catch (InterruptedException e) { + return; + } + } + } else { + while (fRetrieveRms[0] == null) { + try { + wait(); + } catch (InterruptedException e) { + return; + } + } + } + } + } + + public void setUp() throws ExecutionException, InterruptedException { + for (int i = 0; i < fTestCaches.length; i++) { + fTestCaches[i] = new TestCache(i); + } + } + + public void tearDown() throws ExecutionException, InterruptedException { + fRetrieveRms = new DataCallback[NUM_CACHES]; + fTestCaches = new TestCache[NUM_CACHES]; + } + + public void testSingleTransaction() throws InterruptedException, ExecutionException { + final TestSingleTransaction testTransaction = new TestSingleTransaction(); + // Request data from cache + Query q = new Query() { + @Override + protected void execute(DataCallback rm) { + testTransaction.request(rm); + } + }; + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(false); + + // Set the data to caches. + Protocol.invokeAndWait(new Runnable() { + public void run() { + ((DataCallback)fRetrieveRms[0]).setData(1); + fRetrieveRms[0].done(); + } + }); + + Assert.assertEquals(1, (int)q.get()); + } + + public void testSumTransaction() throws InterruptedException, ExecutionException { + + final TestSumTransaction testTransaction = new TestSumTransaction(); + // Request data from cache + Query q = new Query() { + @Override + protected void execute(DataCallback rm) { + testTransaction.request(rm); + } + }; + q.invoke(); + + // Wait until the cache starts data retrieval. + waitForRetrieveRm(true); + + + // Set the data to caches. + Protocol.invokeAndWait(new Runnable() { + public void run() { + for (DataCallback rm : fRetrieveRms) { + ((DataCallback)rm).setData(1); + rm.done(); + } + } + }); + + q.invoke(); + Assert.assertEquals(NUM_CACHES, (int)q.get()); + } + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java index c175167bf..faeb6f8dc 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java @@ -20,6 +20,7 @@ import java.util.TreeSet; import junit.framework.Assert; import org.eclipse.debug.internal.ui.viewers.model.ILabelUpdateListener; +import org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate; @@ -29,7 +30,6 @@ import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy; import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener; -import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer; import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener; import org.eclipse.jface.viewers.TreePath; diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/BreakpointsCM.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/BreakpointsCM.java index 3efade570..b4f1798cb 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/BreakpointsCM.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/BreakpointsCM.java @@ -287,8 +287,8 @@ public class BreakpointsCM extends AbstractCacheManager implements IBreakpoints. // TODO: avoid iterating over all entries, use separate list for events. for (Map.Entry, ICache> entry: fMap.entrySet()) { - if (entry.getKey() instanceof IdEventKey) { - IdEventKey eventKey = (IdEventKey)entry.getKey(); + if (entry.getKey() instanceof EventKey) { + EventKey eventKey = (EventKey)entry.getKey(); if ( ContextAddedCache.class.equals( eventKey.getCacheClass() ) ) { ((ContextAddedCache)entry.getValue()).eventReceived(bps); } @@ -312,8 +312,8 @@ public class BreakpointsCM extends AbstractCacheManager implements IBreakpoints. // TODO: avoid iterating over all entries, use separate list for events. for (Map.Entry, ICache> entry: fMap.entrySet()) { - if (entry.getKey() instanceof IdEventKey) { - IdEventKey eventKey = (IdEventKey)entry.getKey(); + if (entry.getKey() instanceof EventKey) { + EventKey eventKey = (EventKey)entry.getKey(); if ( ContextChangedCache.class.equals( eventKey.getCacheClass() ) ) { ((ContextChangedCache)entry.getValue()).eventReceived(bps); } @@ -342,6 +342,16 @@ public class BreakpointsCM extends AbstractCacheManager implements IBreakpoints. PropertiesCache cache = mapCache(new PropertiesCacheKey(id)); cache.resetProperties(); } + + // TODO: avoid iterating over all entries, use separate list for events. + for (Map.Entry, ICache> entry: fMap.entrySet()) { + if (entry.getKey() instanceof EventKey) { + EventKey eventKey = (EventKey)entry.getKey(); + if ( ContextRemovedCache.class.equals( eventKey.getCacheClass() ) ) { + ((ContextRemovedCache)entry.getValue()).eventReceived(ids); + } + } + } } diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/LineNumbersCM.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/LineNumbersCM.java new file mode 100644 index 000000000..639554384 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/services/LineNumbersCM.java @@ -0,0 +1,232 @@ +/*******************************************************************************sbsb + * Copyright (c) 2012 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test.services; + +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.eclipse.tcf.debug.test.util.CallbackCache; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.ICache; +import org.eclipse.tcf.debug.test.util.TokenCache; +import org.eclipse.tcf.debug.test.util.Transaction; +import org.eclipse.tcf.debug.test.util.TransactionCache; +import org.eclipse.tcf.protocol.IToken; +import org.eclipse.tcf.services.ILineNumbers; +import org.eclipse.tcf.services.ILineNumbers.CodeArea; +import org.eclipse.tcf.services.IMemoryMap; +import org.eclipse.tcf.services.IMemoryMap.MemoryMapListener; +import org.eclipse.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tcf.services.IRunControl.RunControlListener; + +/** + * + */ +public class LineNumbersCM extends AbstractCacheManager { + + private ResetMap fMemContextResetMap = new ResetMap(); + private ILineNumbers fService; + private IMemoryMap fMemoryMap; + private RunControlCM fRunControlCM; + + public LineNumbersCM(ILineNumbers lineNumbers, IMemoryMap memMap, RunControlCM runControlCM) { + fService = lineNumbers; + fMemoryMap = memMap; + fMemoryMap.addListener(fMemoryMapListener); + fRunControlCM = runControlCM; + fRunControlCM.addListener(fRunControlListener); + } + + @Override + public void dispose() { + fRunControlCM.removeListener(fRunControlListener); + fMemoryMap.removeListener(fMemoryMapListener); + super.dispose(); + } + + private abstract class LineNumbersTokenCache extends TokenCache { + abstract protected String getId(); + + protected void set(IToken token, V data, Throwable error) { + fMemContextResetMap.addValid(getId(), this); + super.set(token, data, error); + } + } + + abstract private class MapToSourceKey extends IdKey { + private final Number fStartAdddress; + private final Number fEndAddress; + + public MapToSourceKey(Class cacheClass, String id, Number startAddress, Number endAddress) { + super(cacheClass, id); + fStartAdddress = startAddress; + fEndAddress = endAddress; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj) && obj instanceof MapToSourceKey) { + MapToSourceKey other = (MapToSourceKey)obj; + return fStartAdddress.equals(other.fStartAdddress) && fEndAddress.equals(other.fEndAddress); + } + return false; + } + @Override + public int hashCode() { + return super.hashCode() + fStartAdddress.hashCode() + fEndAddress.hashCode(); + } + } + + public ICache mapToSource(final String context_id, final Number start_address, final Number end_address) { + class MyCache extends TransactionCache { + private String fId = context_id; + + @Override + protected CodeArea[] process() throws InvalidCacheException, ExecutionException { + RunControlContext rcContext = validate(fRunControlCM.getContext(fId)); + String mem_id = rcContext.getProcessID(); + if (mem_id == null) { + // TODO: is this the correct fall-back. Should we save the parent ID for reset? + mem_id = fId; + } + return validate( doMapToSource(mem_id, start_address, end_address) ); + } + } + + return mapCache(new MapToSourceKey(MyCache.class, context_id, start_address, end_address) { + @Override MyCache createCache() { return new MyCache(); } + }); + } + + private ICache doMapToSource(final String mem_id, final Number start_address, final Number end_address) { + class MyCache extends LineNumbersTokenCache implements ILineNumbers.DoneMapToSource { + @Override + protected String getId() { + return mem_id; + } + @Override + protected IToken retrieveToken() { + return fService.mapToSource(mem_id, start_address, end_address, this); + } + + public void doneMapToSource(IToken token, Exception error, CodeArea[] areas) { + set(token, areas, error); + } + + }; + + return mapCache(new MapToSourceKey(MyCache.class, mem_id, start_address, end_address) { + @Override MyCache createCache() { return new MyCache(); } + }); + + } + + abstract private class MapToMemoryKey extends IdKey { + private final String fFile; + private final int fLine; + private final int fColumn; + + public MapToMemoryKey(Class cacheClass, String id, String file, int line, int col) { + super(cacheClass, id); + fFile = file; + fLine = line; + fColumn = col; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj) && obj instanceof MapToMemoryKey) { + MapToMemoryKey other = (MapToMemoryKey)obj; + return fFile.equals(other.fFile) && fLine == other.fLine && fColumn == other.fColumn; + } + return false; + } + @Override + public int hashCode() { + return super.hashCode() + fFile.hashCode()^fLine^(fColumn + 1); + } + } + + public ICache mapToMemory(final String context_id, final String file, final int line, final int column) { + class MyCache extends TransactionCache { + private String fId = context_id; + + protected CodeArea[] process() throws InvalidCacheException, ExecutionException { + RunControlContext rcContext = validate(fRunControlCM.getContext(fId)); + String mem_id = rcContext.getProcessID(); + if (mem_id == null) { + // TODO: is this the correct fall-back. Should we save the parent ID for reset? + mem_id = fId; + } + return validate( doMapToMemory(mem_id, file, line, column) ); + } + } + + return mapCache(new MapToMemoryKey(MyCache.class, context_id, file, line, column) { + @Override MyCache createCache() { return new MyCache(); } + }); + } + + private ICache doMapToMemory(final String mem_id, final String file, final int line, final int column) { + class MyCache extends LineNumbersTokenCache implements ILineNumbers.DoneMapToMemory { + @Override + protected String getId() { + return mem_id; + } + @Override + protected IToken retrieveToken() { + return fService.mapToMemory(mem_id, file, line, column, this); + } + public void doneMapToMemory(IToken token, Exception error, CodeArea[] areas) { + set(token, areas, error); + } + + }; + + return mapCache(new MapToMemoryKey(MyCache.class, mem_id, file, line, column) { + @Override MyCache createCache() { return new MyCache(); } + }); + + } + + interface DoneMapToMemory { + void doneMapToMemory(IToken token, Exception error, CodeArea[] areas); + } + + private RunControlListener fRunControlListener = new RunControlListener() { + + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + resetContext(id); + } + } + + public void contextAdded(RunControlContext[] contexts) {} + public void contextChanged(RunControlContext[] contexts) {} + public void contextSuspended(String context, String pc, String reason, Map params) {} + public void contextResumed(String context) {} + public void containerSuspended(String context, String pc, String reason, Map params, + String[] suspended_ids) {} + public void containerResumed(String[] context_ids) {} + public void contextException(String context, String msg) {} + }; + + private void resetContext(String id) { + fMemContextResetMap.reset(id); + } + + private MemoryMapListener fMemoryMapListener = new MemoryMapListener() { + public void changed(String context_id) { + resetContext(context_id); + } + }; + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AbstractCache.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AbstractCache.java index 29dbd81f1..fcb737215 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AbstractCache.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AbstractCache.java @@ -90,14 +90,14 @@ public abstract class AbstractCache implements ICache { } public V getData() { - if (!fValid) { + if (!isValid()) { throw new IllegalStateException("Cache is not valid. Cache data can be read only when cache is valid."); //$NON-NLS-1$ } return fData; } public Throwable getError() { - if (!fValid) { + if (!isValid()) { throw new IllegalStateException("Cache is not valid. Cache status can be read only when cache is valid."); //$NON-NLS-1$ } return fError; @@ -106,42 +106,37 @@ public abstract class AbstractCache implements ICache { public void update(Callback rm) { assert Protocol.isDispatchThread(); - if (!fValid) { - boolean first = false; - synchronized (this) { - if (fWaitingList == null) { - first = true; - fWaitingList = rm; - } else if (fWaitingList instanceof Callback[]) { - Callback[] waitingList = (Callback[])fWaitingList; - int waitingListLength = waitingList.length; - int i; - for (i = 0; i < waitingListLength; i++) { - if (waitingList[i] == null) { - waitingList[i] = rm; - break; - } - } - if (i == waitingListLength) { - Callback[] newWaitingList = new Callback[waitingListLength + 1]; - System.arraycopy(waitingList, 0, newWaitingList, 0, waitingListLength); - newWaitingList[waitingListLength] = rm; - fWaitingList = newWaitingList; + boolean first = false; + synchronized (this) { + if (fWaitingList == null) { + first = true; + fWaitingList = rm; + } else if (fWaitingList instanceof Callback[]) { + Callback[] waitingList = (Callback[])fWaitingList; + int waitingListLength = waitingList.length; + int i; + for (i = 0; i < waitingListLength; i++) { + if (waitingList[i] == null) { + waitingList[i] = rm; + break; } - } else { - Callback[] newWaitingList = new Callback[2]; - newWaitingList[0] = (Callback)fWaitingList; - newWaitingList[1] = rm; + } + if (i == waitingListLength) { + Callback[] newWaitingList = new Callback[waitingListLength + 1]; + System.arraycopy(waitingList, 0, newWaitingList, 0, waitingListLength); + newWaitingList[waitingListLength] = rm; fWaitingList = newWaitingList; } - } - rm.addCancelListener(fRequestCanceledListener); - if (first) { - retrieve(); + } else { + Callback[] newWaitingList = new Callback[2]; + newWaitingList[0] = (Callback)fWaitingList; + newWaitingList[1] = rm; + fWaitingList = newWaitingList; } - } else { - rm.setError(fError); - rm.done(); + } + rm.addCancelListener(fRequestCanceledListener); + if (first && !isValid()) { + retrieve(); } } @@ -167,7 +162,12 @@ public abstract class AbstractCache implements ICache { } private void completeWaitingRm(Callback rm) { - rm.setError(fError); + if (!isValid() && fError == null) { + rm.setError(INVALID_STATUS); + } else { + rm.setError(fError); + } + rm.removeCancelListener(fRequestCanceledListener); rm.done(); } diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/ICache.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/ICache.java index eca1bb713..c4c761ab3 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/ICache.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/ICache.java @@ -39,15 +39,19 @@ public interface ICache { public Throwable getError(); /** - * Asks the cache to update its value from the source. If the cache is - * already valid, the request is completed immediately, otherwise data will - * first be retrieved from the source. Typically, this method is called by a - * client after it discovers the cache is invalid via {@link #isValid()} + * Asks the cache to update its value from the source. Typically, this + * method is called by a client after it discovers the cache is invalid + * via {@link #isValid()}. * - * @param rm - * RequestMonitor that is called when cache becomes valid. + *

If the cache is already valid, the cache is not updated again from + * source. Instead the callback is completed next time the cache state is + * changed. Clients can use this feature to be notified when the cache is + * being reset. + *

+ * @param cb + * Callback that is called when cache becomes valid. */ - public void update(Callback rm); + public void update(Callback cb); /** * Returns true if the cache is currently valid. I.e. diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Transaction.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Transaction.java index d27281eaf..7729ad1a0 100644 --- a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Transaction.java +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Transaction.java @@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.core.runtime.CoreException; +import org.eclipse.tcf.protocol.Protocol; /** * @since 2.2 @@ -62,6 +63,36 @@ public abstract class Transaction implements Future { execute(); } + protected void preProcess() {} + + protected void postProcess(boolean done, V data, Throwable error) {} + + protected boolean processUnchecked() { + try { + // Execute the transaction logic + V data = process(); + + // No exception means all cache objects used by the transaction + // were valid and up to date. Complete the request + setData(data); + return true; + } + catch (InvalidCacheException e) { + // At least one of the cache objects was stale/unset. Keep the + // request monitor in the incomplete state, thus leaving our client + // "waiting" (asynchronously). We'll get called again once the cache + // objects are updated, thus re-starting the whole transaction + // attempt. + return false; + } + catch (Throwable e) { + // At least one of the cache objects encountered a failure obtaining + // the data from the source. Complete the request. + setError(e); + return true; + } + } + /** * The transaction logic--code that tries to synchronously make use of, * usually, multiple data points that are normally obtained asynchronously. @@ -83,6 +114,24 @@ public abstract class Transaction implements Future { */ abstract protected V process() throws InvalidCacheException, ExecutionException; + /** + * Can be called only while in process(). + * @param data + */ + protected void setData(V data) { + assert Protocol.isDispatchThread(); + fRm.setData(data); + } + + /** + * Can be called only while in process(). + * @param data + */ + protected void setError(Throwable error) { + assert Protocol.isDispatchThread(); + fRm.setError(error); + } + /** * Method which invokes the transaction logic and handles any exception that * may result. If that logic encounters a stale/unset cache object, then we @@ -95,32 +144,15 @@ public abstract class Transaction implements Future { fRm = null; return; } - - try { - // Execute the transaction logic - V data = process(); - - // No exception means all cache objects used by the transaction - // were valid and up to date. Complete the request - fRm.setData(data); - fRm.done(); - fRm = null; - } - catch (InvalidCacheException e) { - // At least one of the cache objects was stale/unset. Keep the - // request monitor in the incomplete state, thus leaving our client - // "waiting" (asynchronously). We'll get called again once the cache - // objects are updated, thus re-starting the whole transaction - // attempt. - } - catch (Throwable e) { - // At least one of the cache objects encountered a failure obtaining - // the data from the source. Complete the request. - fRm.setError(e); + + preProcess(); + if (processUnchecked()) { + postProcess(true, fRm.getData(), fRm.getError()); fRm.done(); fRm = null; + } else { + postProcess(false, null, null); } - } /** diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/TransactionCache.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/TransactionCache.java new file mode 100644 index 000000000..874945331 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/TransactionCache.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2012 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + + +/** + * + */ +public abstract class TransactionCache extends Transaction implements ICache { + + private List> fDependsOn; + private List fDependsOnCallbacks; + + private CallbackCache fCache = new CallbackCache() { + @Override + protected void retrieve(DataCallback rm) { + request(rm); + } + }; + + public V getData() { + return fCache.getData(); + } + + public Throwable getError() { + return fCache.getError(); + } + + public void update(Callback rm) { + fCache.update(rm); + } + + public boolean isValid() { + return fCache.isValid(); + } + + @Override + protected void preProcess() { + super.preProcess(); + + if (fDependsOnCallbacks != null) { + for (Callback cb : fDependsOnCallbacks) { + cb.cancel(); + } + fDependsOnCallbacks = null; + } + fDependsOn = new ArrayList>(4); + } + + protected void postProcess(boolean done, V data, Throwable error) { + if (done) { + fDependsOnCallbacks = new ArrayList(fDependsOn.size()); + for (ICache cache : fDependsOn) { + assert cache.isValid(); + cache.update(new Callback() { + @Override + protected void handleCompleted() { + if (!isCancelled()) { + fCache.reset(); + for (Callback cb : fDependsOnCallbacks) { + if (cb == this) continue; + cb.cancel(); + } + } + } + }); + } + } else { + fDependsOn = null; + } + super.postProcess(done, data, error); + } + + /** + * Can be called while in {@link #process()} + * @param cache + */ + public void addDependsOn(ICache cache) { + fDependsOn.add(cache); + } + + public T validate(ICache cache) throws InvalidCacheException, ExecutionException { + if (cache.isValid()) { + addDependsOn(cache); + } + return super.validate(cache); + } + + @Override + public void validate(Iterable caches) throws InvalidCacheException, ExecutionException { + for (Object cacheObj : caches) { + ICache cache = (ICache)cacheObj; + if (cache.isValid()) { + addDependsOn(cache); + } + } + super.validate(caches); + } +} -- cgit v1.2.3