diff options
author | Eugene Tarassov | 2011-12-15 22:03:09 +0000 |
---|---|---|
committer | Eugene Tarassov | 2011-12-15 22:03:09 +0000 |
commit | da70cd9b8c31a12eca0969fa1bbf657e1945360a (patch) | |
tree | 96eabe792c81797ae2aec27bec56074a78602ab6 /tests | |
parent | 649b46a48f0a085976144d6a191f9726d1df566e (diff) | |
download | org.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.tar.gz org.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.tar.xz org.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.zip |
Bug 349998 - [test] JUnit tests for debug UI
Diffstat (limited to 'tests')
22 files changed, 3160 insertions, 0 deletions
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.classpath b/tests/plugins/org.eclipse.tcf.debug.test/.classpath new file mode 100644 index 000000000..64c5e31b7 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.project b/tests/plugins/org.eclipse.tcf.debug.test/.project new file mode 100644 index 000000000..114675c19 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.tcf.debug.test</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> 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 new file mode 100644 index 000000000..9d7f7d27a --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Thu May 26 13:10:48 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +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 new file mode 100644 index 000000000..48627ce45 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: org.eclipse.tcf.debug.test +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.tcf.debug.test.Activator +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.tcf.debug, + 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" +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/build.properties b/tests/plugins/org.eclipse.tcf.debug.test/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . 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 new file mode 100644 index 000000000..efd2294a2 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java @@ -0,0 +1,605 @@ +package org.eclipse.tcf.debug.test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.TestCase; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.DebugPlugin; +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.IDisconnect; +import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext; +import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer; +import org.eclipse.debug.ui.IDebugUIConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.tcf.core.TransientPeer; +import org.eclipse.tcf.debug.test.util.AggregateCallback; +import org.eclipse.tcf.debug.test.util.Callback; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.Query; +import org.eclipse.tcf.debug.test.util.Task; +import org.eclipse.tcf.debug.test.util.Callback.ICanceledListener; +import org.eclipse.tcf.protocol.IChannel; +import org.eclipse.tcf.protocol.IPeer; +import org.eclipse.tcf.protocol.IToken; +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.IRunControl; +import org.eclipse.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tcf.services.IStackTrace; +import org.eclipse.tcf.services.ISymbols; +import org.junit.Assert; + +/** + * Base test for validating TCF Debugger UI. + */ +@SuppressWarnings("restriction") +public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdatesListenerConstants { + + private final static int NUM_CHANNELS = 1; + + protected IChannel[] channels; + + private Query<Object> fMonitorChannelQuery; + private List<Throwable> errors = new ArrayList<Throwable>(); + private IPeer peer; + protected ILaunch fLaunch; + + protected VirtualTreeModelViewer fDebugViewViewer; + protected TestDebugContextProvider fDebugContextProvider; + protected VirtualViewerUpdatesListener fDebugViewListener; + protected VariablesVirtualTreeModelViewer fVariablesViewViewer; + protected VirtualViewerUpdatesListener fVariablesViewListener; + protected VariablesVirtualTreeModelViewer fRegistersViewViewer; + protected VirtualViewerUpdatesListener fRegistersViewListener; + + protected IDiagnostics diag; + protected IExpressions expr; + protected ISymbols syms; + protected IStackTrace stk; + protected IRunControl rc; + protected IBreakpoints bp; + + protected TestRunControlListener fRcListener; + + + private static class RemotePeer extends TransientPeer { + private final ArrayList<Map<String,String>> attrs; + + public RemotePeer(ArrayList<Map<String,String>> attrs) { + super(attrs.get(0)); + this.attrs = attrs; + } + + public IChannel openChannel() { + assert Protocol.isDispatchThread(); + IChannel c = super.openChannel(); + for (int i = 1; i < attrs.size(); i++) c.redirect(attrs.get(i)); + return c; + } + } + + private static IPeer getPeer(String[] arr) { + ArrayList<Map<String,String>> l = new ArrayList<Map<String,String>>(); + for (String s : arr) { + Map<String,String> map = new HashMap<String,String>(); + int len = s.length(); + int i = 0; + while (i < len) { + int i0 = i; + while (i < len && s.charAt(i) != '=' && s.charAt(i) != 0) i++; + int i1 = i; + if (i < len && s.charAt(i) == '=') i++; + int i2 = i; + while (i < len && s.charAt(i) != ':') i++; + int i3 = i; + if (i < len && s.charAt(i) == ':') i++; + String key = s.substring(i0, i1); + String val = s.substring(i2, i3); + map.put(key, val); + } + l.add(map); + } + return new RemotePeer(l); + } + + protected void setUp() throws Exception { + + createDebugViewViewer(); + createLaunch(); + + // Command line should contain peer description string, for example: + // "ID=Test:TransportName=TCP:Host=127.0.0.1:Port=1534" + final String[] args = new String[] { "TransportName=TCP:Host=127.0.0.1:Port=1534" }; + if (args.length < 1) { + System.err.println("Missing command line argument - peer identification string"); + System.exit(4); + } + + peer = new Query<IPeer>() { + @Override + protected void execute(DataCallback<IPeer> callback) { + callback.setData(getPeer(args)); + callback.done(); + } + }.get(); + + channels = new IChannel[NUM_CHANNELS]; + + new Query<Object>() { + @Override + protected void execute(DataCallback<Object> callback) { + try { + openChannels(peer, callback); + } + catch (Throwable x) { + errors.add(x); + int cnt = 0; + for (int i = 0; i < channels.length; i++) { + if (channels[i] == null) continue; + if (channels[i].getState() != IChannel.STATE_CLOSED) channels[i].close(); + cnt++; + } + if (cnt == 0) { + callback.setError(errors.get(0)); + callback.done(); + } + } + } + }.get(); + + getRemoteServices(); + + validateTestAvailable(); + + new Task<Object>() { + @Override + public Object call() throws Exception { + setUpServiceListeners(); + return null; + } + }.get(); + } + + @Override + protected void tearDown() throws Exception { + new Task<Object>() { + @Override + public Object call() throws Exception { + tearDownServiceListeners(); + return null; + } + }.get(); + + terminateLaunch(); + disposeDebugViewViewer(); + + new Query<Object>() { + @Override + protected void execute(DataCallback<Object> callback) { + closeChannels(callback); + } + }.get(); + + // Check for listener errors at the end of tearDown. + fRcListener.checkError(); + } + + protected String getDiagnosticsTestName() { + return "RCBP1"; + } + + protected void setUpServiceListeners() throws Exception{ + fRcListener = new TestRunControlListener(rc); + } + + protected void tearDownServiceListeners() throws Exception{ + fRcListener.dispose(); + } + + private void createDebugViewViewer() { + final Display display = Display.getDefault(); + display.syncExec(new Runnable() { + public void run() { + fDebugViewViewer = new VirtualTreeModelViewer(display, SWT.NONE, new PresentationContext(IDebugUIConstants.ID_DEBUG_VIEW)); + fDebugViewViewer.setInput(DebugPlugin.getDefault().getLaunchManager()); + fDebugViewViewer.setAutoExpandLevel(-1); + fDebugViewListener = new VirtualViewerUpdatesListener(fDebugViewViewer); + fDebugContextProvider = new TestDebugContextProvider(fDebugViewViewer); + fVariablesViewViewer = new VariablesVirtualTreeModelViewer(IDebugUIConstants.ID_VARIABLE_VIEW, fDebugContextProvider); + fVariablesViewListener = new VirtualViewerUpdatesListener(fVariablesViewViewer); + fRegistersViewViewer = new VariablesVirtualTreeModelViewer(IDebugUIConstants.ID_REGISTER_VIEW, fDebugContextProvider); + fRegistersViewListener = new VirtualViewerUpdatesListener(fRegistersViewViewer); + } + }); + } + + private void disposeDebugViewViewer() { + final Display display = Display.getDefault(); + display.syncExec(new Runnable() { + public void run() { + fDebugViewListener.dispose(); + fDebugContextProvider.dispose(); + fDebugViewViewer.dispose(); + fVariablesViewListener.dispose(); + fVariablesViewViewer.dispose(); + fRegistersViewListener.dispose(); + fRegistersViewViewer.dispose(); + } + }); + + } + + private void createLaunch() throws CoreException { + ILaunchManager lManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType lcType = lManager.getLaunchConfigurationType("org.eclipse.tcf.debug.LaunchConfigurationType"); + ILaunchConfigurationWorkingCopy lcWc = lcType.newInstance(null, "test"); + lcWc.doSave(); + fLaunch = lcWc.launch("debug", new NullProgressMonitor()); + Assert.assertTrue( fLaunch instanceof IDisconnect ); + } + + private void terminateLaunch() throws DebugException, InterruptedException, ExecutionException { + ((IDisconnect)fLaunch).disconnect(); + + new Query<Object>() { + @Override + protected void execute(final DataCallback<Object> callback) { + final ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager(); + + final AtomicBoolean callbackDone = new AtomicBoolean(false); + ILaunchesListener2 disconnectListener = new ILaunchesListener2() { + public void launchesAdded(ILaunch[] launches) {} + public void launchesChanged(ILaunch[] launches) {} + public void launchesRemoved(ILaunch[] launches) {} + public void launchesTerminated(ILaunch[] launches) { + if (Arrays.asList(launches).contains(fLaunch)) { + if (!callbackDone.getAndSet(true)) { + lm.removeLaunchListener(this); + callback.done(); + } + } + } + }; + lm.addLaunchListener(disconnectListener); + if (((IDisconnect)fLaunch).isDisconnected() && !callbackDone.getAndSet(true)) { + lm.removeLaunchListener(disconnectListener); + callback.done(); + + } + } + }.get(); + } + + private void getRemoteServices() { + assert !Protocol.isDispatchThread(); + Protocol.invokeAndWait(new Runnable() { + public void run() { + diag = channels[0].getRemoteService(IDiagnostics.class); + expr = channels[0].getRemoteService(IExpressions.class); + syms = channels[0].getRemoteService(ISymbols.class); + stk = channels[0].getRemoteService(IStackTrace.class); + rc = channels[0].getRemoteService(IRunControl.class); + bp = channels[0].getRemoteService(IBreakpoints.class); + }; + }); + } + + private void openChannels(IPeer peer, Callback callback) { + assert Protocol.isDispatchThread(); + + for (int i = 0; i < channels.length; i++) { + channels[i] = peer.openChannel(); + } + monitorChannels( + new Callback(callback) { + @Override + protected void handleSuccess() { + fMonitorChannelQuery = new Query<Object>() { + protected void execute(org.eclipse.tcf.debug.test.util.DataCallback<Object> callback) { + monitorChannels(callback, true); + }; + }; + fMonitorChannelQuery.invoke(); + super.handleSuccess(); + } + }, + false); + } + + private void closeChannels(final Callback callback) { + assert Protocol.isDispatchThread(); + fMonitorChannelQuery.cancel(false); + try { + fMonitorChannelQuery.get(); + } catch (ExecutionException e) { + callback.setError(e.getCause()); + } catch (CancellationException e) { + // expected + } catch (InterruptedException e) { + } + + for (int i = 0; i < channels.length; i++) { + if (channels[i].getState() != IChannel.STATE_CLOSED) channels[i].close(); + } + monitorChannels(callback, true); + } + + private static class ChannelMonitorListener implements IChannel.IChannelListener { + + final IChannel fChannel; + final boolean fClose; + final Callback fCallback; + private boolean fActive = true; + + private class CancelListener implements ICanceledListener { + public void requestCanceled(Callback rm) { + Protocol.invokeLater(new Runnable() { + public void run() { + if (deactivate()) { + fCallback.done(); + } + } + }); + } + } + + private boolean deactivate() { + if (fActive) { + fChannel.removeChannelListener(ChannelMonitorListener.this); + fActive = false; + return true; + } + return false; + } + + ChannelMonitorListener (IChannel channel, Callback cb, boolean close) { + fCallback = cb; + fClose = close; + fChannel = channel; + fChannel.addChannelListener(this); + fCallback.addCancelListener(new CancelListener()); + } + + public void onChannelOpened() { + if (!deactivate()) return; + + fChannel.removeChannelListener(this); + fCallback.done(); + } + + public void congestionLevel(int level) { + } + + public void onChannelClosed(Throwable error) { + if (!deactivate()) return; + + if (!fClose) { + fCallback.setError( new IOException("Remote peer closed connection before all tests finished") ); + } else { + fCallback.setError(error); + } + fCallback.done(); + } + } + + protected void monitorChannels(final Callback callback, final boolean close) { + assert Protocol.isDispatchThread(); + + AggregateCallback acb = new AggregateCallback(callback); + int count = 0; + for (int i = 0; i < channels.length; i++) { + if (!checkChannelsState(channels[i], close)) { + new ChannelMonitorListener(channels[i], new Callback(acb), close); + count++; + } + } + acb.setDoneCount(count); + } + + // Checks whether all channels have achieved the desired state. + private boolean checkChannelsState(IChannel channel, boolean close) { + if (close) { + if (channel.getState() != IChannel.STATE_CLOSED) { + return false; + } + } else { + if (channel.getState() != IChannel.STATE_OPEN) { + return false; + } + } + return true; + } + + private void validateTestAvailable() throws ExecutionException, InterruptedException { + String[] testList = getDiagnosticsTestList(); + + int i = 0; + for (; i < testList.length; i++) { + if ("RCBP1".equals(testList[i])) break; + } + + Assert.assertTrue("Required test not supported", i != testList.length); + } + + protected String[] getDiagnosticsTestList() throws ExecutionException, InterruptedException { + assert !Protocol.isDispatchThread(); + return new Query<String[]>() { + @Override + protected void execute(final DataCallback<String[]> callback) { + diag.getTestList(new IDiagnostics.DoneGetTestList() { + public void doneGetTestList(IToken token, Throwable error, String[] list) { + callback.setData(list); + callback.setError(error); + callback.done(); + } + }); + + } + }.get(); + } + + protected void setBreakpoint(final String bp_id, final String location) throws InterruptedException, ExecutionException { + new Query<Object> () { + protected void execute(final DataCallback<Object> callback) { + Map<String,Object> m = new HashMap<String,Object>(); + m.put(IBreakpoints.PROP_ID, bp_id); + m.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + m.put(IBreakpoints.PROP_LOCATION, location); + bp.set(new Map[]{ m }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + callback.setError(error); + callback.done(); + } + }); + } + }.get(); + } + + protected String startDiagnosticsTest() throws InterruptedException, ExecutionException { + return new Query<String> () { + protected void execute(final DataCallback<String> callback) { + diag.runTest(getDiagnosticsTestName(), new IDiagnostics.DoneRunTest() { + public void doneRunTest(IToken token, Throwable error, String id) { + callback.setData(id); + callback.setError(error); + callback.done(); + } + }); + } + }.get(); + } + + protected RunControlContext getRunControlContext(final String contextId) throws InterruptedException, ExecutionException { + return new Query<RunControlContext> () { + @Override + protected void execute(final DataCallback<RunControlContext> callback) { + rc.getContext(contextId, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, IRunControl.RunControlContext ctx) { + callback.setData(ctx); + callback.setError(error); + callback.done(); + } + }); + } + }.get(); + } + + protected String getProcessIdFromRunControlContext(final RunControlContext rcContext) throws InterruptedException, ExecutionException { + return new Task<String>() { + @Override + public String call() throws Exception { + return rcContext.getProcessID(); + } + }.get(); + } + + protected String getSingleThreadIdFromProcess(final String processId) throws InterruptedException, ExecutionException { + return new Query<String> () { + protected void execute(final DataCallback<String> callback) { + rc.getChildren(processId, new IRunControl.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] ids) { + if (error != null) { + callback.setError(error); + } + else if (ids == null || ids.length == 0) { + callback.setError(new Exception("Test process has no threads")); + } + else if (ids.length != 1) { + callback.setError(new Exception("Test process has too many threads")); + } + else { + callback.setData(ids[0]); + } + callback.done(); + } + }); + } + }.get(); + } + + protected boolean getRunControlContextHasState(final RunControlContext rcContext) throws InterruptedException, ExecutionException { + return new Task<Boolean>() { + @Override + public Boolean call() throws Exception { + return rcContext.hasState(); + } + }.get(); + } + + protected ISymbol getDiagnosticsSymbol(final String processId, final String testFunction) throws InterruptedException, ExecutionException { + return new Query<ISymbol>() { + @Override + protected void execute(final DataCallback<ISymbol> callback) { + diag.getSymbol(processId, testFunction, new IDiagnostics.DoneGetSymbol() { + public void doneGetSymbol(IToken token, Throwable error, IDiagnostics.ISymbol symbol) { + if (error != null) { + callback.setError(error); + } + else if (symbol == null) { + callback.setError(new Exception("Symbol must not be null: tcf_test_func3")); + } + else { + callback.setData(symbol); + } + callback.done(); + } + }); + } + }.get(); + } + + protected Number getSymbolValue(final ISymbol symbol) throws InterruptedException, ExecutionException { + return new Task<Number>() { + @Override + public Number call() throws Exception { + return symbol.getValue(); + } + }.get(); + } + + protected void resumeContext(final RunControlContext rcContext, final int mode) throws InterruptedException, ExecutionException { + new Query<Object>() { + @Override + protected void execute(final DataCallback<Object> callback) { + rcContext.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + callback.setError(error); + callback.done(); + } + }); + } + }.get(); + } + + protected void resumeAndWaitForSuspend(final RunControlContext rcContext, final int mode) throws InterruptedException, ExecutionException { + Query<String> suspendQuery = new Query<String> () { + @Override + protected void execute(DataCallback<String> callback) { + fRcListener.addWaitingForSuspend(rcContext.getID(), callback); + } + }; + suspendQuery.invoke(); + resumeContext(rcContext, mode); + suspendQuery.get(); + } + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java new file mode 100644 index 000000000..5f6ce2de6 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java @@ -0,0 +1,50 @@ +package org.eclipse.tcf.debug.test; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.tcf.debug.test"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java new file mode 100644 index 000000000..fb8995fc2 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2009 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; + +/** + * Copied from org.eclipse.cdt.tests.dsf. + * + * Convenience interface with constants used by the test model update listener. + */ +public interface IViewerUpdatesListenerConstants { + + public static final int LABEL_SEQUENCE_COMPLETE = 0X00000001; + public static final int CONTENT_SEQUENCE_COMPLETE = 0X00000002; + public static final int CONTENT_SEQUENCE_STARTED = 0X00020000; + public static final int LABEL_UPDATES = 0X00000004; + public static final int LABEL_SEQUENCE_STARTED = 0X00040000; + public static final int HAS_CHILDREN_UPDATES = 0X00000008; + public static final int HAS_CHILDREN_UPDATES_STARTED = 0X00080000; + public static final int CHILD_COUNT_UPDATES = 0X00000010; + public static final int CHILD_COUNT_UPDATES_STARTED = 0X00100000; + public static final int CHILDREN_UPDATES = 0X00000020; + public static final int CHILDREN_UPDATES_STARTED = 0X00200000; + public static final int MODEL_CHANGED_COMPLETE = 0X00000040; + + /** + * Flag to check whether a model proxy was installed for the model. The model proxy installation is tracked by + * looking for the IModelDelta.EXPAND flag, since the model expands and selects threads when Debug view is opened. + */ + public static final int MODEL_PROXIES_INSTALLED = 0X00000080; + public static final int STATE_SAVE_COMPLETE = 0X00000100; + public static final int STATE_SAVE_STARTED = 0X01000000; + public static final int STATE_RESTORE_COMPLETE = 0X00000200; + public static final int STATE_RESTORE_STARTED = 0X02000000; + public static final int STATE_UPDATES = 0X00000400; + public static final int STATE_UPDATES_STARTED = 0X04000000; + + public static final int VIEWER_UPDATES_RUNNING = 0X00001000; + public static final int LABEL_UPDATES_RUNNING = 0X00002000; + + public static final int VIEWER_UPDATES_STARTED = HAS_CHILDREN_UPDATES_STARTED | CHILD_COUNT_UPDATES_STARTED | CHILDREN_UPDATES_STARTED; + + public static final int LABEL_COMPLETE = LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES | LABEL_UPDATES_RUNNING; + public static final int CONTENT_UPDATES = HAS_CHILDREN_UPDATES | CHILD_COUNT_UPDATES | CHILDREN_UPDATES; + public static final int CONTENT_COMPLETE = CONTENT_UPDATES | CONTENT_SEQUENCE_COMPLETE | VIEWER_UPDATES_RUNNING; + + public static final int ALL_UPDATES_COMPLETE = LABEL_COMPLETE | CONTENT_COMPLETE | MODEL_PROXIES_INSTALLED | LABEL_UPDATES_RUNNING | VIEWER_UPDATES_RUNNING; +} 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 new file mode 100644 index 000000000..f370b43df --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java @@ -0,0 +1,162 @@ +package org.eclipse.tcf.debug.test; + +import java.math.BigInteger; +import java.util.concurrent.ExecutionException; +import java.util.regex.Pattern; + +import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.debug.test.util.Query; +import org.eclipse.tcf.debug.test.util.Task; +import org.eclipse.tcf.services.IDiagnostics.ISymbol; +import org.eclipse.tcf.services.IRunControl; +import org.eclipse.tcf.services.IRunControl.RunControlContext; +import org.junit.Assert; + +@SuppressWarnings("restriction") +public class SampleTest extends AbstractTcfUITest +{ + + private TestBreakpointsListener fBpListener; + private String fTestId; + private RunControlContext fTestCtx; + private String fProcessId; + private String fThreadId; + private RunControlContext fThreadCtx; + + @Override + protected void setUpServiceListeners() throws Exception { + super.setUpServiceListeners(); + fBpListener = new TestBreakpointsListener(bp); + } + + @Override + protected void tearDownServiceListeners() throws Exception { + fBpListener.dispose(); + super.tearDownServiceListeners(); + } + + private void createBreakpoint(String bpId, String testFunc) throws InterruptedException, ExecutionException { + fBpListener.setBreakpointId(bpId); + setBreakpoint(bpId, testFunc); + } + + private void startProcess() throws InterruptedException, ExecutionException { + fTestId = startDiagnosticsTest(); + fTestCtx = getRunControlContext(fTestId); + fProcessId = getProcessIdFromRunControlContext(fTestCtx); + fBpListener.setProcess(fProcessId); + fThreadId = null; + if (!fProcessId.equals(fTestId)) { + fThreadId = fTestId; + } else { + fThreadId = getSingleThreadIdFromProcess(fProcessId); + } + fThreadCtx = getRunControlContext(fThreadId); + Assert.assertTrue("Invalid thread context", getRunControlContextHasState(fThreadCtx)); + } + + protected String getProcessNameFromRunControlContext(final RunControlContext rcContext) throws InterruptedException, ExecutionException { + return new Task<String>() { + @Override + public String call() throws Exception { + return rcContext.getName(); + } + }.get(); + } + + private void runToTestEntry(String testFunc) throws InterruptedException, ExecutionException { + final String suspended_pc = new Query<String> () { + @Override + protected void execute(DataCallback<String> callback) { + fRcListener.waitForSuspend(fThreadCtx, callback); + } + }.get(); + ISymbol sym_func0 = getDiagnosticsSymbol(fProcessId, testFunc); + String sym_func0_value = getSymbolValue(sym_func0).toString(); + if (!new BigInteger(sym_func0_value).equals(new BigInteger(suspended_pc))) { + resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_RESUME); + } + } + + private void initProcessModel(String bpId, String testFunc) throws Exception { + createBreakpoint(bpId, testFunc); + + fDebugViewListener.reset(); + fDebugViewListener.setDelayContentUntilProxyInstall(true); + startProcess(); + fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | MODEL_PROXIES_INSTALLED | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); + runToTestEntry(testFunc); + } + + public void testDebugViewContent() throws Exception { + initProcessModel("TestStepBP", "tcf_test_func0"); + + VirtualItem launchItem = fDebugViewListener.findElement(new Pattern[] { Pattern.compile(".*" + fLaunch.getLaunchConfiguration().getName() + ".*") } ); + Assert.assertTrue(launchItem != null); + + VirtualItem processItem = fDebugViewListener.findElement(launchItem, new Pattern[] { Pattern.compile(".*") } ); + Assert.assertTrue(processItem != null); + + VirtualItem threadItem = fDebugViewListener.findElement(processItem, new Pattern[] { Pattern.compile(".*" + fThreadId + ".*") } ); + Assert.assertTrue(threadItem != null); + + VirtualItem frameItem = fDebugViewListener.findElement(threadItem, new Pattern[] { Pattern.compile(".*")}); + Assert.assertTrue(frameItem != null); + + fBpListener.checkError(); + } + + public void testSteppingDebugViewOnly() throws Exception { + initProcessModel("TestStepBP", "tcf_test_func0"); + + // Execute step loop + String previousThreadLabel = null; + for (int stepNum = 0; stepNum < 100; stepNum++) { + fDebugViewListener.reset(); + + resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_STEP_INTO_LINE); + + fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); + VirtualItem topFrameItem = fDebugViewListener.findElement( + new Pattern[] { Pattern.compile(".*"), Pattern.compile(".*"), Pattern.compile(".*" + fProcessId + ".*\\(Step.*"), Pattern.compile(".*")}); + Assert.assertTrue(topFrameItem != null); + String topFrameLabel = ((String[])topFrameItem.getData(VirtualItem.LABEL_KEY))[0]; + Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel)); + previousThreadLabel = topFrameLabel; + } + + fBpListener.checkError(); + } + + public void testSteppingWithVariablesAndRegisters() throws Exception { + fVariablesViewViewer.setActive(true); + fRegistersViewViewer.setActive(true); + + initProcessModel("TestStepBP", "tcf_test_func0"); + + // Execute step loop + String previousThreadLabel = null; + for (int stepNum = 0; stepNum < 100; stepNum++) { + fDebugViewListener.reset(); + fVariablesViewListener.reset(); + fRegistersViewListener.reset(); + + resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_STEP_INTO_LINE); + + fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); + fVariablesViewListener.waitTillFinished(CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); + fRegistersViewListener.waitTillFinished(CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING); + VirtualItem topFrameItem = fDebugViewListener.findElement( + new Pattern[] { Pattern.compile(".*"), Pattern.compile(".*"), Pattern.compile(".*" + fProcessId + ".*\\(Step.*"), Pattern.compile(".*")}); + Assert.assertTrue(topFrameItem != null); + String topFrameLabel = ((String[])topFrameItem.getData(VirtualItem.LABEL_KEY))[0]; + Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel)); + previousThreadLabel = topFrameLabel; + } + + fBpListener.checkError(); + } + + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java new file mode 100644 index 000000000..d84512fb2 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 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.Collection; +import java.util.Map; + +import org.eclipse.tcf.services.IBreakpoints; + +/** + * Listener for breakpoint service events. + */ +public class TestBreakpointsListener implements IBreakpoints.BreakpointsListener{ + + final private IBreakpoints fBreakpoints; + private Throwable fError; + private String bp_id; + private String process_id; + private boolean test_done = false; + + public TestBreakpointsListener(IBreakpoints bp) { + fBreakpoints = bp; + fBreakpoints.addListener(this); + } + + public void dispose() { + test_done = true; + fBreakpoints.removeListener(this); + } + + public void setBreakpointId(String bpId) { + bp_id = bpId; + } + + public void setProcess(String processId) { + process_id = processId; + } + + public void checkError() throws Exception { + if (fError != null) { + throw new Exception(fError); + } + } + + public void exit(Throwable error) { + fError = error; + } + + public void breakpointStatusChanged(String id, Map<String, Object> status) { + if (id.equals(bp_id) && process_id != null && !test_done) { + String s = (String)status.get(IBreakpoints.STATUS_ERROR); + if (s != null) exit(new Exception("Invalid BP status: " + s)); + Collection<Map<String,Object>> list = (Collection<Map<String,Object>>)status.get(IBreakpoints.STATUS_INSTANCES); + if (list == null) return; + String err = null; + for (Map<String,Object> map : list) { + String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT); + if (process_id.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) + err = (String)map.get(IBreakpoints.INSTANCE_ERROR); + } + if (err != null) exit(new Exception("Invalid BP status: " + err)); + } + } + + public void contextAdded(Map<String, Object>[] bps) { + } + + public void contextChanged(Map<String, Object>[] bps) { + } + + public void contextRemoved(String[] ids) { + } + + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java new file mode 100644 index 000000000..7d127ab7a --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Wind River Systems, Inc. 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 org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener; +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.ITreeModelViewer; +import org.eclipse.debug.ui.contexts.AbstractDebugContextProvider; +import org.eclipse.debug.ui.contexts.DebugContextEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.swt.widgets.Display; + +/** + * Copied from org.eclipse.debug.internal.ui.views.launch.LaunchView class. + * + * This debug context provider emulates the Debug view debug context + * provider behavior. + */ +@SuppressWarnings("restriction") +public class TestDebugContextProvider extends AbstractDebugContextProvider implements IModelChangedListener, ISelectionChangedListener{ + + private ISelection fContext = null; + private ITreeModelViewer fViewer = null; + private Visitor fVisitor = new Visitor(); + + class Visitor implements IModelDeltaVisitor { + public boolean visit(IModelDelta delta, int depth) { + if ((delta.getFlags() & (IModelDelta.STATE | IModelDelta.CONTENT)) > 0) { + // state and/or content change + if ((delta.getFlags() & IModelDelta.SELECT) == 0) { + // no select flag + if ((delta.getFlags() & IModelDelta.CONTENT) > 0) { + // content has changed without select >> possible re-activation + possibleChange(getViewerTreePath(delta), DebugContextEvent.ACTIVATED); + } else if ((delta.getFlags() & IModelDelta.STATE) > 0) { + // state has changed without select >> possible state change of active context + possibleChange(getViewerTreePath(delta), DebugContextEvent.STATE); + } + } + } + return true; + } + } + + /** + * Returns a tree path for the node, *not* including the root element. + * + * @param node + * model delta + * @return corresponding tree path + */ + private TreePath getViewerTreePath(IModelDelta node) { + ArrayList<Object> list = new ArrayList<Object>(); + IModelDelta parentDelta = node.getParentDelta(); + while (parentDelta != null) { + list.add(0, node.getElement()); + node = parentDelta; + parentDelta = node.getParentDelta(); + } + return new TreePath(list.toArray()); + } + + public TestDebugContextProvider(ITreeModelViewer viewer) { + super(null); + fViewer = viewer; + fViewer.addModelChangedListener(this); + fViewer.addSelectionChangedListener(this); + } + + protected void dispose() { + fContext = null; + fViewer.removeModelChangedListener(this); + fViewer.removeSelectionChangedListener(this); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.contexts.IDebugContextProvider#getActiveContext() + */ + public synchronized ISelection getActiveContext() { + return fContext; + } + + protected void activate(ISelection selection) { + synchronized (this) { + fContext = selection; + } + fire(new DebugContextEvent(this, selection, DebugContextEvent.ACTIVATED)); + } + + protected void possibleChange(TreePath element, int type) { + DebugContextEvent event = null; + synchronized (this) { + if (fContext instanceof ITreeSelection) { + ITreeSelection ss = (ITreeSelection) fContext; + if (ss.size() == 1) { + TreePath current = ss.getPaths()[0]; + if (current.startsWith(element, null)) { + if (current.getSegmentCount() == element.getSegmentCount()) { + event = new DebugContextEvent(this, fContext, type); + } else { + // if parent of the currently selected element + // changes, issue event to update STATE only + event = new DebugContextEvent(this, fContext, DebugContextEvent.STATE); + } + } + } + } + } + if (event == null) { + return; + } + if (Display.getDefault().getThread() == Thread.currentThread()) { + fire(event); + } else { + final DebugContextEvent finalEvent = event; + Display.getDefault().asyncExec(new Runnable() { + public void run() { + // verify selection is still the same context since job was scheduled + synchronized (TestDebugContextProvider.this) { + if (fContext instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) fContext; + Object changed = ((IStructuredSelection)finalEvent.getContext()).getFirstElement(); + if (!(ss.size() == 1 && ss.getFirstElement().equals(changed))) { + return; + } + } + } + fire(finalEvent); + } + }); + } + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener#modelChanged(org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta) + */ + public void modelChanged(IModelDelta delta, IModelProxy proxy) { + delta.accept(fVisitor); + } + + public void selectionChanged(SelectionChangedEvent event) { + activate(event.getSelection()); + } + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java new file mode 100644 index 000000000..fb5b04704 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Wind River Systems, Inc. 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.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.debug.internal.ui.actions.RunContextualLaunchAction; +import org.eclipse.tcf.debug.test.util.DataCallback; +import org.eclipse.tcf.protocol.IErrorReport; +import org.eclipse.tcf.protocol.IToken; +import org.eclipse.tcf.services.IRunControl; +import org.eclipse.tcf.services.IRunControl.RunControlContext; + +/** + * Run control service listener. Allows client to wait for events with a + * callback. + */ +public class TestRunControlListener implements IRunControl.RunControlListener { + + private IRunControl fRunControl; + private Throwable fError; + + private final HashMap<String, IRunControl.RunControlContext> ctx_map = new HashMap<String,IRunControl.RunControlContext>(); + private final HashMap<String, String> fSuspendedPCs = new HashMap<String, String>(); + private Map<String, List<DataCallback<String>>> waiting_suspend = new HashMap<String, List<DataCallback<String>>>(); + private String process_id; + private boolean test_done; + private String test_ctx_id; + + + public TestRunControlListener(IRunControl rc) { + fRunControl = rc; + fRunControl.addListener(this); + } + + public void dispose() throws Exception { + test_done = true; + fRunControl.removeListener(this); + } + + public void checkError() throws Exception { + if (fError != null) { + throw new Exception(fError); + } + } + + public void waitForSuspend(RunControlContext context, DataCallback<String> cb) { + final String contextId = context.getID(); + if (fSuspendedPCs.containsKey(contextId)) { + String pc = fSuspendedPCs.get(contextId); + if (pc != null) { + cb.setData(pc); + cb.done(); + return; + } else { + addWaitingForSuspend(contextId, cb); + } + } else { + getContextState(context, cb); + } + } + + private void getContextState(RunControlContext context, final DataCallback<String> cb) { + final String contextId = context.getID(); + context.getState(new IRunControl.DoneGetState() { + public void doneGetState(IToken token, Exception error, + boolean suspended, String pc, String reason, + Map<String,Object> params) { + if (error != null) { + exit(error); + } + else if (suspended) { + fSuspendedPCs.put(contextId, pc); + cb.setData(pc); + cb.done(); + } + else { + fSuspendedPCs.put(contextId, null); + if (cb != null) { + addWaitingForSuspend(contextId, cb); + } + } + } + }); + } + + public void addWaitingForSuspend(String contextId, DataCallback<String> cb) { + List<DataCallback<String>> waitingList = waiting_suspend.get(contextId); + if (waitingList == null) { + waitingList = new ArrayList<DataCallback<String>>(1); + waiting_suspend.put(contextId, waitingList); + } + waitingList.add(cb); + } + + private void exit(Throwable e) { + fError = e; + } + + public void contextAdded(RunControlContext[] contexts) { + for (IRunControl.RunControlContext ctx : contexts) { + if (ctx_map.get(ctx.getID()) != null) exit(new Error("Invalid 'contextAdded' event")); + ctx_map.put(ctx.getID(), ctx); + } + } + + public void contextChanged(RunControlContext[] contexts) { + for (IRunControl.RunControlContext ctx : contexts) { + if (ctx_map.get(ctx.getID()) == null) return; + ctx_map.put(ctx.getID(), ctx); + } + } + + public void waitSuspended(DataCallback<String> cb) { + + } + + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + ctx_map.remove(id); + if (id.equals(test_ctx_id)) { + if (test_done) { +// bp.set(null, new IBreakpoints.DoneCommand() { +// public void doneCommand(IToken token, Exception error) { +// exit(error); +// } +// }); + } + else { + exit(new Exception("Test process exited too soon")); + } + return; + } + } + } + + public void contextSuspended(String contextId, String pc, String reason, Map<String, Object> params) { + if (pc == null || "".equals(pc)) { + RunControlContext context = ctx_map.get(contextId); + if (context != null) { + getContextState(context, null); + } + return; + } + + fSuspendedPCs.put(contextId, pc); + List<DataCallback<String>> waitingList = waiting_suspend.remove(contextId); + if (waitingList != null) { + for (DataCallback<String> cb : waitingList) { + cb.setData(pc); + cb.done(); + } + } + if (test_done) { + IRunControl.RunControlContext ctx = ctx_map.get(contextId); + if (ctx != null && process_id != null && process_id.equals(ctx.getParentID())) { + ctx.resume(IRunControl.RM_RESUME, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error instanceof IErrorReport) { + int code = ((IErrorReport)error).getErrorCode(); + if (code == IErrorReport.TCF_ERROR_ALREADY_RUNNING) return; + if (code == IErrorReport.TCF_ERROR_INV_CONTEXT) return; + } + if (error != null) exit(error); + } + }); + } + } + } + + public void contextResumed(String context) { + fSuspendedPCs.put(context, null); + } + + public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, + String[] suspended_ids) + { + for (String id : suspended_ids) { + assert id != null; + contextSuspended(id, id.equals(context) ? pc : null, null, null); + } + } + + public void containerResumed(String[] resumed_ids) { + for (String id : resumed_ids) { + assert id != null; + contextResumed(id); + } + } + + public void contextException(String context, String msg) { + exit(new Exception("Context exception: " + msg)); + } + + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java new file mode 100644 index 000000000..5b21d0dc4 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Wind River - Pawel Piech - NPE when closing the Variables view (Bug 213719) + *******************************************************************************/ +package org.eclipse.tcf.debug.test; + +import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer; +import org.eclipse.debug.internal.ui.viewers.model.ViewerAdapterService; +import org.eclipse.debug.internal.ui.viewers.model.ViewerInputUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputProvider; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputRequestor; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate; + +/** + * Copied from org.eclipse.debug.internal.ui.viewers.model.provisional + * for customization. + * + * Service to compute a viewer input from a source object + * for a given presentation context. + */ +@SuppressWarnings("restriction") +public class TestViewerInputService { + + /** + * An input object which will yield a null input element. + * + * @since 3.6 + */ + public final static Object NULL_INPUT = new IViewerInputProvider() { + public void update(IViewerInputUpdate update) { + update.setInputElement(null); + update.done(); + } + }; + + // previous update request, cancelled when a new request comes in + private IViewerInputUpdate fPendingUpdate = null; + + private IViewerInputRequestor fRequestor = null; + + private ITreeModelViewer fViewer; + + private IViewerInputRequestor fProxyRequest = new IViewerInputRequestor() { + public void viewerInputComplete(final IViewerInputUpdate update) { + synchronized (TestViewerInputService.this) { + fPendingUpdate = null; + } + fRequestor.viewerInputComplete(update); + } + }; + + /** + * Constructs a viewer input service for the given requester and presentation context. + * + * @param requestor client requesting viewer inputs + * @param context context for which inputs are required + */ + public TestViewerInputService(ITreeModelViewer viewer, IViewerInputRequestor requestor) { + fRequestor = requestor; + fViewer = viewer; + } + + /** + * Resolves a viewer input derived from the given source object. + * Reports the result to the given this service's requester. A requester may be called back + * in the same or thread, or asynchronously in a different thread. Cancels any previous + * incomplete request from this service's requester. + * + * @param source source from which to derive a viewer input + */ + public void resolveViewerInput(Object source) { + IViewerInputProvider provdier = ViewerAdapterService.getInputProvider(source); + synchronized (this) { + // cancel any pending update + if (fPendingUpdate != null) { + fPendingUpdate.cancel(); + } + fPendingUpdate = new ViewerInputUpdate(fViewer.getPresentationContext(), fViewer.getInput(), fProxyRequest, source); + } + if (provdier == null) { + fPendingUpdate.setInputElement(source); + fPendingUpdate.done(); + } else { + provdier.update(fPendingUpdate); + } + } + + /** + * Disposes this viewer input service, canceling any pending jobs. + */ + public synchronized void dispose() { + if (fPendingUpdate != null) { + fPendingUpdate.cancel(); + fPendingUpdate = null; + } + } +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java new file mode 100644 index 000000000..439a34b95 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2009 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 org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputRequestor; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext; +import org.eclipse.debug.internal.ui.viewers.model.provisional.ViewerInputService; +import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer; +import org.eclipse.debug.ui.contexts.DebugContextEvent; +import org.eclipse.debug.ui.contexts.IDebugContextListener; +import org.eclipse.debug.ui.contexts.IDebugContextProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; + +/** + * + */ +@SuppressWarnings("restriction") +public class VariablesVirtualTreeModelViewer extends VirtualTreeModelViewer implements IDebugContextListener { + + private IDebugContextProvider fDebugContextProvider; + private ViewerInputService fInputService; + private boolean fActive = false; + + public VariablesVirtualTreeModelViewer(String contextId, IDebugContextProvider debugContextProvider) { + super(Display.getDefault(), SWT.NONE, new PresentationContext(contextId)); + fInputService = new ViewerInputService(this, new IViewerInputRequestor() { + + public void viewerInputComplete(IViewerInputUpdate update) { + if (!update.isCanceled()) { + setInput(update.getInputElement()); + } + } + }); + fDebugContextProvider = debugContextProvider; + debugContextProvider.addDebugContextListener(this); + } + + public void setActive(boolean active) { + if (fActive == active) { + return; + } + fActive = active; + if (fActive) { + setActiveContext(fDebugContextProvider.getActiveContext()); + } else { + fInputService.resolveViewerInput(TestViewerInputService.NULL_INPUT); + } + } + + @Override + public void dispose() { + fDebugContextProvider.removeDebugContextListener(this); + fInputService.dispose(); + super.dispose(); + } + + public void debugContextChanged(DebugContextEvent event) { + if (fActive && (event.getFlags() | DebugContextEvent.ACTIVATED) != 0) { + setActiveContext(event.getContext()); + } + } + + private void setActiveContext(ISelection selection) { + if (selection instanceof IStructuredSelection) { + fInputService.resolveViewerInput(((IStructuredSelection)selection).getFirstElement()); + } + } +} 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 new file mode 100644 index 000000000..6001fd2ab --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java @@ -0,0 +1,649 @@ +/******************************************************************************* + * Copyright (c) 2009 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.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +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.provisional.IChildrenCountUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener; +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; + +/** + * Copied from org.eclipse.cdt.tests.dsf + * + */ +@SuppressWarnings("restriction") +public class ViewerUpdatesListener + implements IViewerUpdateListener, ILabelUpdateListener, IModelChangedListener, IViewerUpdatesListenerConstants, + IStateUpdateListener +{ + private ITreeModelViewer fViewer; + + private boolean fFailOnRedundantUpdates; + private Set<IViewerUpdate> fRedundantUpdates = new HashSet<IViewerUpdate>(); + + private boolean fFailOnMultipleModelUpdateSequences; + private boolean fMultipleModelUpdateSequencesObserved; + private boolean fFailOnMultipleLabelUpdateSequences; + private boolean fMultipleLabelUpdateSequencesObserved; + + private boolean fDelayContentUntilProxyInstall; + + private Set<TreePath> fHasChildrenUpdatesScheduled = makeTreePathSet(); + private Set<IViewerUpdate> fHasChildrenUpdatesRunning = new HashSet<IViewerUpdate>(); + private Set<IViewerUpdate> fHasChildrenUpdatesCompleted = new HashSet<IViewerUpdate>(); + private Map<TreePath, Set<Integer>> fChildrenUpdatesScheduled = makeTreePathMap(); + private Set<IViewerUpdate> fChildrenUpdatesRunning = new HashSet<IViewerUpdate>(); + private Set<IViewerUpdate> fChildrenUpdatesCompleted = new HashSet<IViewerUpdate>(); + private Set<TreePath> fChildCountUpdatesScheduled = makeTreePathSet(); + private Set<IViewerUpdate> fChildCountUpdatesRunning = new HashSet<IViewerUpdate>(); + private Set<IViewerUpdate> fChildCountUpdatesCompleted = new HashSet<IViewerUpdate>(); + private Set<TreePath> fLabelUpdates = makeTreePathSet(); + private Set<IViewerUpdate> fLabelUpdatesRunning = new HashSet<IViewerUpdate>(); + private Set<IViewerUpdate> fLabelUpdatesCompleted = new HashSet<IViewerUpdate>(); + private Set<TreePath> fPropertiesUpdates = makeTreePathSet(); + private boolean fModelProxyInstalled; + private Set<TreePath> fStateUpdates = makeTreePathSet(); + private boolean fContentSequenceStarted; + private boolean fContentSequenceComplete; + private boolean fLabelUpdatesStarted; + private boolean fLabelSequenceComplete; + private boolean fModelChangedComplete; + private boolean fStateSaveStarted; + private boolean fStateSaveComplete; + private boolean fStateRestoreStarted; + private boolean fStateRestoreComplete; + private int fContentUpdatesCounter; + private int fLabelUpdatesCounter; + private int fTimeoutInterval = 60000; + private long fTimeoutTime; + + protected Set<TreePath> makeTreePathSet() { + return new HashSet<TreePath>(); + } + + protected <V> Map<TreePath, V> makeTreePathMap() { + return new HashMap<TreePath, V>(); + } + + public ViewerUpdatesListener(ITreeModelViewer viewer, boolean failOnRedundantUpdates, boolean failOnMultipleModelUpdateSequences) { + this(viewer); + setFailOnRedundantUpdates(failOnRedundantUpdates); + setFailOnMultipleModelUpdateSequences(failOnMultipleModelUpdateSequences); + } + + public ViewerUpdatesListener() { + // No viewer to register with. Client will have to register the listener manually. + } + + public ViewerUpdatesListener(ITreeModelViewer viewer) { + fViewer = viewer; + fViewer.addLabelUpdateListener(this); + fViewer.addModelChangedListener(this); + fViewer.addStateUpdateListener(this); + fViewer.addViewerUpdateListener(this); + } + + public void dispose() { + if (fViewer != null) { + fViewer.removeLabelUpdateListener(this); + fViewer.removeModelChangedListener(this); + fViewer.removeStateUpdateListener(this); + fViewer.removeViewerUpdateListener(this); + fViewer = null; + } + } + + + public void setFailOnRedundantUpdates(boolean failOnRedundantUpdates) { + fFailOnRedundantUpdates = failOnRedundantUpdates; + } + + public void setFailOnMultipleModelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) { + fFailOnMultipleModelUpdateSequences = failOnMultipleLabelUpdateSequences; + } + + public void setFailOnMultipleLabelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) { + fFailOnMultipleLabelUpdateSequences = failOnMultipleLabelUpdateSequences; + } + + /** + * Causes the content sequence started/ended notifications to be ignored + * until the model proxy is installed. This flag has to be set again after + * every reset. + * @param delay If true, listener will delay. + */ + public void setDelayContentUntilProxyInstall(boolean delay) { + fDelayContentUntilProxyInstall = delay; + } + + /** + * Sets the the maximum amount of time (in milliseconds) that the update listener + * is going to wait. If set to -1, the listener will wait indefinitely. + */ + public void setTimeoutInterval(int milis) { + fTimeoutInterval = milis; + } + + public void reset() { + fRedundantUpdates.clear(); + fMultipleLabelUpdateSequencesObserved = false; + fMultipleModelUpdateSequencesObserved = false; + fHasChildrenUpdatesScheduled.clear(); + fHasChildrenUpdatesRunning.clear(); + fHasChildrenUpdatesCompleted.clear(); + fChildrenUpdatesScheduled.clear(); + fChildrenUpdatesRunning.clear(); + fChildrenUpdatesCompleted.clear(); + fChildCountUpdatesScheduled.clear(); + fChildCountUpdatesRunning.clear(); + fChildCountUpdatesCompleted.clear(); + fLabelUpdates.clear(); + fLabelUpdatesRunning.clear(); + fLabelUpdatesCompleted.clear(); + fModelProxyInstalled = false; + fContentSequenceStarted = false; + fContentSequenceComplete = false; + fLabelUpdatesStarted = false; + fLabelSequenceComplete = false; + fStateSaveStarted = false; + fStateSaveComplete = false; + fStateRestoreStarted = false; + fStateRestoreComplete = false; + fDelayContentUntilProxyInstall = false; + + fTimeoutTime = System.currentTimeMillis() + fTimeoutInterval; + resetModelChanged(); + } + + public void resetModelChanged() { + fModelChangedComplete = false; + } + + public void addHasChildrenUpdate(TreePath path) { + fHasChildrenUpdatesScheduled.add(path); + } + + public void removeHasChildrenUpdate(TreePath path) { + fHasChildrenUpdatesScheduled.remove(path); + } + + public void addChildCountUpdate(TreePath path) { + fChildCountUpdatesScheduled.add(path); + } + + public void removeChildreCountUpdate(TreePath path) { + fChildCountUpdatesScheduled.remove(path); + } + + public void addChildreUpdate(TreePath path, int index) { + Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path); + if (childrenIndexes == null) { + childrenIndexes = new TreeSet<Integer>(); + fChildrenUpdatesScheduled.put(path, childrenIndexes); + } + childrenIndexes.add(new Integer(index)); + } + + public void removeChildrenUpdate(TreePath path, int index) { + Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path); + if (childrenIndexes != null) { + childrenIndexes.remove(new Integer(index)); + if (childrenIndexes.isEmpty()) { + fChildrenUpdatesScheduled.remove(path); + } + } + } + + public void addLabelUpdate(TreePath path) { + fLabelUpdates.add(path); + } + + public void addPropertiesUpdate(TreePath path) { + fPropertiesUpdates.add(path); + } + + public void removeLabelUpdate(TreePath path) { + fLabelUpdates.remove(path); + } + + public void addStateUpdate(TreePath path) { + fStateUpdates.add(path); + } + + public void removeStateUpdate(TreePath path) { + fStateUpdates.remove(path); + } + + + public boolean isFinished() { + return isFinished(ALL_UPDATES_COMPLETE); + } + + public boolean isTimedOut() { + return fTimeoutInterval > 0 && fTimeoutTime < System.currentTimeMillis(); + } + + public void waitTillFinished(int flags) throws InterruptedException { + synchronized(this) { + while(!isFinished(flags)) { + wait(100); + } + } + } + + public boolean isFinished(int flags) { +// if (isTimedOut()) { +// throw new RuntimeException("Timed Out: " + toString(flags)); +// } + + if (fFailOnRedundantUpdates && !fRedundantUpdates.isEmpty()) { + Assert.fail("Redundant Updates: " + fRedundantUpdates.toString()); + } + if (fFailOnMultipleLabelUpdateSequences && !fMultipleLabelUpdateSequencesObserved) { + Assert.fail("Multiple label update sequences detected"); + } + if (fFailOnMultipleModelUpdateSequences && fMultipleModelUpdateSequencesObserved) { + Assert.fail("Multiple viewer update sequences detected"); + } + + if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) { + if (!fLabelSequenceComplete) return false; + } + if ( (flags & LABEL_SEQUENCE_STARTED) != 0) { + if (!fLabelUpdatesStarted) return false; + } + if ( (flags & LABEL_UPDATES) != 0) { + if (!fLabelUpdates.isEmpty()) return false; + } + if ( (flags & CONTENT_SEQUENCE_STARTED) != 0) { + if (!fContentSequenceStarted) return false; + } + if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) { + if (!fContentSequenceComplete) return false; + } + if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) { + if (fHasChildrenUpdatesRunning.isEmpty() && fHasChildrenUpdatesCompleted.isEmpty()) return false; + } + if ( (flags & HAS_CHILDREN_UPDATES) != 0) { + if (!fHasChildrenUpdatesScheduled.isEmpty()) return false; + } + if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) { + if (fChildCountUpdatesRunning.isEmpty() && fChildCountUpdatesCompleted.isEmpty()) return false; + } + if ( (flags & CHILD_COUNT_UPDATES) != 0) { + if (!fChildCountUpdatesScheduled.isEmpty()) return false; + } + if ( (flags & CHILDREN_UPDATES_STARTED) != 0) { + if (fChildrenUpdatesRunning.isEmpty() && fChildrenUpdatesCompleted.isEmpty()) return false; + } + if ( (flags & CHILDREN_UPDATES) != 0) { + if (!fChildrenUpdatesScheduled.isEmpty()) return false; + } + if ( (flags & MODEL_CHANGED_COMPLETE) != 0) { + if (!fModelChangedComplete) return false; + } + if ( (flags & STATE_SAVE_COMPLETE) != 0) { + if (!fStateSaveComplete) return false; + } + if ( (flags & STATE_SAVE_STARTED) != 0) { + if (!fStateSaveStarted) return false; + } + if ( (flags & STATE_RESTORE_COMPLETE) != 0) { + if (!fStateRestoreComplete) return false; + } + if ( (flags & STATE_RESTORE_STARTED) != 0) { + if (!fStateRestoreStarted) return false; + } + if ( (flags & MODEL_PROXIES_INSTALLED) != 0) { + if (!fModelProxyInstalled) return false; + } + if ( (flags & VIEWER_UPDATES_RUNNING) != 0) { + if (fContentUpdatesCounter != 0) { + return false; + } + } + if ( (flags & LABEL_UPDATES_RUNNING) != 0) { + if (fLabelUpdatesCounter != 0) { + return false; + } + } + + return true; + } + + public synchronized void updateStarted(IViewerUpdate update) { + synchronized (this) { + fContentUpdatesCounter++; + if (update instanceof IHasChildrenUpdate) { + fHasChildrenUpdatesRunning.add(update); + } if (update instanceof IChildrenCountUpdate) { + fChildCountUpdatesRunning.add(update); + } else if (update instanceof IChildrenUpdate) { + fChildCountUpdatesRunning.add(update); + } + } + notifyAll(); + } + + public synchronized void updateComplete(IViewerUpdate update) { + synchronized (this) { + fContentUpdatesCounter--; + } + + if (!update.isCanceled()) { + if (update instanceof IHasChildrenUpdate) { + fHasChildrenUpdatesRunning.remove(update); + fHasChildrenUpdatesCompleted.add(update); + if (!fHasChildrenUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) { + fRedundantUpdates.add(update); + } + } if (update instanceof IChildrenCountUpdate) { + fChildCountUpdatesRunning.remove(update); + fChildCountUpdatesCompleted.add(update); + if (!fChildCountUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) { + fRedundantUpdates.add(update); + } + } else if (update instanceof IChildrenUpdate) { + fChildrenUpdatesRunning.remove(update); + fChildrenUpdatesCompleted.add(update); + + int start = ((IChildrenUpdate)update).getOffset(); + int end = start + ((IChildrenUpdate)update).getLength(); + + Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(update.getElementPath()); + if (childrenIndexes != null) { + for (int i = start; i < end; i++) { + childrenIndexes.remove(new Integer(i)); + } + if (childrenIndexes.isEmpty()) { + fChildrenUpdatesScheduled.remove(update.getElementPath()); + } + } else if (fFailOnRedundantUpdates) { + fRedundantUpdates.add(update); + } + } + } + notifyAll(); + } + + public synchronized void viewerUpdatesBegin() { + if (fDelayContentUntilProxyInstall && !fModelProxyInstalled) return; + if (fFailOnMultipleModelUpdateSequences && fContentSequenceComplete) { + fMultipleModelUpdateSequencesObserved = true; + } + fContentSequenceStarted = true; + notifyAll(); + } + + public synchronized void viewerUpdatesComplete() { + if (fDelayContentUntilProxyInstall && !fModelProxyInstalled) return; + fContentSequenceComplete = true; + notifyAll(); + } + + public synchronized void labelUpdateComplete(ILabelUpdate update) { + fLabelUpdatesRunning.remove(update); + fLabelUpdatesCompleted.add(update); + fLabelUpdatesCounter--; + if (!fLabelUpdates.remove(update.getElementPath()) && fFailOnRedundantUpdates) { + fRedundantUpdates.add(update); + } + notifyAll(); + } + + public synchronized void labelUpdateStarted(ILabelUpdate update) { + fLabelUpdatesRunning.add(update); + fLabelUpdatesCounter++; + notifyAll(); + } + + public synchronized void labelUpdatesBegin() { + if (fFailOnMultipleLabelUpdateSequences && fLabelSequenceComplete) { + fMultipleLabelUpdateSequencesObserved = true; + } + fLabelUpdatesStarted = true; + notifyAll(); + } + + public synchronized void labelUpdatesComplete() { + fLabelSequenceComplete = true; + notifyAll(); + } + + public synchronized void modelChanged(IModelDelta delta, IModelProxy proxy) { + fModelChangedComplete = true; + + if (!fModelProxyInstalled) { + delta.accept(new IModelDeltaVisitor() { + public boolean visit(IModelDelta delta, int depth) { + if ((delta.getFlags() & IModelDelta.EXPAND) != 0) { + fModelProxyInstalled = true; + return false; + } + return true; + } + }); + } + notifyAll(); + } + + public synchronized void stateRestoreUpdatesBegin(Object input) { + fStateRestoreStarted = true; + notifyAll(); + } + + public synchronized void stateRestoreUpdatesComplete(Object input) { + fStateRestoreComplete = true; + notifyAll(); + } + + public synchronized void stateSaveUpdatesBegin(Object input) { + fStateSaveStarted = true; + notifyAll(); + } + + public synchronized void stateSaveUpdatesComplete(Object input) { + fStateSaveComplete = true; + notifyAll(); + } + + public void stateUpdateComplete(Object input, IViewerUpdate update) { + } + + public void stateUpdateStarted(Object input, IViewerUpdate update) { + } + + private String toString(int flags) { + StringBuffer buf = new StringBuffer("Viewer Update Listener"); + + if (fFailOnRedundantUpdates) { + buf.append("\n\t"); + buf.append("fRedundantUpdates = "); + buf.append( toStringViewerUpdatesSet(fRedundantUpdates) ); + } + if (fFailOnMultipleLabelUpdateSequences) { + buf.append("\n\t"); + buf.append("fMultipleLabelUpdateSequencesObserved = " + fMultipleLabelUpdateSequencesObserved); + } + if (fFailOnMultipleModelUpdateSequences) { + buf.append("\n\t"); + buf.append("fMultipleModelUpdateSequencesObserved = " + fMultipleModelUpdateSequencesObserved); + } + if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) { + buf.append("\n\t"); + buf.append("fLabelSequenceComplete = " + fLabelSequenceComplete); + } + if ( (flags & LABEL_UPDATES_RUNNING) != 0) { + buf.append("\n\t"); + buf.append("fLabelUpdatesRunning = " + fLabelUpdatesCounter); + } + if ( (flags & LABEL_SEQUENCE_STARTED) != 0) { + buf.append("\n\t"); + buf.append("fLabelUpdatesRunning = "); + buf.append( toStringViewerUpdatesSet(fLabelUpdatesRunning) ); + buf.append("\n\t"); + buf.append("fLabelUpdatesCompleted = "); + buf.append( toStringViewerUpdatesSet(fLabelUpdatesCompleted) ); + } + if ( (flags & LABEL_UPDATES) != 0) { + buf.append("\n\t"); + buf.append("fLabelUpdates = "); + buf.append( toString(fLabelUpdates) ); + } + if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) { + buf.append("\n\t"); + buf.append("fContentSequenceComplete = " + fContentSequenceComplete); + } + if ( (flags & VIEWER_UPDATES_RUNNING) != 0) { + buf.append("\n\t"); + buf.append("fContentUpdatesCounter = " + fContentUpdatesCounter); + } + if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) { + buf.append("\n\t"); + buf.append("fHasChildrenUpdatesRunning = "); + buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesRunning) ); + buf.append("\n\t"); + buf.append("fHasChildrenUpdatesCompleted = "); + buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesCompleted) ); + } + if ( (flags & HAS_CHILDREN_UPDATES) != 0) { + buf.append("\n\t"); + buf.append("fHasChildrenUpdates = "); + buf.append( toString(fHasChildrenUpdatesScheduled) ); + } + if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) { + buf.append("\n\t"); + buf.append("fChildCountUpdatesRunning = "); + buf.append( toStringViewerUpdatesSet(fChildCountUpdatesRunning) ); + buf.append("\n\t"); + buf.append("fChildCountUpdatesCompleted = "); + buf.append( toStringViewerUpdatesSet(fChildCountUpdatesCompleted) ); + } + if ( (flags & CHILD_COUNT_UPDATES) != 0) { + buf.append("\n\t"); + buf.append("fChildCountUpdates = "); + buf.append( toString(fChildCountUpdatesScheduled) ); + } + if ( (flags & CHILDREN_UPDATES_STARTED) != 0) { + buf.append("\n\t"); + buf.append("fChildrenUpdatesRunning = "); + buf.append( fChildrenUpdatesRunning ); + buf.append("\n\t"); + buf.append("fChildrenUpdatesCompleted = "); + buf.append( toStringViewerUpdatesSet(fChildrenUpdatesCompleted) ); + } + if ( (flags & CHILDREN_UPDATES) != 0) { + buf.append("\n\t"); + buf.append("fChildrenUpdates = "); + buf.append( toStringTreePathMap(fChildrenUpdatesScheduled) ); + } + if ( (flags & MODEL_CHANGED_COMPLETE) != 0) { + buf.append("\n\t"); + buf.append("fModelChangedComplete = " + fModelChangedComplete); + } + if ( (flags & STATE_SAVE_COMPLETE) != 0) { + buf.append("\n\t"); + buf.append("fStateSaveComplete = " + fStateSaveComplete); + } + if ( (flags & STATE_RESTORE_COMPLETE) != 0) { + buf.append("\n\t"); + buf.append("fStateRestoreComplete = " + fStateRestoreComplete); + } + if ( (flags & MODEL_PROXIES_INSTALLED) != 0) { + buf.append("\n\t"); + buf.append("fModelProxyInstalled = " + fModelProxyInstalled); + } + if (fTimeoutInterval > 0) { + buf.append("\n\t"); + buf.append("fTimeoutInterval = " + fTimeoutInterval); + } + return buf.toString(); + } + + private String toString(Set<TreePath> set) { + if (set.isEmpty()) { + return "(EMPTY)"; + } + StringBuffer buf = new StringBuffer(); + for (Iterator<TreePath> itr = set.iterator(); itr.hasNext(); ) { + buf.append("\n\t\t"); + buf.append(toStringTreePath(itr.next())); + } + return buf.toString(); + } + + private String toStringViewerUpdatesSet(Set<IViewerUpdate> set) { + if (set.isEmpty()) { + return "(EMPTY)"; + } + StringBuffer buf = new StringBuffer(); + for (Iterator<IViewerUpdate> itr = set.iterator(); itr.hasNext(); ) { + buf.append("\n\t\t"); + buf.append(toStringTreePath((itr.next()).getElementPath())); + } + return buf.toString(); + } + + private String toStringTreePathMap(Map<TreePath, Set<Integer>> map) { + if (map.isEmpty()) { + return "(EMPTY)"; + } + StringBuffer buf = new StringBuffer(); + for (Iterator<TreePath> itr = map.keySet().iterator(); itr.hasNext(); ) { + buf.append("\n\t\t"); + TreePath path = itr.next(); + buf.append(toStringTreePath(path)); + Set<?> updates = map.get(path); + buf.append(" = "); + buf.append(updates.toString()); + } + return buf.toString(); + } + + private String toStringTreePath(TreePath path) { + if (path.getSegmentCount() == 0) { + return "/"; + } + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < path.getSegmentCount(); i++) { + buf.append("/"); + buf.append(path.getSegment(i)); + } + return buf.toString(); + } + + @Override + public String toString() { + return toString(ALL_UPDATES_COMPLETE | MODEL_CHANGED_COMPLETE | STATE_RESTORE_COMPLETE | + VIEWER_UPDATES_STARTED | LABEL_SEQUENCE_STARTED); + } +} + + diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java new file mode 100644 index 000000000..a06fd6789 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2009 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.Iterator; +import java.util.regex.Pattern; + +import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem; +import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer; + +/** + * Extends base listener to use virtual viewer capabilities. + */ +public class VirtualViewerUpdatesListener extends ViewerUpdatesListener { + private final VirtualTreeModelViewer fVirtualViewer; + + public VirtualViewerUpdatesListener(VirtualTreeModelViewer viewer) { + super(viewer, false, false); + fVirtualViewer = viewer; + } + + public VirtualItem findElement(Pattern[] patterns) { + return findElement(fVirtualViewer.getTree(), patterns); + } + + public VirtualItem findElement(VirtualItem parent, Pattern[] patterns) { + VirtualItem item = parent; + patterns: for (int i = 0; i < patterns.length; i++) { + for (VirtualItem child : item.getItems()) { + String[] label = (String[])child.getData(VirtualItem.LABEL_KEY); + if (label != null && label.length >= 1 && label[0] != null && patterns[i].matcher(label[0]).matches()) { + item = child; + continue patterns; + } + } + return null; + } + return item; + } + + +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java new file mode 100644 index 000000000..1e3281d1a --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * 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.util; + +import java.util.List; + +/** + * Copied and adapted from org.eclipse.cdt.dsf.concurrent. + * + * A request monitor that is used for multiple activities. We are told the + * number of activities that constitutes completion. When our {@link #done()} is + * called that many times, the request is considered complete. + * + * The usage is as follows: <code><pre> + * final CountingRequestMonitor countingRm = new CountingRequestMonitor(fExecutor, null) { + * public void handleCompleted() { + * System.out.println("All complete, errors=" + !getStatus().isOK()); + * } + * }; + * + * int count = 0; + * for (int i : elements) { + * service.call(i, countingRm); + * count++; + * } + * + * countingRm.setDoneCount(count); + * </pre></code> + * + * @since 1.0 + */ +public class AggregateCallback extends Callback { + + /** + * Counter tracking the remaining number of times that the done() method + * needs to be called before this request monitor is actually done. + */ + private int fDoneCounter; + + /** + * Flag indicating whether the initial count has been set on this monitor. + */ + private boolean fInitialCountSet = false; + + public AggregateCallback(Callback parentCallback) { + super(parentCallback); + } + + /** + * Sets the number of times that this request monitor needs to be called + * before this monitor is truly considered done. This method must be called + * exactly once in the life cycle of each counting request monitor. + * @param count Number of times that done() has to be called to mark the request + * monitor as complete. If count is '0', then the counting request monitor is + * marked as done immediately. + */ + public synchronized void setDoneCount(int count) { + assert !fInitialCountSet; + fInitialCountSet = true; + fDoneCounter += count; + if (fDoneCounter <= 0) { + assert fDoneCounter == 0; // Mismatch in the count. + super.done(); + } + } + + /** + * Called to indicate that one of the calls using this monitor is finished. + * Only when we've been called the number of times corresponding to the + * completion count will this request monitor will be considered complete. + * This method can be called before {@link #setDoneCount(int)}; in that + * case, we simply bump the count that tracks the number of times we've been + * called. The monitor will not move into the completed state until after + * {@link #setDoneCount(int)} has been called (or during if the given + * completion count is equal to the number of times {@link #done()} has + * already been called.) + */ + @Override + public synchronized void done() { + fDoneCounter--; + if (fInitialCountSet && fDoneCounter <= 0) { + assert fDoneCounter == 0; // Mismatch in the count. + super.done(); + } + } + + @Override + public String toString() { + return "AggregateError: " + getError().toString(); //$NON-NLS-1$ + } + + @Override + public synchronized void setError(Throwable error) { + if ((getError() == null)) { + setError(new AggregateError("") { + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + StringBuffer message = new StringBuffer(); + List<Throwable> children = getChildren(); + for (int i = 0; i < children.size(); i++) { + message.append(children.get(i).getMessage()); + if (i + 1 < children.size()) { + message.append(" ,"); //$NON-NLS-1$ + } + } + return message.toString(); + } + }); + } + + + if ((getError() instanceof AggregateError)) { + ((AggregateError)getError()).add(error); + } + }; +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java new file mode 100644 index 000000000..a358f8523 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2008, 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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Exception that combines several exceptions for propagation to client. + */ +public class AggregateError extends Exception { + private static final long serialVersionUID = 1L; + + final private List<Throwable> fChildren = Collections.synchronizedList(new ArrayList<Throwable>(1)); + + public AggregateError(String message) { + super(message); + } + + public void add(Throwable child) { + boolean initCause = false; + synchronized(fChildren) { + if (fChildren.isEmpty()) { + initCause = true; + } + fChildren.add(child); + } + if (initCause) { + super.initCause(child); + } + } + + public List<Throwable> getChildren() { + return fChildren; + } +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java new file mode 100644 index 000000000..78c7e47bc --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java @@ -0,0 +1,424 @@ +/******************************************************************************* + * 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.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; + +import org.eclipse.tcf.protocol.Protocol; + +/** + * Copied and apdapted from org.eclipse.cdt.dsf.concurrent. + * + * Used to monitor the result of an asynchronous request. Because of the + * asynchronous nature of DSF code, a very large number of methods needs to + * signal the result of an operation through a call-back. This class is the base + * class for such call backs. + * <p> + * The intended use of this class, is that a client who is calling an asynchronous + * method, will sub-class Callback, and implement the method {@link #handleCompleted()}, + * or any of the other <code>handle...</code> methods, in order to interpret the + * results of the request. The object implementing the asynchronous method is required + * to call the {@link #done()} method on the request monitor object that it received + * as an argument. + * </p> + * <p> + * The error the returned by #getError() can be used to + * determine the success or failure of the asynchronous operation. By convention + * the error value returned by asynchronous method should be interpreted as follows: + * <ul> + * <li><code>null</code> - Result is a success. In DataCallback, getData() should + * return a value.</li> + * <li>non-<code>null</code> - An error condition that should probably be reported + * to the user.</li> + * <li><code>CancellationException</code> - The request was canceled, and the + * asynchronous method was not completed.</li> + * </ul> + * </p> + * <p> + * The Callback constructor accepts an optional "parent" request monitor. If a + * parent monitor is specified, it will automatically be invoked by this monitor when + * the request is completed. The parent option is useful when implementing a method + * which is asynchronous (and accepts a request monitor as an argument) and which itself + * calls another asynchronous method to complete its operation. For example, in the + * request monitor implementation below, the implementation only needs to override + * <code>handleSuccess()</code>, because the base implementation will handle notifying the + * parent <code>rm</code> in case the <code>getIngredients()</code> call fails. + * <pre> + * public void createCupCakes(final DataCallback<CupCake[]> rm) { + * getIngredients(new DataCallback<Ingredients>(fExecutor, rm) { + * public void handleSuccess() { + * rm.setData( new CupCake(getData().getFlour(), getData().getSugar(), + * getData().getBakingPowder())); + * rm.done(); + * } + * }); + * } + * </pre> + * </p> + * + * @since 1.0 + */ +public class Callback { + + /** + * Interface used by Callback to notify when a given request monitor + * is canceled. + * + * @see Callback + */ + public static interface ICanceledListener { + + /** + * Called when the given request monitor is canceled. + */ + public void requestCanceled(Callback rm); + } + + /** + * The request monitor which was used to call into the method that created this + * monitor. + */ + private final Callback fParentCallback; + + private List<ICanceledListener> fCancelListeners; + + /** + * Status + */ + private Throwable fError = null; + private boolean fCanceled = false; + private boolean fDone = false; + + private final ICanceledListener fCanceledListener; + + /** + * This field is never read by any code; its purpose is strictly to assist + * developers debug DPF code. Developer can select this field in the + * Variables view and see a monitor backtrace in the details pane. See + * {@link DsfExecutable#DEBUG_MONITORS}. + * + * <p> + * This field is set only when tracing is enabled. + */ + @SuppressWarnings("unused") + private String fMonitorBacktrace; + + public Callback() { + this(null); + } + + /** + * Constructor with an optional parent monitor. + * + * @param executor + * This executor will be used to invoke the runnable that will + * allow processing the completion code of this request monitor. + * I.e., the runnable will call {@link #handleCompleted()}. + * @param parentCallback + * An optional parent request monitor. By default, our completion + * handlers invoke the parent monitor's <code>done</code> method, + * thus allowing monitors to be daisy chained. If this request is + * unsuccessful, its status is set into the parent monitor. + * Parameter may be null. + */ + public Callback(Callback parentCallback) { + fParentCallback = parentCallback; + + // If the parent rm is not null, add ourselves as a listener so that + // this request monitor will automatically be canceled when the parent + // is canceled. + if (fParentCallback != null) { + fCanceledListener = new ICanceledListener() { + public void requestCanceled(Callback rm) { + cancel(); + } + }; + + fParentCallback.addCancelListener(fCanceledListener); + } else { + fCanceledListener = null; + } + } + + /** + * Sets the status of the result of the request. If status is OK, this + * method does not need to be called. + */ + public synchronized void setError(Throwable error) { + fError = error; + } + + /** Returns the status of the completed method. */ + public synchronized Throwable getError() { + if (isCanceled()) { + return new CancellationException(); + } + return fError; + } + + /** + * Sets this request monitor as canceled and calls the cancel listeners if + * any. + * <p> + * Note: Calling cancel() does not automatically complete the + * Callback. The asynchronous call still has to call done(). + * </p> + * <p> + * Note: logically a request should only be canceled by the client that + * issued the request in the first place. After a request is canceled, the + * method that is fulfilling the request may call + * {@link #setError(Throwable)} with <code>CancelledException</code> + * to indicate that it recognized that the given request was canceled and it + * did not perform the given operation. + * </p> + * <p> + * Canceling a monitor effectively cancels all descendant monitors, by + * virtue of the default implementation of {@link #isCanceled()}, which + * checks not only its own state but that of its parent. However, only the + * cancel listeners of the monitor directly canceled will be called. + * </p> + */ + public void cancel() { + ICanceledListener[] listeners = null; + synchronized (this) { + // Check to make sure the request monitor wasn't previously canceled. + if (!fCanceled) { + fCanceled = true; + if (fCancelListeners != null) { + listeners = fCancelListeners.toArray(new ICanceledListener[fCancelListeners.size()]); + } + } + } + + // Call the listeners outside of a synchronized section to reduce the + // risk of deadlocks. + if (listeners != null) { + for (ICanceledListener listener : listeners) { + listener.requestCanceled(this); + } + } + } + + /** + * Returns whether the request was canceled. Even if the request is + * canceled by the client, the implementor handling the request should + * still call {@link #done()} in order to complete handling + * of the request monitor. + * + * <p> + * A request monitor is considered canceled if either it or its parent was canceled. + * </p> + */ + public boolean isCanceled() { + boolean canceled = false; + + // Avoid holding onto this lock while calling parent RM, which may + // acquire other locks (bug 329488). + synchronized(this) { + canceled = fCanceled; + } + return canceled || (fParentCallback != null && fParentCallback.isCanceled()); + } + + /** + * Adds the given listener to list of listeners that are notified when this + * request monitor is directly canceled. + */ + public synchronized void addCancelListener(ICanceledListener listener) { + if (fCancelListeners == null) { + fCancelListeners = new ArrayList<ICanceledListener>(1); + } + fCancelListeners.add(listener); + } + + /** + * Removes the given listener from the list of listeners that are notified + * when this request monitor is directly canceled. + */ + public synchronized void removeCancelListener(ICanceledListener listener) { + if (fCancelListeners != null) { + fCancelListeners.remove(listener); + } + } + + /** + * Marks this request as completed. Once this method is called, the + * monitor submits a runnable to the DSF Executor to call the + * <code>handle...</code> methods. + * <p> + * Note: This method should be called once and only once, for every request + * issued. Even if the request was canceled. + * </p> + */ + public synchronized void done() { + if (fDone) { + throw new IllegalStateException("Callback: " + this + ", done() method called more than once"); //$NON-NLS-1$//$NON-NLS-2$ + } + fDone = true; + + // This Callback is done, it can no longer be canceled. + // We must clear the list of cancelListeners because it causes a + // circular reference between parent and child Callback, which + // causes a memory leak. + fCancelListeners = null; + + if (fParentCallback != null) { + fParentCallback.removeCancelListener(fCanceledListener); + } + + try { + Protocol.invokeLater(new Runnable() { + public void run() { + Callback.this.handleCompleted(); + } + @Override + public String toString() { + return "Completed: " + Callback.this.toString(); //$NON-NLS-1$ + } + }); + } catch (IllegalStateException e) { + handleEventQueueShutDown(e); + } + } + + @Override + public String toString() { + return "Callback (" + super.toString() + "): " + getError(); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Checks whether the given request monitor completed with success or + * failure result. If the request monitor was canceled it is considered a + * failure, regardless of the status. If the status has a severity higher + * than INFO (i.e., WARNING, ERROR or CANCEL), it is considered a failure. + */ + public synchronized boolean isSuccess() { + return !isCanceled() && getError() == null ; + } + + /** + * First tier handler for the completion of the request. By default, the + * {@link #done()} method drives this method on the executor specified at + * construction time. By default, this handler merely calls a more + * specialized handler, which in turn may call an even more specialized + * handler, and so on, thus giving a subclass the ability to + * compartmentalize its completion logic by overriding specific handlers. + * All handlers are named <code>handleXxxxx</code>. More specifically, the + * base implementation calls {@link #handleSuccess()} if the request + * succeeded, and calls {@link #handleFailure()} otherwise. <br> + * + * The complete hierarchy of handlers is as follows: <br> + * <pre> + * + handleCompleted + * - handleSuccess + * + handleFailure + * - handleCancel + * + handleErrororWarning + * - handleError + * - handleWarning + * </pre> + * + * <p> + * Note: Sub-classes may override this method. + */ + protected void handleCompleted() { + if (isSuccess()) { + handleSuccess(); + } else { + handleFailure(); + } + } + + /** + * Default handler for a successful the completion of a request. If this + * monitor has a parent monitor that was configured by the constructor, that + * parent monitor is notified. Otherwise this method does nothing. + * {@link #handleFailure()} or cancel otherwise. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleSuccess() { + if (fParentCallback != null) { + fParentCallback.done(); + } + } + + /** + * The default implementation of a cancellation or an error result of a + * request. The implementation delegates to {@link #handleCancel()} and + * {@link #handleErrorOrWarning()} as needed. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleFailure() { + assert !isSuccess(); + + if (isCanceled()) { + handleCancel(); + } else { + handleError(); + } + } + + /** + * The default implementation of an error result of a request. If this + * monitor has a parent monitor that was configured by the constructor, that + * parent monitor is configured with a new status containing this error. + * Otherwise the error is logged. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleError() { + if (fParentCallback != null) { + fParentCallback.setError(getError()); + fParentCallback.done(); + } else { + Protocol.log("Unhandled error in callback " + toString(), getError()); + } + } + + + /** + * Default completion handler for a canceled request. If this monitor was + * constructed with a parent monitor, the status is propagated up to it. + * Otherwise this method does nothing. <br> + * Note: Sub-classes may override this method. + */ + protected void handleCancel() { + if (fParentCallback != null) { + if (getError() instanceof CancellationException && !fParentCallback.isCanceled()) { + Protocol.log("Sub-request " + toString() + " was canceled and not handled.'", getError()); + } else { + fParentCallback.setError(getError()); + } + fParentCallback.done(); + } + } + + /** + * Default handler for when the executor supplied in the constructor + * rejects the runnable that is submitted invoke this request monitor. + * This usually happens only when the executor is shutting down. + * <p> + * The default handler creates a new error status for the rejected + * execution and propagates it to the client or logs it. + */ + protected void handleEventQueueShutDown(IllegalStateException e) { + if (fParentCallback != null) { + fParentCallback.setError(e); + fParentCallback.done(); + } else { + Protocol.log("In callback " + toString() + ", unhandled event queue shut down.", e); + } + } +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java new file mode 100644 index 000000000..e9822fa16 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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; + + + +/** + * Copied and apdapted from org.eclipse.cdt.dsf.concurrent. + * + * Request monitor that allows data to be returned to the request initiator. + * + * @param V The type of the data object that this monitor handles. + * + * @since 1.0 + */ +public class DataCallback<V> extends Callback { + + /** Data object reference */ + private V fData; + + public DataCallback() { + this(null); + } + + public DataCallback(Callback parentCallback) { + super(parentCallback); + } + + /** + * Sets the data object to specified value. To be called by the + * asynchronous method implementor. + * @param data Data value to set. + */ + public synchronized void setData(V data) { fData = data; } + + /** + * Returns the data value, null if not set. + */ + public synchronized V getData() { return fData; } + + @Override + public String toString() { + if (getData() != null) { + return getData().toString(); + } else { + return super.toString(); + } + } +} diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java new file mode 100644 index 000000000..b75b74627 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * 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.util; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.tcf.protocol.Protocol; + + +/** + * Copied and adapted from org.eclipse.cdt.dsf.concurrent. + * + * A convenience class that allows a client to retrieve data from services + * synchronously from a non-dispatch thread. This class is different from + * a Callable<V> in that it allows the implementation code to calculate + * the result in several dispatches, rather than requiring it to return the + * data at end of Callable#call method. + * <p> + * Usage:<br/> + * <pre> + * class DataQuery extends Query<Data> { + * protected void execute(DataCallback<Data> callback) { + * callback.setData(fSlowService.getData()); + * callback.done(); + * } + * } + * + * DsfExecutor executor = getExecutor(); + * DataQuery query = new DataQuery(); + * executor.submit(query); + * + * try { + * Data data = query.get(); + * } + * + * </pre> + * <p> + * @see java.util.concurrent.Callable + * + */ +abstract public class Query<V> implements Future<V> +{ + private class QueryCallback extends DataCallback<V> { + + boolean fExecuted = false; + + boolean fCompleted = false; + + private QueryCallback() { + super(null); + } + + @Override + public synchronized void handleCompleted() { + fCompleted = true; + notifyAll(); + } + + public synchronized boolean isCompleted() { + return fCompleted; + } + + public synchronized boolean setExecuted() { + if (fExecuted || isCanceled()) { + // already executed or canceled + return false; + } + fExecuted = true; + return true; + } + }; + + private final QueryCallback fCallback = new QueryCallback(); + + /** + * The no-argument constructor + */ + public Query() {} + + public V get() throws InterruptedException, ExecutionException { + invoke(); + Throwable error; + V data; + synchronized (fCallback) { + while (!isDone()) { + fCallback.wait(); + } + error = fCallback.getError(); + data = fCallback.getData(); + } + + if (error instanceof CancellationException) { + throw new CancellationException(); + } else if (error != null) { + throw new ExecutionException(error); + } + return data; + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + invoke(); + + long timeLeft = unit.toMillis(timeout); + long timeoutTime = System.currentTimeMillis() + unit.toMillis(timeout); + + Throwable error; + V data; + synchronized (fCallback) { + while (!isDone()) { + if (timeLeft <= 0) { + throw new TimeoutException(); + } + fCallback.wait(timeLeft); + timeLeft = timeoutTime - System.currentTimeMillis(); + } + error = fCallback.getError(); + data = fCallback.getData(); + } + + if (error instanceof CancellationException) { + throw new CancellationException(); + } else if (error != null) { + throw new ExecutionException(error); + } + return data; + } + + /** + * Don't try to interrupt the DSF executor thread, just ignore the request + * if set. + */ + public boolean cancel(boolean mayInterruptIfRunning) { + boolean completed = false; + synchronized (fCallback) { + completed = fCallback.isCompleted(); + if (!completed) { + fCallback.cancel(); + fCallback.notifyAll(); + } + } + return !completed; + } + + public boolean isCancelled() { return fCallback.isCanceled(); } + + public boolean isDone() { + synchronized (fCallback) { + // If future is canceled, return right away. + return fCallback.isCompleted() || fCallback.isCanceled(); + } + } + + abstract protected void execute(DataCallback<V> callback); + + public void invoke() { + Protocol.invokeLater(new Runnable() { + public void run() { + if (fCallback.setExecuted()) { + execute(fCallback); + } + } + }); + } +} + diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java new file mode 100644 index 000000000..8af55fa85 --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009 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.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.tcf.protocol.Protocol; + +/** + * Future implementation that executes a call on the TCF Protocol + * thread. + */ +public abstract class Task<V> implements Future<V> , Callable<V> { + + private final AtomicBoolean fInvoked = new AtomicBoolean(false); + private final FutureTask<V> fInternalFT = new FutureTask<V>(this); + + public boolean cancel(boolean mayInterruptIfRunning) { + return fInternalFT.cancel(mayInterruptIfRunning); + } + + public boolean isCancelled() { + return fInternalFT.isCancelled(); + } + + public boolean isDone() { + return fInternalFT.isDone(); + } + + public V get() throws InterruptedException, ExecutionException { + invoke(); + return fInternalFT.get(); + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + invoke(); + return fInternalFT.get(timeout, unit); + } + + public void invoke() { + if (!fInvoked.getAndSet(true)) { + Protocol.invokeLater(fInternalFT); + } + } + + abstract public V call() throws Exception; +} |