/*******************************************************************************
* Copyright (c) 2012 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.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.ILaunchesListener2;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDisconnect;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.tcf.debug.test.services.BreakpointsCM;
import org.eclipse.tcf.debug.test.services.DiagnosticsCM;
import org.eclipse.tcf.debug.test.services.LineNumbersCM;
import org.eclipse.tcf.debug.test.services.ProcessesCM;
import org.eclipse.tcf.debug.test.services.RegistersCM;
import org.eclipse.tcf.debug.test.services.RunControlCM;
import org.eclipse.tcf.debug.test.services.RunControlCM.ContextState;
import org.eclipse.tcf.debug.test.services.StackTraceCM;
import org.eclipse.tcf.debug.test.services.SymbolsCM;
import org.eclipse.tcf.debug.test.util.AggregateCallback;
import org.eclipse.tcf.debug.test.util.Callback;
import org.eclipse.tcf.debug.test.util.Callback.ICanceledListener;
import org.eclipse.tcf.debug.test.util.DataCallback;
import org.eclipse.tcf.debug.test.util.ICache;
import org.eclipse.tcf.debug.test.util.Query;
import org.eclipse.tcf.debug.test.util.Task;
import org.eclipse.tcf.debug.test.util.Transaction;
import org.eclipse.tcf.internal.debug.launch.TCFLaunchDelegate;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.protocol.JSON;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IBreakpoints;
import org.eclipse.tcf.services.IDiagnostics;
import org.eclipse.tcf.services.IDiagnostics.ISymbol;
import org.eclipse.tcf.services.IExpressions;
import org.eclipse.tcf.services.ILineNumbers;
import org.eclipse.tcf.services.IMemory;
import org.eclipse.tcf.services.IMemoryMap;
import org.eclipse.tcf.services.IProcesses;
import org.eclipse.tcf.services.IRegisters;
import org.eclipse.tcf.services.IRegisters.RegistersContext;
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.eclipse.tcf.te.tests.interfaces.IConfigurationProperties;
import org.eclipse.tcf.te.tests.tcf.TcfTestCase;
import org.junit.Assert;
import org.osgi.framework.Bundle;
/**
* Base test for validating TCF Debugger UI.
*/
@SuppressWarnings("restriction")
public abstract class AbstractCMTest extends TcfTestCase implements IViewerUpdatesListenerConstants {
private final static int NUM_CHANNELS = 1;
protected IChannel[] channels;
private Query<Object> fMonitorChannelQuery;
private List<Throwable> errors = new ArrayList<Throwable>();
protected ILaunch fLaunch;
protected Object fTestRunKey;
protected IDiagnostics diag;
protected IExpressions expr;
protected ISymbols syms;
protected IStackTrace stk;
protected IRunControl rc;
protected IBreakpoints bp;
protected IMemory fMemory;
protected IMemoryMap fMemoryMap;
protected ILineNumbers fLineNumbers;
protected IRegisters fRegisters;
protected IProcesses fProcesses;
protected RunControlCM fRunControlCM;
protected DiagnosticsCM fDiagnosticsCM;
protected BreakpointsCM fBreakpointsCM;
protected StackTraceCM fStackTraceCM;
protected SymbolsCM fSymbolsCM;
protected LineNumbersCM fLineNumbersCM;
protected RegistersCM fRegistersCM;
protected ProcessesCM fProcessesCM;
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tests.CoreTestCase#getTestBundle()
*/
@Override
protected Bundle getTestBundle() {
return Activator.getDefault().getBundle();
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tests.CoreTestCase#initialize()
*/
@Override
protected void initialize() {
// Turn off the automatic perspective switch and debug view activation to avoid
// JFace views from interfering with the virtual viewers used in tests.
IPreferenceStore prefs = DebugUITools.getPreferenceStore();
prefs.setValue(IInternalDebugUIConstants.PREF_ACTIVATE_DEBUG_VIEW, false);
prefs.setValue(IInternalDebugUIConstants.PREF_SWITCH_PERSPECTIVE_ON_SUSPEND, MessageDialogWithToggle.NEVER);
super.initialize();
// Do not activate debug view or debug perspective, also to avoid interfering
// with tests' virtual viewers.
setProperty(IConfigurationProperties.TARGET_PERSPECTIVE, "org.eclipse.cdt.ui.CPerspective"); //$NON-NLS-1$
setProperty(IConfigurationProperties.TARGET_VIEW, "org.eclipse.cdt.ui.CView"); //$NON-NLS-1$
}
@Override
protected void setUp() throws Exception {
super.setUp();
fTestRunKey = new Object();
// Launch the agent
createLaunch();
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();
new Task<Object>() {
@Override
public Object call() throws Exception {
setUpServiceListeners();
return null;
}
}.get();
validateTestAvailable();
clearBreakpoints();
}
@Override
protected void tearDown() throws Exception {
new Task<Object>() {
@Override
public Object call() throws Exception {
tearDownServiceListeners();
return null;
}
}.get();
terminateLaunch();
new Query<Object>() {
@Override
protected void execute(DataCallback<Object> callback) {
closeChannels(callback);
}
}.get();
super.tearDown();
}
protected String getDiagnosticsTestName() {
return "RCBP1";
}
protected void setUpServiceListeners() throws Exception{
fRunControlCM = new RunControlCM(channels[0], rc);
fDiagnosticsCM = new DiagnosticsCM(channels[0], diag);
fBreakpointsCM = new BreakpointsCM(channels[0], bp);
fStackTraceCM = new StackTraceCM(channels[0], stk, fRunControlCM, fMemory, fMemoryMap);
fSymbolsCM = new SymbolsCM(channels[0], syms, fRunControlCM, fMemoryMap);
fLineNumbersCM = new LineNumbersCM(channels[0], fLineNumbers, fMemoryMap, fRunControlCM);
fRegistersCM = new RegistersCM(channels[0], fRegisters, rc);
fProcessesCM = new ProcessesCM(channels[0], fProcesses);
}
protected void tearDownServiceListeners() throws Exception{
fProcessesCM.dispose();
fRegistersCM.dispose();
fSymbolsCM.dispose();
fBreakpointsCM.dispose();
fStackTraceCM.dispose();
fRunControlCM.dispose();
fDiagnosticsCM.dispose();
fLineNumbersCM.dispose();
}
private void createLaunch() throws Exception {
ILaunchManager lManager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType lcType = lManager.getLaunchConfigurationType("org.eclipse.tcf.debug.LaunchConfigurationType");
ILaunchConfigurationWorkingCopy lcWc = lcType.newInstance(null, "test");
lcWc.setAttribute(TCFLaunchDelegate.ATTR_USE_LOCAL_AGENT, false);
lcWc.setAttribute(TCFLaunchDelegate.ATTR_RUN_LOCAL_AGENT, false);
lcWc.setAttribute(TCFLaunchDelegate.ATTR_PEER_ID, peer.getID());
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);
fMemory = channels[0].getRemoteService(IMemory.class);
fMemoryMap = channels[0].getRemoteService(IMemoryMap.class);
fLineNumbers = channels[0].getRemoteService(ILineNumbers.class);
fRegisters = channels[0].getRemoteService(IRegisters.class);
fProcesses = channels[0].getRemoteService(IProcesses.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>() {
@Override
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 = new Transaction<String[]>() {
@Override
protected String[] process() throws InvalidCacheException ,ExecutionException {
return validate( fDiagnosticsCM.getTestList() );
}
}.get();
int i = 0;
for (; i < testList.length; i++) {
if ("RCBP1".equals(testList[i])) break;
}
Assert.assertTrue("Required test not supported", i != testList.length);
}
protected void clearBreakpoints() throws InterruptedException, ExecutionException, CoreException {
// Delete eclipse breakpoints.
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
IBreakpointManager mgr = DebugPlugin.getDefault().getBreakpointManager();
IBreakpoint[] bps = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints();
mgr.removeBreakpoints(bps, true);
}
};
ResourcesPlugin.getWorkspace().run(wr, null, 0, null);
// Delete TCF breakpoints
new Transaction<Object>() {
@Override
protected Object process() throws InvalidCacheException, ExecutionException {
// Initialize the event cache for breakpoint status
@SuppressWarnings("unchecked")
Map<String, Object>[] bps = new Map[] { };
validate( fBreakpointsCM.set(bps, this) );
return null;
}
}.get();
}
protected TestProcessInfo startProcess() throws InterruptedException, ExecutionException {
return new Transaction<TestProcessInfo>() {
@Override
protected TestProcessInfo process() throws Transaction.InvalidCacheException ,ExecutionException {
String testId = validate( fDiagnosticsCM.runTest(getDiagnosticsTestName(), this) );
RunControlContext testCtx = validate( fRunControlCM.getContext(testId) );
String processId = testCtx.getProcessID();
// Create the cache to listen for exceptions.
fRunControlCM.waitForContextException(testId, fTestRunKey);
String threadId = "";
if (!processId.equals(testId)) {
threadId = testId;
} else {
String[] threads = validate( fRunControlCM.getChildren(processId) );
threadId = threads[0];
}
RunControlContext threadCtx = validate( fRunControlCM.getContext(threadId) );
Assert.assertTrue("Invalid thread context", threadCtx.hasState());
return new TestProcessInfo(testId, testCtx, processId, threadId, threadCtx);
};
}.get();
}
protected boolean runToTestEntry(final TestProcessInfo processInfo, final String testFunc) throws InterruptedException, ExecutionException {
return new Transaction<Boolean>() {
Object fWaitForResumeKey;
Object fWaitForSuspendKey;
boolean fSuspendEventReceived = false;
@Override
protected Boolean process() throws Transaction.InvalidCacheException ,ExecutionException {
ISymbol sym_func0 = validate( fDiagnosticsCM.getSymbol(processInfo.fProcessId, testFunc) );
String sym_func0_value = sym_func0.getValue().toString();
ContextState state = validate (fRunControlCM.getState(processInfo.fThreadId));
while (!state.suspended || !new BigInteger(state.pc).equals(new BigInteger(sym_func0_value))) {
if (state.suspended && fWaitForSuspendKey == null) {
fSuspendEventReceived = true;
// We are not at test entry. Create a new suspend wait cache.
fWaitForResumeKey = new Object();
fWaitForSuspendKey = new Object();
ICache<Object> waitForResume = fRunControlCM.waitForContextResumed(processInfo.fThreadId, fWaitForResumeKey);
// Issue resume command.
validate( fRunControlCM.resume(processInfo.fThreadCtx, fWaitForResumeKey, IRunControl.RM_RESUME, 1) );
// Wait until we receive the resume event.
validate(waitForResume);
fWaitForSuspendKey = new Object();
fRunControlCM.waitForContextSuspended(processInfo.fThreadId, fWaitForSuspendKey);
} else {
if (fWaitForResumeKey != null) {
// Validate resume command
validate( fRunControlCM.resume(processInfo.fThreadCtx, fWaitForResumeKey, IRunControl.RM_RESUME, 1) );
fWaitForResumeKey = null;
}
// Wait until we suspend.
validate( fRunControlCM.waitForContextSuspended(processInfo.fThreadId, fWaitForSuspendKey) );
fWaitForSuspendKey = null;
}
}
return fSuspendEventReceived;
}
}.get();
}
protected TestProcessInfo startProcess(String testFunc) throws Exception {
String bpId = "entryPointBreakpoint";
createBreakpoint(bpId, testFunc);
final TestProcessInfo processInfo = startProcess();
runToTestEntry(processInfo, testFunc);
removeBreakpoint(bpId);
new Transaction<String>() {
@Override
protected String process() throws InvalidCacheException, ExecutionException {
String[] frameIds = validate( fStackTraceCM.getChildren(processInfo.fThreadId) );
Assert.assertTrue("No stack frames" , frameIds.length != 0);
return frameIds[frameIds.length - 1];
}
}.get();
return processInfo;
}
protected ContextState resumeAndWaitForSuspend(final RunControlContext context, final int mode) throws InterruptedException, ExecutionException {
return new Transaction<ContextState>() {
@Override
protected ContextState process() throws InvalidCacheException, ExecutionException {
ICache<Object> waitCache = fRunControlCM.waitForContextSuspended(context.getID(), this);
validate( fRunControlCM.resume(context, this, mode, 1) );
validate(waitCache);
return validate( fRunControlCM.getState(context.getID()) );
}
}.get();
}
protected void createBreakpoint(final String bpId, final String testFunc)
throws InterruptedException, ExecutionException
{
new Transaction<Object>() {
private Map<String,Object> fBp;
{
fBp = new TreeMap<String,Object>();
fBp.put(IBreakpoints.PROP_ID, bpId);
fBp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE);
fBp.put(IBreakpoints.PROP_LOCATION, testFunc);
}
@Override
protected Object process() throws InvalidCacheException, ExecutionException {
// Prime event wait caches
fBreakpointsCM.waitContextAdded(this);
validate( fBreakpointsCM.add(fBp, this) );
validate( fBreakpointsCM.waitContextAdded(this));
// Wait for breakpoint status event and validate it.
Map<String, Object> status = validate(fBreakpointsCM.getStatus(bpId));
String s = (String)status.get(IBreakpoints.STATUS_ERROR);
if (s != null) {
Assert.fail("Invalid BP status: " + s);
}
@SuppressWarnings("unchecked")
Collection<Map<String,Object>> list =
(Collection<Map<String,Object>>)status.get(IBreakpoints.STATUS_INSTANCES);
if (list != null) {
String err = null;
for (Map<String,Object> map : list) {
if (map.get(IBreakpoints.INSTANCE_ERROR) != null) {
err = (String)map.get(IBreakpoints.INSTANCE_ERROR);
}
}
if (err != null) {
Assert.fail("Invalid BP status: " + s);
}
}
return null;
}
}.get();
}
protected void removeBreakpoint(final String bpId) throws InterruptedException, ExecutionException {
new Transaction<Object>() {
@Override
protected Object process() throws InvalidCacheException, ExecutionException {
// Prime event wait caches
fBreakpointsCM.waitContextRemoved(this);
// Remove
validate( fBreakpointsCM.remove(new String[] { bpId }, this) );
// Verify removed event
String[] removedIds = validate( fBreakpointsCM.waitContextRemoved(this));
Assert.assertTrue(Arrays.asList(removedIds).contains(bpId));
return null;
}
}.get();
}
protected void moveToLocation(final String context, final Number address) throws
DebugException, ExecutionException, InterruptedException
{
final RegistersContext pcReg = new Transaction<RegistersContext>() {
@Override
protected RegistersContext process() throws InvalidCacheException ,ExecutionException {
String[] registers = validate(fRegistersCM.getChildren(context));
return findPCRegister(registers);
}
private RegistersContext findPCRegister(String[] registerIds) throws InvalidCacheException ,ExecutionException {
for (String regId : registerIds) {
RegistersContext reg = validate(fRegistersCM.getContext(regId));
if ( IRegisters.ROLE_PC.equals(reg.getRole()) ) {
return reg;
}
}
for (String regId : registerIds) {
String[] children = validate(fRegistersCM.getChildren(regId));
RegistersContext pc = findPCRegister(children);
if (pc != null) return pc;
}
return null;
}
}.get();
assertNotNull("Cannot find PC register", pcReg);
new Transaction<Object>() {
@Override
protected Object process() throws Transaction.InvalidCacheException ,ExecutionException {
byte[] value = addressToByteArray(address, pcReg.getSize(), pcReg.isBigEndian());
validate(fRegistersCM.setContextValue(pcReg, this, value));
return null;
};
}.get();
}
private byte[] addressToByteArray(Number address, int size, boolean bigEndian) {
byte[] bytes = new byte[size];
byte[] addrBytes = JSON.toBigInteger(address).toByteArray();
for (int i=0; i < bytes.length; ++i) {
byte b = 0;
if (i < addrBytes.length) {
b = addrBytes[addrBytes.length - i - 1];
}
bytes[bigEndian ? size -i - 1 : i] = b;
}
return bytes;
}
}