diff options
Diffstat (limited to 'plugins/org.eclipse.tcf.debug/src/org')
37 files changed, 9697 insertions, 0 deletions
diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/Activator.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/Activator.java new file mode 100644 index 000000000..7aa6d5f0c --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/Activator.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.eclipse.tm.internal.tcf.debug.launch.TCFLocalAgent; +import org.eclipse.tm.internal.tcf.debug.launch.TCFUserDefPeer; +import org.eclipse.tm.internal.tcf.debug.model.TCFBreakpointsModel; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.osgi.framework.BundleContext; + + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends Plugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.tm.tcf.debug"; + + // The shared instance + private static Activator plugin; + private static TCFBreakpointsModel bp_model; + + public Activator() { + plugin = this; + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + bp_model = new TCFBreakpointsModel(); + Protocol.invokeLater(new Runnable() { + + public void run() { + TCFUserDefPeer.loadPeers(); + } + }); + } + + @Override + public void stop(BundleContext context) throws Exception { + bp_model.dispose(); + bp_model = null; + TCFLocalAgent.destroy(); + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + + public static TCFBreakpointsModel getBreakpointsModel() { + return bp_model; + } + + /** + * Send error message into Eclipse log. + * @param msg - error message test + * @param err - exception + */ + public static void log(String msg, Throwable err) { + if (plugin == null || plugin.getLog() == null) { + System.err.println(msg); + if (err != null) err.printStackTrace(); + } + else { + plugin.getLog().log(new Status(IStatus.ERROR, + plugin.getBundle().getSymbolicName(), IStatus.OK, msg, err)); + } + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFAction.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFAction.java new file mode 100644 index 000000000..cba7c80c6 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFAction.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.actions; + +import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch; +import org.eclipse.tm.tcf.protocol.Protocol; + +/** + * TCFAction class represents user request to perform some action(s) on + * a remote context, for example, step over line command. + * Such action might require multiple data exchanges with remote target. + * Actions for a particular context should be executed sequentially - + * it does not make sense to execute two step commands concurrently. + * If user requests actions faster then they are executed, + * actions are placed into a FIFO queue. + * + * Clients are expected to implement run() method to perform the action job. + * When the job is done, client code should call done() method. + */ +public abstract class TCFAction implements Runnable { + + protected final TCFLaunch launch; + protected final String ctx_id; + + protected boolean aborted; + + public TCFAction(TCFLaunch launch, String ctx_id) { + assert Protocol.isDispatchThread(); + this.launch = launch; + this.ctx_id = ctx_id; + launch.addContextAction(this); + } + + public void abort() { + aborted = true; + } + + public String getContextID() { + return ctx_id; + } + + public int getPriority() { + return 0; + } + + public void setActionResult(String id, String result) { + launch.setContextActionResult(id, result); + } + + public void done() { + assert Protocol.isDispatchThread(); + launch.removeContextAction(this); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepInto.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepInto.java new file mode 100644 index 000000000..0966ce985 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepInto.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.actions; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.tm.internal.tcf.debug.model.TCFContextState; +import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch; +import org.eclipse.tm.internal.tcf.debug.model.TCFSourceRef; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.ILineNumbers; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tm.tcf.util.TCFDataCache; + +public abstract class TCFActionStepInto extends TCFAction implements IRunControl.RunControlListener { + + private boolean step_line; + private boolean step_back; + private final IRunControl rc = launch.getService(IRunControl.class); + + private IRunControl.RunControlContext ctx; + private TCFDataCache<TCFContextState> state; + private TCFSourceRef source_ref; + private BigInteger pc0; + private BigInteger pc1; + private int step_cnt; + private boolean second_step_back; + private boolean final_step; + + protected boolean exited; + + public TCFActionStepInto(TCFLaunch launch, IRunControl.RunControlContext ctx, boolean step_line, boolean back_step) { + super(launch, ctx.getID()); + this.ctx = ctx; + this.step_line = step_line; + this.step_back = back_step; + } + + protected abstract TCFDataCache<TCFContextState> getContextState(); + protected abstract TCFDataCache<TCFSourceRef> getLineInfo(); + protected abstract TCFDataCache<?> getStackTrace(); + + public void run() { + if (exited) return; + try { + runAction(); + } + catch (Throwable x) { + exit(x); + } + } + + private void setSourceRef(TCFSourceRef ref) { + ILineNumbers.CodeArea area = ref.area; + if (area != null) { + pc0 = JSON.toBigInteger(area.start_address); + pc1 = JSON.toBigInteger(area.end_address); + } + else { + pc0 = null; + pc1 = null; + } + source_ref = ref; + } + + private void runAction() { + if (aborted) { + exit(null); + return; + } + if (state == null) { + rc.addListener(this); + state = getContextState(); + if (state == null) { + exit(new Exception("Invalid context ID")); + return; + } + } + if (!state.validate(this)) return; + if (state.getData() == null || !state.getData().is_suspended) { + Throwable error = state.getError(); + if (error == null) error = new Exception("Context is not suspended"); + exit(error); + return; + } + if (step_cnt > 0) { + String reason = state.getData().suspend_reason; + if (!IRunControl.REASON_STEP.equals(reason)) { + exit(null, reason); + return; + } + } + int mode = 0; + if (!step_line) mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO : IRunControl.RM_STEP_INTO; + else mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO_LINE : IRunControl.RM_STEP_INTO_LINE; + if (ctx.canResume(mode)) { + if (step_cnt > 0) { + exit(null); + return; + } + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + step_cnt++; + return; + } + if (!step_line) { + exit(new Exception("Step into is not supported")); + return; + } + TCFDataCache<?> stack_trace = getStackTrace(); + if (!stack_trace.validate(this)) return; + if (stack_trace.getData() == null) { + exit(stack_trace.getError()); + return; + } + if (source_ref == null) { + TCFDataCache<TCFSourceRef> line_info = getLineInfo(); + if (!line_info.validate(this)) return; + TCFSourceRef ref = line_info.getData(); + if (ref == null) { + step_line = false; + Protocol.invokeLater(this); + return; + } + if (ref.error != null) { + exit(ref.error); + return; + } + setSourceRef(ref); + } + BigInteger pc = new BigInteger(state.getData().suspend_pc); + if (step_cnt > 0) { + if (pc == null || pc0 == null || pc1 == null) { + exit(null); + return; + } + if (pc.compareTo(pc0) < 0 || pc.compareTo(pc1) >= 0) { + TCFDataCache<TCFSourceRef> line_info = getLineInfo(); + if (!line_info.validate(this)) return; + TCFSourceRef ref = line_info.getData(); + if (ref == null || ref.area == null) { + exit(null); + } + else if (isSameLine(source_ref.area, ref.area)) { + setSourceRef(ref); + } + else if (step_back && !second_step_back) { + // After step back we stop at last instruction of previous line. + // Do second step back into line to skip that line. + second_step_back = true; + setSourceRef(ref); + } + else if (step_back && !final_step) { + // After second step back we have stepped one instruction more then needed. + // Do final step forward to correct that. + final_step = true; + step_back = false; + setSourceRef(ref); + } + else { + exit(null); + return; + } + } + } + step_cnt++; + mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO_RANGE : IRunControl.RM_STEP_INTO_RANGE; + if (ctx.canResume(mode) && + pc != null && pc0 != null && pc1 != null && + pc.compareTo(pc0) >= 0 && pc.compareTo(pc1) < 0) { + HashMap<String,Object> args = new HashMap<String,Object>(); + args.put(IRunControl.RP_RANGE_START, pc0); + args.put(IRunControl.RP_RANGE_END, pc1); + ctx.resume(mode, 1, args, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO : IRunControl.RM_STEP_INTO; + if (ctx.canResume(mode)) { + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + exit(new Exception("Step into is not supported")); + } + + private boolean isSameLine(ILineNumbers.CodeArea x, ILineNumbers.CodeArea y) { + if (x == null || y == null) return false; + if (x.start_line != y.start_line) return false; + if (x.directory != y.directory && (x.directory == null || !x.directory.equals(y.directory))) return false; + if (x.file != y.file && (x.file == null || !x.file.equals(y.file))) return false; + return true; + } + + protected void exit(Throwable error) { + exit(error, "Step Into"); + } + + protected void exit(Throwable error, String reason) { + if (exited) return; + rc.removeListener(this); + exited = true; + if (error == null) setActionResult(getContextID(), reason); + else launch.removeContextActions(getContextID()); + done(); + } + + public void containerResumed(String[] context_ids) { + } + + public void containerSuspended(String context, String pc, + String reason, Map<String, Object> params, + String[] suspended_ids) { + for (String id : suspended_ids) { + if (!id.equals(context)) contextSuspended(id, null, null, null); + } + contextSuspended(context, pc, reason, params); + } + + public void contextAdded(RunControlContext[] contexts) { + } + + public void contextChanged(RunControlContext[] contexts) { + for (RunControlContext c : contexts) { + if (c.getID().equals(ctx.getID())) ctx = c; + } + } + + public void contextException(String context, String msg) { + if (context.equals(ctx.getID())) exit(new Exception(msg)); + } + + public void contextRemoved(String[] context_ids) { + for (String context : context_ids) { + if (context.equals(ctx.getID())) exit(null); + } + } + + public void contextResumed(String context) { + } + + public void contextSuspended(String context, String pc, String reason, Map<String,Object> params) { + if (!context.equals(ctx.getID())) return; + Protocol.invokeLater(this); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOut.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOut.java new file mode 100644 index 000000000..3656395cc --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOut.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.actions; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.tm.internal.tcf.debug.model.TCFContextState; +import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IStackTrace; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tm.tcf.util.TCFDataCache; + +public abstract class TCFActionStepOut extends TCFAction implements IRunControl.RunControlListener { + + private final boolean step_back; + private final IRunControl rc = launch.getService(IRunControl.class); + private final IBreakpoints bps = launch.getService(IBreakpoints.class); + + private IRunControl.RunControlContext ctx; + private TCFDataCache<TCFContextState> state; + private int step_cnt; + private Map<String,Object> bp; + + protected boolean exited; + + public TCFActionStepOut(TCFLaunch launch, IRunControl.RunControlContext ctx, boolean step_back) { + super(launch, ctx.getID()); + this.ctx = ctx; + this.step_back = step_back; + } + + protected abstract TCFDataCache<TCFContextState> getContextState(); + protected abstract TCFDataCache<?> getStackTrace(); + protected abstract TCFDataCache<IStackTrace.StackTraceContext> getStackFrame(); + protected abstract int getStackFrameIndex(); + + public void run() { + if (exited) return; + try { + runAction(); + } + catch (Throwable x) { + exit(x); + } + } + + private void runAction() { + if (aborted) { + exit(null); + return; + } + if (state == null) { + rc.addListener(this); + state = getContextState(); + if (state == null) { + exit(new Exception("Invalid context ID")); + return; + } + } + if (!state.validate(this)) return; + if (state.getData() == null || !state.getData().is_suspended) { + Throwable error = state.getError(); + if (error == null) error = new Exception("Context is not suspended"); + exit(error); + return; + } + TCFDataCache<?> stack_trace = getStackTrace(); + if (!stack_trace.validate(this)) return; + int frame_index = getStackFrameIndex(); + if (step_cnt > 0) { + TCFContextState state_data = state.getData(); + boolean ok = isMyBreakpoint(state_data) || IRunControl.REASON_STEP.equals(state_data.suspend_reason); + if (!ok) exit(null, state_data.suspend_reason); + else if (frame_index < 0) exit(null); + if (exited) return; + } + int mode = step_back ? IRunControl.RM_REVERSE_STEP_OUT : IRunControl.RM_STEP_OUT; + if (ctx.canResume(mode)) { + int cnt = 1; + if (ctx.canCount(mode)) cnt += frame_index; + ctx.resume(mode, cnt, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + step_cnt++; + return; + } + if (bps != null && ctx.canResume(step_back ? IRunControl.RM_REVERSE_RESUME : IRunControl.RM_RESUME)) { + if (bp == null) { + TCFDataCache<IStackTrace.StackTraceContext> frame = getStackFrame(); + if (!frame.validate(this)) return; + Number addr = null; + if (frame.getData() != null) addr = frame.getData().getReturnAddress(); + if (addr == null) { + exit(new Exception("Unknown stack frame return address")); + return; + } + if (step_back) { + BigInteger n = JSON.toBigInteger(addr); + addr = n.subtract(BigInteger.valueOf(1)); + } + String id = "Step." + ctx.getID(); + bp = new HashMap<String,Object>(); + bp.put(IBreakpoints.PROP_ID, id); + bp.put(IBreakpoints.PROP_LOCATION, addr.toString()); + bp.put(IBreakpoints.PROP_CONDITION, "$thread==\"" + ctx.getID() + "\""); + bp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + bps.add(bp, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + } + ctx.resume(step_back ? IRunControl.RM_REVERSE_RESUME : IRunControl.RM_RESUME, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + step_cnt++; + return; + } + exit(new Exception("Step out is not supported")); + } + + protected void exit(Throwable error) { + exit(error, "Step Out"); + } + + protected void exit(Throwable error, String reason) { + if (exited) return; + if (bp != null) { + bps.remove(new String[]{ (String)bp.get(IBreakpoints.PROP_ID) }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + } + }); + } + rc.removeListener(this); + exited = true; + if (error == null) setActionResult(getContextID(), reason); + else launch.removeContextActions(getContextID()); + done(); + } + + public void containerResumed(String[] context_ids) { + } + + public void containerSuspended(String context, String pc, + String reason, Map<String, Object> params, + String[] suspended_ids) { + for (String id : suspended_ids) { + if (!id.equals(context)) contextSuspended(id, null, null, null); + } + contextSuspended(context, pc, reason, params); + } + + public void contextAdded(RunControlContext[] contexts) { + } + + public void contextChanged(RunControlContext[] contexts) { + for (RunControlContext c : contexts) { + if (c.getID().equals(ctx.getID())) ctx = c; + } + } + + public void contextException(String context, String msg) { + if (context.equals(ctx.getID())) exit(new Exception(msg)); + } + + public void contextRemoved(String[] context_ids) { + for (String context : context_ids) { + if (context.equals(ctx.getID())) exit(null); + } + } + + public void contextResumed(String context) { + } + + public void contextSuspended(String context, String pc, String reason, Map<String,Object> params) { + if (!context.equals(ctx.getID())) return; + Protocol.invokeLater(this); + } + + private boolean isMyBreakpoint(TCFContextState state_data) { + if (bp == null) return false; + if (!IRunControl.REASON_BREAKPOINT.equals(state_data.suspend_reason)) return false; + if (state_data.suspend_params != null) { + Object ids = state_data.suspend_params.get(IRunControl.STATE_BREAKPOINT_IDS); + if (ids != null) { + @SuppressWarnings("unchecked") + Collection<String> c = (Collection<String>)ids; + if (c.contains(bp.get(IBreakpoints.PROP_ID))) return true; + } + } + if (state_data.suspend_pc == null) return false; + BigInteger x = new BigInteger(state_data.suspend_pc); + BigInteger y = new BigInteger((String)bp.get(IBreakpoints.PROP_LOCATION)); + return x.equals(y); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOver.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOver.java new file mode 100644 index 000000000..c52ae5834 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/actions/TCFActionStepOver.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.actions; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.tm.internal.tcf.debug.model.TCFContextState; +import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch; +import org.eclipse.tm.internal.tcf.debug.model.TCFSourceRef; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; +import org.eclipse.tm.tcf.services.ILineNumbers; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IStackTrace; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tm.tcf.util.TCFDataCache; + +public abstract class TCFActionStepOver extends TCFAction implements IRunControl.RunControlListener { + + private boolean step_line; + private boolean step_back; + private final IRunControl rc = launch.getService(IRunControl.class); + private final IBreakpoints bps = launch.getService(IBreakpoints.class); + + private IRunControl.RunControlContext ctx; + private TCFDataCache<TCFContextState> state; + private TCFDataCache<TCFSourceRef> line_info; + private TCFSourceRef source_ref; + private BigInteger pc0; + private BigInteger pc1; + private int step_cnt; + private Map<String,Object> bp; + private boolean second_step_back; + private boolean final_step; + + protected boolean exited; + + public TCFActionStepOver(TCFLaunch launch, IRunControl.RunControlContext ctx, boolean step_line, boolean step_back) { + super(launch, ctx.getID()); + this.ctx = ctx; + this.step_line = step_line; + this.step_back = step_back; + } + + protected abstract TCFDataCache<TCFContextState> getContextState(); + protected abstract TCFDataCache<TCFSourceRef> getLineInfo(); + protected abstract TCFDataCache<?> getStackTrace(); + protected abstract TCFDataCache<IStackTrace.StackTraceContext> getStackFrame(); + protected abstract int getStackFrameIndex(); + + public void run() { + if (exited) return; + try { + runAction(); + } + catch (Throwable x) { + exit(x); + } + } + + private void setSourceRef(TCFSourceRef ref) { + ILineNumbers.CodeArea area = ref.area; + if (area != null) { + pc0 = JSON.toBigInteger(area.start_address); + pc1 = JSON.toBigInteger(area.end_address); + } + else { + pc0 = null; + pc1 = null; + } + source_ref = ref; + } + + private void runAction() { + if (aborted) { + exit(null); + return; + } + if (state == null) { + rc.addListener(this); + state = getContextState(); + if (state == null) { + exit(new Exception("Invalid context ID")); + return; + } + } + if (!state.validate(this)) return; + if (state.getData() == null || !state.getData().is_suspended) { + Throwable error = state.getError(); + if (error == null) error = new Exception("Context is not suspended"); + exit(error); + return; + } + if (step_cnt > 0) { + boolean ok = false; + TCFContextState state_data = state.getData(); + if (IRunControl.REASON_STEP.equals(state_data.suspend_reason) || isMyBreakpoint(state_data)) { + ok = true; + } + else if (IRunControl.REASON_BREAKPOINT.equals(state_data.suspend_reason) && pc0 != null && pc1 != null) { + BigInteger x = new BigInteger(state_data.suspend_pc); + ok = x.compareTo(pc0) >= 0 && x.compareTo(pc1) < 0; + } + if (!ok) { + exit(null, state_data.suspend_reason); + return; + } + } + int mode = 0; + if (!step_line) mode = step_back ? IRunControl.RM_REVERSE_STEP_OVER : IRunControl.RM_STEP_OVER; + else mode = step_back ? IRunControl.RM_REVERSE_STEP_OVER_LINE : IRunControl.RM_STEP_OVER_LINE; + if (ctx.canResume(mode)) { + if (step_cnt > 0) { + exit(null); + return; + } + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + step_cnt++; + return; + } + TCFDataCache<?> stack_trace = getStackTrace(); + if (!stack_trace.validate(this)) return; + if (step_line && source_ref == null) { + line_info = getLineInfo(); + if (!line_info.validate(this)) return; + TCFSourceRef ref = line_info.getData(); + if (ref == null) { + step_line = false; + Protocol.invokeLater(this); + return; + } + if (ref.error != null) { + exit(ref.error); + return; + } + setSourceRef(ref); + } + int fno = getStackFrameIndex(); + if (fno > 0) { + mode = step_back ? IRunControl.RM_REVERSE_STEP_OUT : IRunControl.RM_STEP_OUT; + if (ctx.canResume(mode)) { + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + mode = step_back ? IRunControl.RM_REVERSE_RESUME : IRunControl.RM_RESUME; + if (bps != null && ctx.canResume(mode)) { + if (bp == null) { + TCFDataCache<IStackTrace.StackTraceContext> frame = getStackFrame(); + if (!frame.validate(this)) return; + Number addr = frame.getData().getInstructionAddress(); + if (addr == null) { + exit(new Exception("Unknown PC address")); + return; + } + if (step_back) { + BigInteger n = JSON.toBigInteger(addr); + addr = n.subtract(BigInteger.valueOf(1)); + } + String id = "Step." + ctx.getID(); + bp = new HashMap<String,Object>(); + bp.put(IBreakpoints.PROP_ID, id); + bp.put(IBreakpoints.PROP_LOCATION, addr.toString()); + bp.put(IBreakpoints.PROP_CONDITION, "$thread==\"" + ctx.getID() + "\""); + bp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + bps.add(bp, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + } + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + step_cnt++; + return; + } + exit(new Exception("Step over is not supported")); + return; + } + if (bp != null) { + bps.remove(new String[]{ (String)bp.get(IBreakpoints.PROP_ID) }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + bp = null; + } + BigInteger pc = new BigInteger(state.getData().suspend_pc); + if (step_cnt > 0) { + if (pc0 == null && pc1 == null || state.getData().suspend_pc == null) { + exit(null); + return; + } + assert step_line; + if (pc.compareTo(pc0) < 0 || pc.compareTo(pc1) >= 0) { + if (!line_info.validate(this)) return; + TCFSourceRef ref = line_info.getData(); + if (ref == null || ref.area == null) { + if (fno < 0 && (stack_trace.getError() == null || step_cnt >= 10)) { + exit(stack_trace.getError()); + return; + } + // No line info for current PC, continue stepping + } + else if (isSameLine(source_ref.area, ref.area)) { + setSourceRef(ref); + } + else if (step_back && !second_step_back) { + // After step back we stop at last instruction of previous line. + // Do second step back over line to skip that line. + second_step_back = true; + setSourceRef(ref); + } + else if (step_back && !final_step) { + // After second step back we have stepped one instruction more then needed. + // Do final step forward to correct that. + final_step = true; + step_back = false; + setSourceRef(ref); + } + else { + exit(null); + return; + } + } + } + step_cnt++; + mode = step_back ? IRunControl.RM_REVERSE_STEP_OVER : IRunControl.RM_STEP_OVER; + if (ctx.canResume(mode)) { + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO_RANGE : IRunControl.RM_STEP_INTO_RANGE; + if (ctx.canResume(mode) && + pc != null && pc0 != null && pc1 != null && + pc.compareTo(pc0) >= 0 && pc.compareTo(pc1) < 0) { + HashMap<String,Object> args = new HashMap<String,Object>(); + args.put(IRunControl.RP_RANGE_START, pc0); + args.put(IRunControl.RP_RANGE_END, pc1); + ctx.resume(mode, 1, args, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + mode = step_back ? IRunControl.RM_REVERSE_STEP_INTO : IRunControl.RM_STEP_INTO; + if (ctx.canResume(mode)) { + ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + return; + } + exit(new Exception("Step over is not supported")); + } + + protected void exit(Throwable error) { + exit(error, "Step Over"); + } + + protected void exit(Throwable error, String reason) { + if (exited) return; + if (bp != null) { + bps.remove(new String[]{ (String)bp.get(IBreakpoints.PROP_ID) }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + } + }); + } + rc.removeListener(this); + exited = true; + if (error == null) setActionResult(getContextID(), reason); + else launch.removeContextActions(getContextID()); + done(); + } + + public void containerResumed(String[] context_ids) { + } + + public void containerSuspended(String context, String pc, + String reason, Map<String, Object> params, + String[] suspended_ids) { + for (String id : suspended_ids) { + if (!id.equals(context)) contextSuspended(id, null, null, null); + } + contextSuspended(context, pc, reason, params); + } + + public void contextAdded(RunControlContext[] contexts) { + } + + public void contextChanged(RunControlContext[] contexts) { + for (RunControlContext c : contexts) { + if (c.getID().equals(ctx.getID())) ctx = c; + } + } + + public void contextException(String context, String msg) { + if (context.equals(ctx.getID())) exit(new Exception(msg)); + } + + public void contextRemoved(String[] context_ids) { + for (String context : context_ids) { + if (context.equals(ctx.getID())) exit(null); + } + } + + public void contextResumed(String context) { + } + + public void contextSuspended(String context, String pc, String reason, Map<String, Object> params) { + if (!context.equals(ctx.getID())) return; + Protocol.invokeLater(this); + } + + private boolean isSameLine(ILineNumbers.CodeArea x, ILineNumbers.CodeArea y) { + if (x == null || y == null) return false; + if (x.start_line != y.start_line) return false; + if (x.directory != y.directory && (x.directory == null || !x.directory.equals(y.directory))) return false; + if (x.file != y.file && (x.file == null || !x.file.equals(y.file))) return false; + return true; + } + + private boolean isMyBreakpoint(TCFContextState state_data) { + if (bp == null) return false; + if (!IRunControl.REASON_BREAKPOINT.equals(state_data.suspend_reason)) return false; + if (state_data.suspend_params != null) { + Object ids = state_data.suspend_params.get(IRunControl.STATE_BREAKPOINT_IDS); + if (ids != null) { + @SuppressWarnings("unchecked") + Collection<String> c = (Collection<String>)ids; + if (c.contains(bp.get(IBreakpoints.PROP_ID))) return true; + } + } + if (state_data.suspend_pc == null) return false; + BigInteger x = new BigInteger(state_data.suspend_pc); + BigInteger y = new BigInteger((String)bp.get(IBreakpoints.PROP_LOCATION)); + return x.equals(y); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLaunchDelegate.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLaunchDelegate.java new file mode 100644 index 000000000..ecf35cb11 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLaunchDelegate.java @@ -0,0 +1,350 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.launch; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.model.LaunchConfigurationDelegate; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.internal.tcf.debug.model.ITCFConstants; +import org.eclipse.tm.internal.tcf.debug.model.TCFLaunch; +import org.eclipse.tm.internal.tcf.debug.model.TCFMemoryRegion; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IMemoryMap; +import org.eclipse.tm.tcf.services.IPathMap; +import org.eclipse.tm.tcf.util.TCFTask; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +public class TCFLaunchDelegate extends LaunchConfigurationDelegate { + + public static final String + ATTR_PEER_ID = ITCFConstants.ID_TCF_DEBUG_MODEL + ".PeerID", + ATTR_PROJECT_NAME = ITCFConstants.ID_TCF_DEBUG_MODEL + ".ProjectName", + ATTR_LOCAL_PROGRAM_FILE = ITCFConstants.ID_TCF_DEBUG_MODEL + ".LocalProgramFile", + ATTR_REMOTE_PROGRAM_FILE = ITCFConstants.ID_TCF_DEBUG_MODEL + ".ProgramFile", + ATTR_COPY_TO_REMOTE_FILE = ITCFConstants.ID_TCF_DEBUG_MODEL + ".CopyToRemote", + ATTR_PROGRAM_ARGUMENTS = ITCFConstants.ID_TCF_DEBUG_MODEL + ".ProgramArguments", + ATTR_WORKING_DIRECTORY = ITCFConstants.ID_TCF_DEBUG_MODEL + ".WorkingDirectory", + ATTR_ATTACH_CHILDREN = ITCFConstants.ID_TCF_DEBUG_MODEL + ".AttachChildren", + ATTR_DISCONNECT_ON_CTX_EXIT = ITCFConstants.ID_TCF_DEBUG_MODEL + ".DisconnectOnCtxExit", + ATTR_USE_TERMINAL = ITCFConstants.ID_TCF_DEBUG_MODEL + ".UseTerminal", + ATTR_RUN_LOCAL_AGENT = ITCFConstants.ID_TCF_DEBUG_MODEL + ".RunLocalAgent", + ATTR_USE_LOCAL_AGENT = ITCFConstants.ID_TCF_DEBUG_MODEL + ".UseLocalAgent", + ATTR_SIGNALS_DONT_STOP = ITCFConstants.ID_TCF_DEBUG_MODEL + ".SignalsDontStop", + ATTR_SIGNALS_DONT_PASS = ITCFConstants.ID_TCF_DEBUG_MODEL + ".SignalsDontPath", + ATTR_PATH_MAP = ITCFConstants.ID_TCF_DEBUG_MODEL + ".PathMap", + ATTR_MEMORY_MAP = ITCFConstants.ID_TCF_DEBUG_MODEL + ".MemoryMap"; + + public static class PathMapRule implements IPathMap.PathMapRule { + + private final Map<String,Object> props; + + public PathMapRule(Map<String,Object> props) { + this.props = props; + } + + public Map<String,Object> getProperties() { + return props; + } + + public String getID() { + return (String)props.get(IPathMap.PROP_ID); + } + + public String getSource() { + return (String)props.get(IPathMap.PROP_SOURCE); + } + + public String getDestination() { + return (String)props.get(IPathMap.PROP_DESTINATION); + } + + public String getHost() { + return (String)props.get(IPathMap.PROP_HOST); + } + + public String getProtocol() { + return (String)props.get(IPathMap.PROP_PROTOCOL); + } + + public String toString() { + StringBuffer bf = new StringBuffer(); + for (String nm : props.keySet()) { + Object o = props.get(nm); + if (o != null) { + bf.append(nm); + bf.append('='); + String s = o.toString(); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (ch >= ' ' && ch != '|' && ch != '\\') { + bf.append(ch); + } + else { + bf.append('\\'); + bf.append((int)ch); + bf.append(';'); + } + } + bf.append('|'); + } + } + bf.append('|'); + return bf.toString(); + } + } + + /** + * Given value of ATTR_PATH_MAP, return array of PathMapRule objects. + * @param s - value of ATTR_PATH_MAP. + * @return array of PathMapRule objects. + */ + public static ArrayList<PathMapRule> parsePathMapAttribute(String s) { + ArrayList<PathMapRule> map = new ArrayList<PathMapRule>(); + StringBuffer bf = new StringBuffer(); + int i = 0; + while (i < s.length()) { + PathMapRule e = new PathMapRule(new HashMap<String,Object>()); + while (i < s.length()) { + char ch = s.charAt(i++); + if (ch == '|') { + map.add(e); + break; + } + bf.setLength(0); + bf.append(ch); + while (i < s.length()) { + ch = s.charAt(i++); + if (ch == '=') break; + bf.append(ch); + } + String nm = bf.toString(); + bf.setLength(0); + while (i < s.length()) { + ch = s.charAt(i++); + if (ch == '|') { + if (bf.length() > 0) e.props.put(nm, bf.toString()); + break; + } + else if (ch == '\\') { + int n = 0; + while (i < s.length()) { + char d = s.charAt(i++); + if (d == ';') break; + n = n * 10 + (d - '0'); + } + bf.append((char)n); + } + else { + bf.append(ch); + } + } + } + } + return map; + } + + /** + * Given value of ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, + * return array of PathMapRule objects. + * @param s - value of ATTR_PATH_MAP. + * @return array of PathMapRule objects. + */ + public static ArrayList<PathMapRule> parseSourceLocatorMemento(String s) throws CoreException { + ArrayList<PathMapRule> map = new ArrayList<PathMapRule>(); + if (s == null || s.length() == 0) return map; + Element root = DebugPlugin.parseDocument(s); + NodeList list = root.getChildNodes(); + int length = list.getLength(); + for (int i = 0; i < length; i++) { + Node node = list.item(i); + short type = node.getNodeType(); + if (type == Node.ELEMENT_NODE) { + Element entry = (Element)node; + if (entry.getNodeName().equalsIgnoreCase("sourceContainers")) { + parseSourceContainers(map, entry); + } + } + } + return map; + } + + private static void parseSourceContainers(ArrayList<PathMapRule> map, Element element) throws CoreException { + NodeList list = element.getChildNodes(); + int length = list.getLength(); + for (int i = 0; i < length; i++) { + Node node = list.item(i); + short type = node.getNodeType(); + if (type == Node.ELEMENT_NODE) { + Element entry = (Element)node; + String memento = entry.getAttribute("memento"); + if (memento != null && memento.length() > 0) readSourceContainer(map, memento); + } + } + } + + private static void readSourceContainer(ArrayList<PathMapRule> map, String s) throws CoreException { + Element root = DebugPlugin.parseDocument(s); + if ("mapping".equals(root.getNodeName())) { + NodeList list = root.getChildNodes(); + int length = list.getLength(); + for (int i = 0; i < length; i++) { + Node node = list.item(i); + short type = node.getNodeType(); + if (type == Node.ELEMENT_NODE) { + Element entry = (Element)node; + if (entry.getNodeName().equalsIgnoreCase("mapEntry")) { + String memento = entry.getAttribute("memento"); + if (memento != null && memento.length() > 0) { + Element map_entry = DebugPlugin.parseDocument(memento); + String src = map_entry.getAttribute("backendPath"); + String dst = map_entry.getAttribute("localPath"); + if (src != null) src = src.replace('\\', '/'); + PathMapRule e = new PathMapRule(new HashMap<String,Object>()); + e.props.put(IPathMap.PROP_SOURCE, src); + e.props.put(IPathMap.PROP_DESTINATION, dst); + map.add(e); + } + } + } + } + } + } + + /** + * Given value of ATTR_MEMORY_MAP, add lists of TCFMemoryRegion objects into 'maps'. + * @param maps - Map object to fill with memory maps. + * @param s - value of ATTR_MEMORY_MAP. + */ + @SuppressWarnings("unchecked") + public static void parseMemMapsAttribute(Map<String,ArrayList<IMemoryMap.MemoryRegion>> maps, String s) throws Exception { + if (s == null || s.length() == 0) return; + Collection<Map<String,Object>> list = (Collection<Map<String,Object>>)JSON.parseOne(s.getBytes("UTF-8")); + if (list == null) return; + for (Map<String,Object> map : list) { + String id = (String)map.get(IMemoryMap.PROP_ID); + if (id != null) { + ArrayList<IMemoryMap.MemoryRegion> l = maps.get(id); + if (l == null) { + l = new ArrayList<IMemoryMap.MemoryRegion>(); + maps.put(id, l); + } + l.add(new TCFMemoryRegion(map)); + } + } + } + + /** + * Read ATTR_MEMORY_MAP attribute of a launch configuration. + * @param maps - Map object to fill with memory maps. + * @param cfg - the launch configuration. + * @throws Exception + */ + public static void getMemMapsAttribute(Map<String,ArrayList<IMemoryMap.MemoryRegion>> maps, + ILaunchConfiguration cfg) throws Exception { + String maps_cfg = cfg.getAttribute(ATTR_MEMORY_MAP, (String)null); + parseMemMapsAttribute(maps, maps_cfg); + } + + /** + * Given project name and program name returns absolute path of the program. + * @param project_name - workspace project name. + * @param program_name - launch program name. + * @return program path or null if both project name and program name are null. + */ + public static String getProgramPath(String project_name, String program_name) { + if (program_name == null || program_name.length() == 0) return null; + if (project_name == null || project_name.length() == 0) { + File file = new File(program_name); + if (!file.isAbsolute()) { + File ws = ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile(); + file = new File(ws, program_name); + } + return file.getAbsolutePath(); + } + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(project_name); + IPath program_path = new Path(program_name); + if (!program_path.isAbsolute()) { + if (project == null || !project.getFile(program_name).exists()) return null; + program_path = project.getFile(program_name).getLocation(); + } + return program_path.toOSString(); + } + + /** + * Create new TCF launch object. + * @return new TCFLaunch object + */ + public ILaunch getLaunch(final ILaunchConfiguration configuration, final String mode) throws CoreException { + return new TCFTask<ILaunch>() { + int cnt; + public void run() { + // Need to delay at least one dispatch cycle to work around + // a possible racing between thread that calls getLaunch() and + // the process of activation of other TCF plug-ins. + if (cnt++ < 2) Protocol.invokeLater(this); + else done(new TCFLaunch(configuration, mode)); + } + }.getE(); + } + + /** + * Launch TCF session. + */ + public void launch(final ILaunchConfiguration configuration, final String mode, + final ILaunch launch, final IProgressMonitor monitor) throws CoreException { + String local_id = null; + int task_cnt = 1; + if (configuration.getAttribute(ATTR_RUN_LOCAL_AGENT, false)) { + task_cnt++; + if (monitor != null) monitor.beginTask("Starting TCF Agent", task_cnt); //$NON-NLS-1$ + local_id = TCFLocalAgent.runLocalAgent(); + } + else if (configuration.getAttribute(ATTR_USE_LOCAL_AGENT, true)) { + task_cnt++; + if (monitor != null) monitor.beginTask("Searching TCF Agent", task_cnt); //$NON-NLS-1$ + local_id = TCFLocalAgent.getLocalAgentID(); + if (local_id == null) throw new CoreException(new Status(IStatus.ERROR, + Activator.PLUGIN_ID, 0, + "Cannot find TCF agent on the local host", + null)); + } + if (monitor != null) monitor.beginTask("Launching TCF debugger session", task_cnt); //$NON-NLS-1$ + final String id = + configuration.getAttribute(ATTR_USE_LOCAL_AGENT, true) ? + local_id : configuration.getAttribute(ATTR_PEER_ID, ""); + Protocol.invokeLater(new Runnable() { + public void run() { + ((TCFLaunch)launch).launchTCF(mode, id); + if (monitor != null) monitor.done(); + } + }); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLocalAgent.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLocalAgent.java new file mode 100644 index 000000000..b16fdb255 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFLocalAgent.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2009, 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.tm.internal.tcf.debug.launch; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.tcf.protocol.IPeer; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.ILocator; +import org.eclipse.tm.tcf.util.TCFTask; +import org.osgi.framework.Bundle; + +/** + * This class checks that TCF Agent is running on the local host, + * and starts a new instance of the agent if it cannot be located. + */ +public class TCFLocalAgent { + + private static final String + AGENT_HOST = "127.0.0.1", + AGENT_PORT = "1534"; + + private static Process agent; + private static boolean destroed; + + private static String getAgentFileName() { + String os = System.getProperty("os.name"); + String arch = System.getProperty("os.arch"); + String fnm = "agent"; + if (arch.equals("x86")) arch = "i386"; + if (arch.equals("i686")) arch = "i386"; + if (os.startsWith("Windows")) { + os = "Windows"; + fnm = "agent.exe"; + } + if (os.equals("Linux")) os = "GNU/Linux"; + return "agent/" + os + "/" + arch + "/" + fnm; + } + + public static synchronized String runLocalAgent() throws CoreException { + if (destroed) return null; + String id = getLocalAgentID(); + if (id != null) return id; + if (agent != null) { + agent.destroy(); + agent = null; + } + Path fnm = new Path(getAgentFileName()); + try { + Bundle bundle = Platform.getBundle(Activator.PLUGIN_ID); + URL url = FileLocator.find(bundle, fnm, null); + if (url != null) { + URLConnection ucn = url.openConnection(); + ucn.setRequestProperty("Method", "HEAD"); + ucn.connect(); + long mtime = ucn.getLastModified(); + File f = Activator.getDefault().getStateLocation().append(fnm).toFile(); + if (!f.exists() || mtime != f.lastModified()) { + f.getParentFile().mkdirs(); + InputStream inp = url.openStream(); + OutputStream out = new FileOutputStream(f); + byte[] buf = new byte[0x1000]; + for (;;) { + int len = inp.read(buf); + if (len < 0) break; + out.write(buf, 0, len); + } + out.close(); + inp.close(); + if (!"exe".equals(fnm.getFileExtension())) { + String[] cmd = { + "chmod", + "a+x", + f.getAbsolutePath() + }; + Runtime.getRuntime().exec(cmd).waitFor(); + } + f.setLastModified(mtime); + } + String[] cmd = { + f.getAbsolutePath(), + "-s", + "TCP:" + AGENT_HOST + ":" + AGENT_PORT + }; + final Process prs = agent = Runtime.getRuntime().exec(cmd); + final TCFTask<String> waiting = waitAgentReady(); + Thread t = new Thread() { + public void run() { + try { + final int n = prs.waitFor(); + if (n != 0) { + Protocol.invokeLater(new Runnable() { + public void run() { + if (waiting.isDone()) return; + waiting.error(new IOException("TCF Agent exited with code " + n)); + } + }); + } + synchronized (TCFLocalAgent.class) { + if (agent == prs) { + if (n != 0 && !destroed) { + Activator.log("TCF Agent exited with code " + n, null); + } + agent = null; + } + } + } + catch (InterruptedException x) { + Activator.log("TCF Agent Monitor interrupted", x); + } + } + }; + t.setDaemon(true); + t.setName("TCF Agent Monitor"); + t.start(); + return waiting.getIO(); + } + } + catch (Throwable x) { + agent = null; + throw new CoreException(new Status(IStatus.ERROR, + Activator.PLUGIN_ID, 0, + "Cannot start local TCF agent.", + x)); + } + throw new CoreException(new Status(IStatus.ERROR, + Activator.PLUGIN_ID, 0, + "Cannot start local TCF agent: file not available:\n" + fnm, + null)); + } + + private static boolean isLocalAgent(IPeer p) { + String prot = p.getTransportName(); + if (prot.equals("PIPE")) return true; + if (prot.equals("UNIX")) { + String port = p.getAttributes().get(IPeer.ATTR_IP_PORT); + return AGENT_PORT.equals(port); + } + String host = p.getAttributes().get(IPeer.ATTR_IP_HOST); + String port = p.getAttributes().get(IPeer.ATTR_IP_PORT); + return AGENT_HOST.equals(host) && AGENT_PORT.equals(port); + } + + public static synchronized String getLocalAgentID() { + return new TCFTask<String>() { + public void run() { + final ILocator locator = Protocol.getLocator(); + for (IPeer p : locator.getPeers().values()) { + if (isLocalAgent(p)) { + done(p.getID()); + return; + } + } + done(null); + } + }.getE(); + } + + private static TCFTask<String> waitAgentReady() { + return new TCFTask<String>() { + public void run() { + final ILocator locator = Protocol.getLocator(); + for (IPeer p : locator.getPeers().values()) { + if (isLocalAgent(p)) { + done(p.getID()); + return; + } + } + final ILocator.LocatorListener listener = new ILocator.LocatorListener() { + public void peerAdded(IPeer p) { + if (!isDone() && isLocalAgent(p)) { + done(p.getID()); + locator.removeListener(this); + } + } + public void peerChanged(IPeer peer) { + } + public void peerHeartBeat(String id) { + } + public void peerRemoved(String id) { + } + }; + locator.addListener(listener); + Protocol.invokeLater(30000, new Runnable() { + public void run() { + if (!isDone()) { + error(new Exception("Timeout waiting for TCF Agent to start")); + locator.removeListener(listener); + } + } + }); + } + }; + } + + public static synchronized void destroy() { + if (agent != null) { + destroed = true; + agent.destroy(); + } + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupDirector.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupDirector.java new file mode 100644 index 000000000..48cc08230 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupDirector.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.launch; + +import org.eclipse.debug.core.model.ISourceLocator; +import org.eclipse.debug.core.model.IStackFrame; +import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector; +import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; +import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; +import org.eclipse.tm.tcf.services.ILineNumbers; + +/** + * TCF source lookup director. + * For TCF source lookup there is one source lookup participant. + */ +public class TCFSourceLookupDirector extends AbstractSourceLookupDirector { + + public static Object lookup(ISourceLocator locator, Object element) { + Object source_element = null; + if (locator instanceof ISourceLookupDirector) { + if (element instanceof ILineNumbers.CodeArea) { + String file_name = TCFSourceLookupParticipant.toFileName((ILineNumbers.CodeArea)element); + if (file_name != null) source_element = ((ISourceLookupDirector)locator).getSourceElement(file_name); + } + else { + source_element = ((ISourceLookupDirector)locator).getSourceElement(element); + } + } + else if (element instanceof IStackFrame) { + source_element = locator.getSourceElement((IStackFrame)element); + } + return source_element; + } + + public void initializeParticipants() { + addParticipants(new ISourceLookupParticipant[] { new TCFSourceLookupParticipant() }); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupParticipant.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupParticipant.java new file mode 100644 index 000000000..350f72a48 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourceLookupParticipant.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.launch; + +import java.io.File; +import java.net.InetAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupParticipant; +import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; +import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; +import org.eclipse.tm.internal.tcf.debug.launch.TCFLaunchDelegate.PathMapRule; +import org.eclipse.tm.tcf.services.ILineNumbers; + +/** + * The TCF source lookup participant knows how to translate a ILineNumbers.CodeArea + * into a source file name + */ +public class TCFSourceLookupParticipant extends AbstractSourceLookupParticipant { + + @SuppressWarnings("serial") + private final LinkedHashMap<String,Object[]> cache = new LinkedHashMap<String,Object[]>(511, 0.75f, true) { + @Override + @SuppressWarnings("rawtypes") + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 1023; + } + }; + + @Override + public void sourceContainersChanged(ISourceLookupDirector director) { + cache.clear(); + } + + public String getSourceName(Object object) throws CoreException { + if (object instanceof String) { + return (String)object; + } + if (object instanceof ILineNumbers.CodeArea) { + ILineNumbers.CodeArea area = (ILineNumbers.CodeArea)object; + return toFileName(area); + } + return null; + } + + public static String toFileName(ILineNumbers.CodeArea area) { + if (area.directory != null && area.file != null && !isAbsolutePath(area.file)) { + return area.directory + "/" + area.file; + } + return area.file; + } + + private static boolean isAbsolutePath(String fnm) { + if (fnm.length() == 0) return false; + char ch = fnm.charAt(0); + if (ch == '/' || ch == '\\') return true; + if (fnm.length() >= 3 && fnm.charAt(1) == ':') { + ch = fnm.charAt(2); + if (ch == '/' || ch == '\\') return true; + } + return false; + } + + private String applyPathMap(String fnm) { + ILaunchConfiguration cfg = getDirector().getLaunchConfiguration(); + if (cfg == null) return fnm; + try { + String path_map = cfg.getAttribute(TCFLaunchDelegate.ATTR_PATH_MAP, ""); + if (path_map.length() == 0) return fnm; + ArrayList<PathMapRule> map = TCFLaunchDelegate.parsePathMapAttribute(path_map); + for (PathMapRule r : map) { + String src = r.getSource(); + if (!fnm.startsWith(src)) continue; + String host = r.getHost(); + if (host != null && host.length() > 0) { + if (!InetAddress.getLocalHost().equals(InetAddress.getByName(host))) continue; + } + String dst = r.getDestination(); + if (dst == null || dst.length() == 0) continue; + int l = src.length(); + if (dst.endsWith("/") && l < fnm.length() && fnm.charAt(l) == '/') l++; + return dst + fnm.substring(l); + } + if (fnm.startsWith("/cygdrive/")) { + fnm = fnm.substring(10, 11) + ":" + fnm.substring(11); + } + return fnm; + } + catch (Exception x) { + return fnm; + } + } + + private Object[] findSource(String name) throws CoreException { + Object[] res; + File file = new File(applyPathMap(name)); + if (file.isAbsolute() && file.exists() && file.isFile()) { + res = new Object[]{ new LocalFileStorage(file) }; + } + else { + res = super.findSourceElements(name); + } + ArrayList<Object> list = new ArrayList<Object>(); + for (Object o : res) { + if (o instanceof IStorage && !(o instanceof IFile)) { + IPath path = ((IStorage)o).getFullPath(); + if (path != null) { + URI uri = URIUtil.toURI(path); + IFile[] arr = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri); + if (arr != null && arr.length > 0) { + int cnt = list.size(); + for (IFile fileResource : arr) { + if (fileResource.isAccessible()) { + list.add(fileResource); + } + } + if (list.size() > cnt) continue; + } + } + } + list.add(o); + } + return list.toArray(new Object[list.size()]); + } + + @Override + public Object[] findSourceElements(Object object) throws CoreException { + String name = getSourceName(object); + if (name == null) return null; + if (cache.containsKey(name)) return cache.get(name); + Object[] res = findSource(name); + cache.put(name, res); + return res; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourcePathComputerDelegate.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourcePathComputerDelegate.java new file mode 100644 index 000000000..2b5b74776 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFSourcePathComputerDelegate.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.launch; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashSet; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.sourcelookup.ISourceContainer; +import org.eclipse.debug.core.sourcelookup.ISourcePathComputerDelegate; +import org.eclipse.debug.core.sourcelookup.containers.ProjectSourceContainer; +import org.eclipse.debug.core.sourcelookup.containers.WorkspaceSourceContainer; + +/** + * Computes the default source lookup path for a TCF launch configuration. The + * default source lookup path is the project containing the TCF + * program being launched. If the program is not specified, the workspace is + * searched by default. + */ +public class TCFSourcePathComputerDelegate implements ISourcePathComputerDelegate { + + public ISourceContainer[] computeSourceContainers( + ILaunchConfiguration configuration, IProgressMonitor monitor) + throws CoreException { + ArrayList<ISourceContainer> res = new ArrayList<ISourceContainer>(); + String project_name = configuration.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_NAME, (String)null); + String program_name = configuration.getAttribute(TCFLaunchDelegate.ATTR_LOCAL_PROGRAM_FILE, (String)null); + String path = TCFLaunchDelegate.getProgramPath(project_name, program_name); + if (path != null) { + URI uri = URIUtil.toURI(new Path(path)); + IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri); + if (files != null && files.length > 0) { + HashSet<IProject> projects = new HashSet<IProject>(); + for (IFile file : files) projects.add(file.getProject()); + for (IProject project : projects) res.add(new ProjectSourceContainer(project, true)); + } + } + if (res.size() == 0 && project_name != null && project_name.length() > 0) { + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(project_name); + if (project != null) res.add(new ProjectSourceContainer(project, true)); + } + if (res.size() == 0) res.add(new WorkspaceSourceContainer()); + return res.toArray(new ISourceContainer[res.size()]); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFUserDefPeer.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFUserDefPeer.java new file mode 100644 index 000000000..a045fdbf4 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/launch/TCFUserDefPeer.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.launch; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.tcf.core.AbstractPeer; +import org.eclipse.tm.tcf.protocol.IPeer; +import org.eclipse.tm.tcf.protocol.Protocol; + +/** + * The class represents manually configured (user defined) TCF peers (targets). + * Unlike auto-discovered peers, manually configured ones are persistent - + * they exist until explicitly deleted by user. + * Eclipse plug-in state storage is used to keep the configuration data. + */ +public class TCFUserDefPeer extends AbstractPeer { + + public TCFUserDefPeer(Map<String, String> attrs) { + super(attrs); + } + + /** + * Load manually configured peers from persistent storage. + */ + public static void loadPeers() { + try { + assert Protocol.isDispatchThread(); + IPath path = Activator.getDefault().getStateLocation(); + File f = path.append("peers.ini").toFile(); + if (!f.exists()) return; + HashMap<String,String> attrs = new HashMap<String,String>(); + BufferedReader rd = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + for (;;) { + String s = rd.readLine(); + if (s == null) break; + if (s.length() == 0) { + new TCFUserDefPeer(attrs); + attrs = new HashMap<String,String>(); + } + else { + int i = s.indexOf('='); + if (i > 0) attrs.put(s.substring(0, i), s.substring(i + 1)); + } + } + rd.close(); + } + catch (Exception x) { + Activator.log("Cannot read peer list", x); + } + } + + /** + * Save manually configured peers to persistent storage. + */ + public static void savePeers() { + try { + assert Protocol.isDispatchThread(); + IPath path = Activator.getDefault().getStateLocation(); + File f = path.append("peers.ini").toFile(); + BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8")); + for (IPeer peer : Protocol.getLocator().getPeers().values()) { + if (peer instanceof TCFUserDefPeer) { + Map<String,String> attrs = peer.getAttributes(); + for (String nm : attrs.keySet()) { + wr.write(nm); + wr.write('='); + wr.write(attrs.get(nm)); + wr.newLine(); + } + wr.newLine(); + } + } + wr.close(); + } + catch (Exception x) { + Activator.log("Cannot save peer list", x); + } + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFBreakpointListener.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFBreakpointListener.java new file mode 100644 index 000000000..80ce441e1 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFBreakpointListener.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +public interface ITCFBreakpointListener { + + public void breakpointStatusChanged(String id); + public void breakpointRemoved(String id); + public void breakpointChanged(String id); + +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFConstants.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFConstants.java new file mode 100644 index 000000000..ab5d7179a --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/ITCFConstants.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +public interface ITCFConstants { + + /** + * Unique identifier for the TCF debug model + */ + public static final String ID_TCF_DEBUG_MODEL = "org.eclipse.tm.tcf.debug"; + +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpoint.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpoint.java new file mode 100644 index 000000000..3a3528e6b --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpoint.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +import java.util.Map; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.WorkspaceJob; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.model.Breakpoint; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; + + +public class TCFBreakpoint extends Breakpoint { + + public static final String MARKER_TYPE = "org.eclipse.tm.tcf.debug.breakpoint.marker"; + + private static final String[] attr_names = { + TCFBreakpointsModel.ATTR_ADDRESS, "address", + TCFBreakpointsModel.ATTR_FUNCTION, "location", + TCFBreakpointsModel.ATTR_EXPRESSION, "expression", + TCFBreakpointsModel.ATTR_CONDITION, "condition", + TCFBreakpointsModel.ATTR_CONTEXTNAMES, "scope (names)", + TCFBreakpointsModel.ATTR_CONTEXTIDS, "scope (IDs)", + TCFBreakpointsModel.ATTR_EXE_PATHS, "scope (modules)", + TCFBreakpointsModel.ATTR_STOP_GROUP, "stop group", + }; + + private static long last_id = 0; + + private static String createNewID() { + assert Protocol.isDispatchThread(); + long id = System.currentTimeMillis(); + if (id <= last_id) id = last_id + 1; + last_id = id; + return Long.toHexString(id); + } + + public static TCFBreakpoint createFromMarkerAttributes(Map<String,Object> attrs) throws CoreException { + assert !Protocol.isDispatchThread(); + assert attrs.get(TCFBreakpointsModel.ATTR_ID) != null; + TCFBreakpoint bp = new TCFBreakpoint(); + IResource resource = ResourcesPlugin.getWorkspace().getRoot(); + IMarker marker = resource.createMarker(MARKER_TYPE); + bp.setMarker(marker); + marker.setAttributes(attrs); + DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(bp); + return bp; + } + + public static TCFBreakpoint createFromTCFProperties(Map<String,Object> props) { + assert Protocol.isDispatchThread(); + if (props.get(IBreakpoints.PROP_ID) == null) props.put(IBreakpoints.PROP_ID, createNewID()); + final TCFBreakpoint bp = new TCFBreakpoint(); + final Map<String,Object> m = Activator.getBreakpointsModel().toMarkerAttributes(props); + final IResource resource = ResourcesPlugin.getWorkspace().getRoot(); + final ISchedulingRule rule = bp.getMarkerRule(resource); + Job job = new WorkspaceJob("Add Breakpoint") { //$NON-NLS-1$ + @Override + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + IMarker marker = resource.createMarker(MARKER_TYPE); + bp.setMarker(marker); + marker.setAttributes(m); + DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(bp); + return Status.OK_STATUS; + } + }; + job.setRule(rule); + job.setPriority(Job.SHORT); + job.setSystem(true); + job.schedule(); + return bp; + } + + public TCFBreakpoint() { + } + + @Override + public void setEnabled(boolean b) throws CoreException { + if (!Activator.getBreakpointsModel().isLocal(getMarker())) return; + super.setEnabled(b); + } + + public String getModelIdentifier() { + return ITCFConstants.ID_TCF_DEBUG_MODEL; + } + + public String getText() { + IMarker marker = getMarker(); + if (marker == null) return null; + StringBuffer bf = new StringBuffer(); + for (int i = 0; i < attr_names.length; i += 2) { + String s = marker.getAttribute(attr_names[i], null); + if (s == null || s.length() == 0) continue; + bf.append('['); + bf.append(attr_names[i + 1]); + bf.append(": "); + bf.append(s); + bf.append(']'); + } + if (bf.length() == 0) { + String id = marker.getAttribute( + ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_ID, null); + bf.append(id); + } + return bf.toString(); + } + + public void notifyStatusChaged() throws CoreException { + IMarker marker = getMarker(); + if (marker == null) return; + int cnt = 0; + String status = marker.getAttribute(TCFBreakpointsModel.ATTR_STATUS, null); + if (status != null) cnt = Integer.parseInt(status); + setAttribute(TCFBreakpointsModel.ATTR_STATUS, Integer.toString(cnt + 1)); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsModel.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsModel.java new file mode 100644 index 000000000..e796716cb --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsModel.java @@ -0,0 +1,676 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.IBreakpointListener; +import org.eclipse.debug.core.IBreakpointManager; +import org.eclipse.debug.core.IBreakpointManagerListener; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.model.IBreakpoint; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; + +/** + * TCFBreakpointsModel class handles breakpoints for all active TCF launches. + * It downloads initial set of breakpoint data when launch is activated, + * listens for Eclipse breakpoint manager events and propagates breakpoint changes to TCF targets. + */ +public class TCFBreakpointsModel { + + public static final String + CDATA_CLIENT_ID = "ClientID", + CDATA_TYPE = "Type", + CDATA_FILE = "File", + CDATA_MARKER = "Marker"; + + public static final String + ATTR_ID = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_ID, + ATTR_STATUS = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + "Status", + ATTR_INSTALL_COUNT = "org.eclipse.cdt.debug.core.installCount", + ATTR_ADDRESS = "org.eclipse.cdt.debug.core.address", + ATTR_FUNCTION = "org.eclipse.cdt.debug.core.function", + ATTR_EXPRESSION = "org.eclipse.cdt.debug.core.expression", + ATTR_READ = "org.eclipse.cdt.debug.core.read", + ATTR_WRITE = "org.eclipse.cdt.debug.core.write", + ATTR_SIZE = "org.eclipse.cdt.debug.core.range", + ATTR_FILE = "org.eclipse.cdt.debug.core.sourceHandle", + ATTR_CONDITION = "org.eclipse.cdt.debug.core.condition", + ATTR_IGNORE_COUNT = "org.eclipse.cdt.debug.core.ignoreCount", + ATTR_CONTEXTNAMES = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_CONTEXTNAMES, + ATTR_CONTEXTIDS = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_CONTEXTIDS, + ATTR_EXE_PATHS = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_EXECUTABLEPATHS, + ATTR_STOP_GROUP = ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + IBreakpoints.PROP_STOP_GROUP; + + private final IBreakpointManager bp_manager = DebugPlugin.getDefault().getBreakpointManager(); + private final HashMap<IChannel,Map<String,Object>> channels = new HashMap<IChannel,Map<String,Object>>(); + private final HashMap<String,IBreakpoint> id2bp = new HashMap<String,IBreakpoint>(); + private final String client_id = UUID.randomUUID().toString(); + + private abstract class BreakpointUpdate implements Runnable { + + private final IBreakpoint breakpoint; + private boolean removed; + protected final Map<String,Object> marker_attrs; + protected final boolean is_local; + protected final String marker_id; + private final String marker_file; + private final String marker_type; + + IBreakpoints service; + IBreakpoints.DoneCommand done; + Map<String,Object> tcf_attrs; + + BreakpointUpdate(IBreakpoint breakpoint, boolean removed) throws CoreException, IOException { + this.breakpoint = breakpoint; + this.removed = removed; + IMarker marker = breakpoint.getMarker(); + marker_attrs = new HashMap<String,Object>(marker.getAttributes()); + is_local = isLocal(marker); + marker_file = getFilePath(breakpoint.getMarker().getResource()); + marker_type = breakpoint.getMarker().getType(); + marker_id = getBreakpointID(breakpoint); + } + + synchronized void exec() throws InterruptedException { + assert !Protocol.isDispatchThread(); + if (marker_id != null) { + Protocol.invokeLater(this); + wait(); + } + } + + public void run() { + if (disposed) return; + if (removed) id2bp.remove(marker_id); + else id2bp.put(marker_id, breakpoint); + if (is_local) { + for (final IChannel channel : channels.keySet()) { + tcf_attrs = toBreakpointAttributes(channel, marker_id, marker_file, marker_type, marker_attrs); + service = channel.getRemoteService(IBreakpoints.class); + if (!isSupported(channel, breakpoint)) continue; + done = new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) channel.terminate(error); + } + }; + update(); + } + } + Protocol.sync(new Runnable() { + public void run() { + synchronized (BreakpointUpdate.this) { + BreakpointUpdate.this.notify(); + } + } + }); + }; + + abstract void update(); + } + + private final IBreakpointListener breakpoint_listener = new IBreakpointListener() { + + public void breakpointAdded(final IBreakpoint breakpoint) { + try { + new BreakpointUpdate(breakpoint, false) { + @Override + void update() { + service.add(tcf_attrs, done); + } + }.exec(); + } + catch (Throwable x) { + Activator.log("Unhandled exception in breakpoint listener", x); + } + } + + private Set<String> calcMarkerDeltaKeys(IMarker marker, IMarkerDelta delta) throws CoreException { + Set<String> keys = new HashSet<String>(); + if (delta == null) return keys; + Map<String,Object> m0 = delta.getAttributes(); + Map<String,Object> m1 = marker.getAttributes(); + if (m0 != null) keys.addAll(m0.keySet()); + if (m1 != null) keys.addAll(m1.keySet()); + for (Iterator<String> i = keys.iterator(); i.hasNext();) { + String key = i.next(); + Object v0 = m0 != null ? m0.get(key) : null; + Object v1 = m1 != null ? m1.get(key) : null; + if (v0 instanceof String && ((String)v0).length() == 0) v0 = null; + if (v1 instanceof String && ((String)v1).length() == 0) v1 = null; + if (v0 instanceof Boolean && !((Boolean)v0).booleanValue()) v0 = null; + if (v1 instanceof Boolean && !((Boolean)v1).booleanValue()) v1 = null; + if ((v0 == null) != (v1 == null)) continue; + if (v0 != null && !v0.equals(v1)) continue; + i.remove(); + } + keys.remove(ATTR_INSTALL_COUNT); + keys.remove(ATTR_STATUS); + return keys; + } + + public void breakpointChanged(final IBreakpoint breakpoint, IMarkerDelta delta) { + try { + final Set<String> s = calcMarkerDeltaKeys(breakpoint.getMarker(), delta); + if (s.isEmpty()) return; + new BreakpointUpdate(breakpoint, false) { + @Override + void update() { + if (s.size() == 1 && s.contains(IBreakpoint.ENABLED)) { + Boolean enabled = (Boolean)tcf_attrs.get(IBreakpoints.PROP_ENABLED); + if (enabled == null || !enabled.booleanValue()) { + service.disable(new String[]{ marker_id }, done); + } + else { + service.enable(new String[]{ marker_id }, done); + } + } + else { + service.change(tcf_attrs, done); + } + } + }.exec(); + } + catch (Throwable x) { + Activator.log("Unhandled exception in breakpoint listener", x); + } + } + + public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { + try { + new BreakpointUpdate(breakpoint, true) { + @Override + void update() { + service.remove(new String[]{ marker_id }, done); + } + }.exec(); + } + catch (Throwable x) { + Activator.log("Unhandled exception in breakpoint listener", x); + } + } + }; + + private final IBreakpointManagerListener manager_listener = new IBreakpointManagerListener() { + + public void breakpointManagerEnablementChanged(final boolean enabled) { + try { + IBreakpoint[] arr = bp_manager.getBreakpoints(); + if (arr == null || arr.length == 0) return; + final Map<String,IBreakpoint> map = new HashMap<String,IBreakpoint>(); + for (int i = 0; i < arr.length; i++) { + IMarker marker = arr[i].getMarker(); + Boolean b = marker.getAttribute(IBreakpoint.ENABLED, Boolean.FALSE); + if (!b.booleanValue()) continue; + String id = getBreakpointID(arr[i]); + if (id == null) continue; + map.put(id, arr[i]); + } + if (map.isEmpty()) return; + Runnable r = new Runnable() { + public void run() { + if (disposed) return; + for (final IChannel channel : channels.keySet()) { + IBreakpoints service = channel.getRemoteService(IBreakpoints.class); + Set<String> ids = new HashSet<String>(); + for (String id : map.keySet()) { + IBreakpoint bp = map.get(id); + if (isSupported(channel, bp)) ids.add(id); + } + IBreakpoints.DoneCommand done = new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) channel.terminate(error); + } + }; + if (enabled) { + service.enable(ids.toArray(new String[ids.size()]), done); + } + else { + service.disable(ids.toArray(new String[ids.size()]), done); + } + } + Protocol.sync(new Runnable() { + public void run() { + synchronized (map) { + map.notify(); + } + } + }); + } + }; + synchronized (map) { + assert !Protocol.isDispatchThread(); + Protocol.invokeLater(r); + map.wait(); + } + } + catch (Throwable x) { + Activator.log("Unhandled exception in breakpoint listener", x); + } + } + }; + + private boolean disposed; + + public static TCFBreakpointsModel getBreakpointsModel() { + return Activator.getBreakpointsModel(); + } + + public void dispose() { + bp_manager.removeBreakpointListener(breakpoint_listener); + bp_manager.removeBreakpointManagerListener(manager_listener); + channels.clear(); + disposed = true; + } + + public boolean isSupported(IChannel channel, IBreakpoint bp) { + // TODO: implement per-channel breakpoint filtering + return true; + } + + /** + * Get TCF ID of a breakpoint. + * @param bp - IBreakpoint object. + * @return TCF ID of the breakpoint. + * @throws CoreException + */ + public static String getBreakpointID(IBreakpoint bp) throws CoreException { + IMarker marker = bp.getMarker(); + String id = (String)marker.getAttributes().get(ATTR_ID); + if (id != null) return id; + id = marker.getResource().getLocationURI().toString(); + if (id == null) return null; + return id + ':' + marker.getId(); + } + + /** + * Get IBreakpoint for given TCF breakpoint ID. + * The mapping works only for breakpoints that were sent to (or received from) a debug target. + * It can be used to map target responses to IBreakpoint objects. + * @param id - TCF breakpoint ID. + * @return IBreakpoint object associated with the ID, or null. + */ + public IBreakpoint getBreakpoint(String id) { + assert Protocol.isDispatchThread(); + return id2bp.get(id); + } + + /** + * Check breakpoint ownership. + * @param properties - breakpoint properties as reported by TCF Breakpoints service. + * @return true if the breakpoint is owned by local instance of Eclipse. + * Return false if the properties represent a breakpoint created by some other TCF client. + */ + @SuppressWarnings("unchecked") + public boolean isLocal(Map<String,Object> properties) { + if (properties == null) return false; + Map<String,Object> client_data = (Map<String,Object>)properties.get(IBreakpoints.PROP_CLIENT_DATA); + if (client_data == null) return false; + String id = (String)client_data.get(TCFBreakpointsModel.CDATA_CLIENT_ID); + return client_id.equals(id); + } + + /** + * Check breakpoint marker ownership. + * @param marker - breakpoint marker. + * @return true if the marker is owned by local instance of Eclipse. + * Return false if the marker represents a breakpoint created by some other TCF client. + */ + public boolean isLocal(IMarker marker) { + try { + Map<String,Object> marker_attrs = marker.getAttributes(); + if (marker_attrs == null) return false; + return !Boolean.FALSE.equals(marker_attrs.get(IBreakpoint.PERSISTED)); + } + catch (CoreException e) { + return false; + } + } + + /** + * Download breakpoint info to a target. + * This is supposed to be done once after a channel is opened. + * @param channel - TCF communication channel. + * @param done - client callback. + * @throws IOException + * @throws CoreException + */ + @SuppressWarnings("unchecked") + public void downloadBreakpoints(final IChannel channel, final Runnable done) throws IOException, CoreException { + assert !disposed; + assert Protocol.isDispatchThread(); + final IBreakpoints service = channel.getRemoteService(IBreakpoints.class); + if (service != null) { + service.getCapabilities(null, new IBreakpoints.DoneGetCapabilities() { + public void doneGetCapabilities(IToken token, Exception error, Map<String,Object> capabilities) { + if (channel.getState() != IChannel.STATE_OPEN) { + Protocol.invokeLater(done); + return; + } + if (channels.isEmpty()) { + bp_manager.addBreakpointListener(breakpoint_listener); + bp_manager.addBreakpointManagerListener(manager_listener); + } + channel.addChannelListener(new IChannel.IChannelListener() { + public void congestionLevel(int level) { + } + public void onChannelClosed(Throwable error) { + if (disposed) return; + channels.remove(channel); + if (channels.isEmpty()) { + bp_manager.removeBreakpointListener(breakpoint_listener); + bp_manager.removeBreakpointManagerListener(manager_listener); + id2bp.clear(); + } + } + public void onChannelOpened() { + } + }); + channels.put(channel, capabilities); + IBreakpoint[] arr = bp_manager.getBreakpoints(); + if (arr != null && arr.length > 0) { + List<Map<String,Object>> bps = new ArrayList<Map<String,Object>>(arr.length); + for (int i = 0; i < arr.length; i++) { + try { + if (!isSupported(channel, arr[i])) continue; + String id = getBreakpointID(arr[i]); + if (id == null) continue; + if (!arr[i].isPersisted()) continue; + IMarker marker = arr[i].getMarker(); + String file = getFilePath(marker.getResource()); + bps.add(toBreakpointAttributes(channel, id, file, marker.getType(), marker.getAttributes())); + id2bp.put(id, arr[i]); + } + catch (Exception x) { + Activator.log("Cannot get breakpoint attributes", x); + } + } + if (!bps.isEmpty()) { + Map<String, Object>[] bp_arr = (Map<String,Object>[])bps.toArray(new Map[bps.size()]); + service.set(bp_arr, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error == null) done.run(); + else channel.terminate(error); + } + }); + return; + } + } + Protocol.invokeLater(done); + } + }); + } + } + + private String getFilePath(IResource resource) throws IOException { + if (resource == ResourcesPlugin.getWorkspace().getRoot()) return null; + IPath p = resource.getRawLocation(); + if (p == null) return null; + return p.toFile().getCanonicalPath(); + } + + /** + * Translate TCF breakpoint properties to Eclipse breakpoint marker attributes. + * @param p - TCF breakpoint properties. + * @return Eclipse marker attributes. + */ + @SuppressWarnings("unchecked") + public Map<String,Object> toMarkerAttributes(Map<String,Object> p) { + assert !disposed; + assert Protocol.isDispatchThread(); + Map<String,Object> client_data = (Map<String,Object>)p.get(IBreakpoints.PROP_CLIENT_DATA); + if (client_data != null) { + Map<String,Object> m = (Map<String,Object>)client_data.get(CDATA_MARKER); + if (m != null) return m; + } + Map<String,Object> m = new HashMap<String,Object>(); + for (Map.Entry<String,Object> e : p.entrySet()) { + String key = e.getKey(); + Object val = e.getValue(); + if (key.equals(IBreakpoints.PROP_ENABLED)) continue; + if (key.equals(IBreakpoints.PROP_FILE)) continue; + if (key.equals(IBreakpoints.PROP_LINE)) continue; + if (key.equals(IBreakpoints.PROP_COLUMN)) continue; + if (key.equals(IBreakpoints.PROP_LOCATION)) continue; + if (key.equals(IBreakpoints.PROP_ACCESSMODE)) continue; + if (key.equals(IBreakpoints.PROP_SIZE)) continue; + if (key.equals(IBreakpoints.PROP_CONDITION)) continue; + if (val instanceof String[]) { + StringBuffer bf = new StringBuffer(); + for (String s : (String[])val) { + if (bf.length() > 0) bf.append(','); + bf.append(s); + } + if (bf.length() == 0) continue; + val = bf.toString(); + } + else if (val instanceof Collection) { + StringBuffer bf = new StringBuffer(); + for (String s : (Collection<String>)val) { + if (bf.length() > 0) bf.append(','); + bf.append(s); + } + if (bf.length() == 0) continue; + val = bf.toString(); + } + m.put(ITCFConstants.ID_TCF_DEBUG_MODEL + '.' + key, val); + } + Boolean enabled = (Boolean)p.get(IBreakpoints.PROP_ENABLED); + if (enabled == null) m.put(IBreakpoint.ENABLED, Boolean.FALSE); + else m.put(IBreakpoint.ENABLED, enabled); + String location = (String)p.get(IBreakpoints.PROP_LOCATION); + if (location != null && location.length() > 0) { + int access_mode = IBreakpoints.ACCESSMODE_EXECUTE; + Number access_mode_num = (Number)p.get(IBreakpoints.PROP_ACCESSMODE); + if (access_mode_num != null) access_mode = access_mode_num.intValue(); + if ((access_mode & IBreakpoints.ACCESSMODE_EXECUTE) != 0) { + if (Character.isDigit(location.charAt(0))) { + m.put(ATTR_ADDRESS, location); + } + else { + m.put(ATTR_FUNCTION, location); + } + } + else { + m.put(ATTR_EXPRESSION, location.replaceFirst("^&\\((.+)\\)$", "$1")); + m.put(ATTR_READ, (access_mode & IBreakpoints.ACCESSMODE_READ) != 0); + m.put(ATTR_WRITE, (access_mode & IBreakpoints.ACCESSMODE_WRITE) != 0); + } + Number size_num = (Number)p.get(IBreakpoints.PROP_SIZE); + if (size_num != null) m.put(ATTR_SIZE, size_num.toString()); + } + m.put(IBreakpoint.REGISTERED, Boolean.TRUE); + m.put(IBreakpoint.PERSISTED, Boolean.TRUE); + m.put(IBreakpoint.ID, ITCFConstants.ID_TCF_DEBUG_MODEL); + String msg = ""; + if (location != null) msg += location; + m.put(IMarker.MESSAGE, "Breakpoint: " + msg); + String file = (String)p.get(IBreakpoints.PROP_FILE); + if (file != null && file.length() > 0) { + m.put(ATTR_FILE, file); + } + Number line = (Number)p.get(IBreakpoints.PROP_LINE); + if (line != null) { + m.put(IMarker.LINE_NUMBER, new Integer(line.intValue())); + Number column = (Number)p.get(IBreakpoints.PROP_COLUMN); + if (column != null) { + m.put(IMarker.CHAR_START, new Integer(column.intValue())); + m.put(IMarker.CHAR_END, new Integer(column.intValue() + 1)); + } + } + String condition = (String)p.get(IBreakpoints.PROP_CONDITION); + if (condition != null && condition.length() > 0) m.put(ATTR_CONDITION, condition); + Number ignore_count = (Number)p.get(IBreakpoints.PROP_IGNORECOUNT); + if (ignore_count != null) m.put(ATTR_IGNORE_COUNT, ignore_count); + return m; + } + + /** + * Translate Eclipse breakpoint marker attributes to TCF breakpoint properties. + * @param channel - TCF communication channel. + * @param id - breakpoint ID. + * @param file - the maker file or null. + * @param type - the marker type. + * @param p - the marker attributes. + * @return TCF breakpoint properties. + */ + public Map<String,Object> toBreakpointAttributes(IChannel channel, String id, String file, String type, Map<String,Object> p) { + assert !disposed; + assert Protocol.isDispatchThread(); + Map<String,Object> m = new HashMap<String,Object>(); + Map<String,Object> capabilities = channels.get(channel); + Map<String,Object> client_data = null; + if (capabilities != null) { + Object obj = capabilities.get(IBreakpoints.CAPABILITY_CLIENT_DATA); + if (obj instanceof Boolean && ((Boolean)obj).booleanValue()) client_data = new HashMap<String,Object>(); + } + m.put(IBreakpoints.PROP_ID, id); + if (client_data != null) { + m.put(IBreakpoints.PROP_CLIENT_DATA, client_data); + client_data.put(CDATA_CLIENT_ID, client_id); + if (type != null) client_data.put(CDATA_TYPE, type); + if (file != null) client_data.put(CDATA_FILE, file); + client_data.put(CDATA_MARKER, p); + } + for (Map.Entry<String,Object> e : p.entrySet()) { + String key = e.getKey(); + Object val = e.getValue(); + if (key.startsWith(ITCFConstants.ID_TCF_DEBUG_MODEL)) { + String tcf_key = key.substring(ITCFConstants.ID_TCF_DEBUG_MODEL.length() + 1); + if (IBreakpoints.PROP_CONTEXTIDS.equals(tcf_key)) { + val = filterContextIds(channel, ((String)val).split(",\\s*")); + } + else if (IBreakpoints.PROP_CONTEXTNAMES.equals(tcf_key) || + IBreakpoints.PROP_STOP_GROUP.equals(tcf_key) || + IBreakpoints.PROP_EXECUTABLEPATHS.equals(tcf_key)) { + val = ((String)val).split(",\\s*"); + } + m.put(tcf_key, val); + } + } + Boolean enabled = (Boolean)p.get(IBreakpoint.ENABLED); + if (enabled != null && enabled.booleanValue() && bp_manager.isEnabled()) { + m.put(IBreakpoints.PROP_ENABLED, enabled); + } + if (file == null) file = (String)p.get(ATTR_FILE); + if (file != null && file.length() > 0) { + String name = file; + boolean file_mapping = false; + if (capabilities != null) { + Object obj = capabilities.get(IBreakpoints.CAPABILITY_FILE_MAPPING); + if (obj instanceof Boolean) file_mapping = ((Boolean)obj).booleanValue(); + } + if (!file_mapping) { + int i = file.lastIndexOf('/'); + int j = file.lastIndexOf('\\'); + if (i > j) name = file.substring(i + 1); + else if (i < j) name = file.substring(j + 1); + } + m.put(IBreakpoints.PROP_FILE, name); + Integer line = (Integer)p.get(IMarker.LINE_NUMBER); + if (line != null) { + m.put(IBreakpoints.PROP_LINE, new Integer(line.intValue())); + Integer column = (Integer)p.get(IMarker.CHAR_START); + if (column != null) m.put(IBreakpoints.PROP_COLUMN, column); + } + } + if (p.get(ATTR_EXPRESSION) != null) { + String expr = (String)p.get(ATTR_EXPRESSION); + if (expr != null && expr.length() != 0) { + boolean writeAccess = Boolean.TRUE.equals(p.get(ATTR_WRITE)); + boolean readAccess = Boolean.TRUE.equals(p.get(ATTR_READ)); + int accessMode = 0; + if (readAccess) accessMode |= IBreakpoints.ACCESSMODE_READ; + if (writeAccess) accessMode |= IBreakpoints.ACCESSMODE_WRITE; + m.put(IBreakpoints.PROP_ACCESSMODE, Integer.valueOf(accessMode)); + Object range = p.get(ATTR_SIZE); + if (range != null) { + int size = Integer.parseInt(range.toString()); + if (size > 0) m.put(IBreakpoints.PROP_SIZE, size); + } + if (!Character.isDigit(expr.charAt(0))) { + expr = "&(" + expr + ')'; + } + m.put(IBreakpoints.PROP_LOCATION, expr); + } + } + else if (p.get(ATTR_FUNCTION) != null) { + String expr = (String)p.get(ATTR_FUNCTION); + if (expr != null && expr.length() != 0) m.put(IBreakpoints.PROP_LOCATION, expr); + } + else if (file == null) { + String address = (String)p.get(ATTR_ADDRESS); + if (address != null && address.length() > 0) m.put(IBreakpoints.PROP_LOCATION, address); + } + String condition = (String)p.get(ATTR_CONDITION); + if (condition != null && condition.length() > 0) m.put(IBreakpoints.PROP_CONDITION, condition); + Number ignore_count = (Number)p.get(ATTR_IGNORE_COUNT); + if (ignore_count != null && ignore_count.intValue() > 0) m.put(IBreakpoints.PROP_IGNORECOUNT, ignore_count); + return m; + } + + /** + * Filter given array of scope IDs of the form sessionId/contextId + * to those applicable to the given channel. + */ + private String[] filterContextIds(IChannel channel, String[] scopeIds) { + String sessionId = getSessionId(channel); + List<String> contextIds = new ArrayList<String>(); + for (String scopeId : scopeIds) { + if (scopeId.length() == 0) continue; + int slash = scopeId.indexOf('/'); + if (slash < 0) { + contextIds.add(scopeId); + } + else if (sessionId != null && sessionId.equals(scopeId.substring(0, slash))) { + contextIds.add(scopeId.substring(slash+1)); + } + } + return (String[]) contextIds.toArray(new String[contextIds.size()]); + } + + /** + * @return launch config name for given channel or <code>null</code> + */ + private String getSessionId(IChannel channel) { + ILaunch[] launches = DebugPlugin.getDefault().getLaunchManager().getLaunches(); + for (ILaunch launch : launches) { + if (launch instanceof TCFLaunch) { + if (channel == ((TCFLaunch) launch).getChannel()) { + ILaunchConfiguration lc = launch.getLaunchConfiguration(); + return lc != null ? lc.getName() : null; + } + } + } + return null; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsStatus.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsStatus.java new file mode 100644 index 000000000..f65428522 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFBreakpointsStatus.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.model.IBreakpoint; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; + + +public class TCFBreakpointsStatus { + + private final IBreakpoints service; + private final Map<String,Map<String,Object>> breakpoints = new HashMap<String,Map<String,Object>>(); + private final Map<String,Map<String,Object>> status = new HashMap<String,Map<String,Object>>(); + private final Set<ITCFBreakpointListener> listeners = new HashSet<ITCFBreakpointListener>(); + + private static final Map<String,Object> status_not_supported = new HashMap<String,Object>(); + + static { + status_not_supported.put(IBreakpoints.STATUS_ERROR, "Not supported"); + } + + TCFBreakpointsStatus(TCFLaunch launch) { + assert Protocol.isDispatchThread(); + service = launch.getChannel().getRemoteService(IBreakpoints.class); + if (service != null) { + final IBreakpoints.BreakpointsListener listener = new IBreakpoints.BreakpointsListener() { + + public void breakpointStatusChanged(String id, Map<String,Object> m) { + assert Protocol.isDispatchThread(); + if (status.get(id) == null) return; + status.put(id, m); + for (Iterator<ITCFBreakpointListener> i = listeners.iterator(); i.hasNext();) { + i.next().breakpointStatusChanged(id); + } + } + + public void contextAdded(Map<String,Object>[] bps) { + for (Map<String,Object> bp : bps) { + String id = (String)bp.get(IBreakpoints.PROP_ID); + breakpoints.put(id, bp); + if (status.get(id) != null) continue; + status.put(id, new HashMap<String,Object>()); + for (Iterator<ITCFBreakpointListener> i = listeners.iterator(); i.hasNext();) { + i.next().breakpointStatusChanged(id); + } + } + } + + public void contextChanged(Map<String,Object>[] bps) { + for (Map<String,Object> bp : bps) { + String id = (String)bp.get(IBreakpoints.PROP_ID); + breakpoints.put(id, bp); + if (!status.containsKey(id)) continue; + for (Iterator<ITCFBreakpointListener> i = listeners.iterator(); i.hasNext();) { + i.next().breakpointChanged(id); + } + } + } + + public void contextRemoved(String[] ids) { + for (String id : ids) { + breakpoints.remove(id); + if (!status.containsKey(id)) continue; + for (Iterator<ITCFBreakpointListener> i = listeners.iterator(); i.hasNext();) { + i.next().breakpointRemoved(id); + } + status.remove(id); + } + } + }; + service.addListener(listener); + + // query foreign breakpoints + service.getIDs(new IBreakpoints.DoneGetIDs() { + @SuppressWarnings("unchecked") + public void doneGetIDs(IToken token, Exception error, String[] ids) { + if (error != null || ids == null) return; + for (final String id : ids) { + service.getProperties(id, new IBreakpoints.DoneGetProperties() { + public void doneGetProperties(IToken token, Exception error, Map<String,Object> props) { + if (error == null) { + listener.contextAdded((Map<String,Object>[]) new Map[] { props }); + service.getStatus(id, new IBreakpoints.DoneGetStatus() { + public void doneGetStatus(IToken token, Exception error, Map<String,Object> status) { + if (error == null) listener.breakpointStatusChanged(id, status); + } + }); + } + } + }); + } + } + }); + } + } + + public Set<String> getStatusIDs() { + return status.keySet(); + } + + public Map<String,Object> getStatus(String id) { + assert id != null; + assert Protocol.isDispatchThread(); + if (service == null) return status_not_supported; + return status.get(id); + } + + public Map<String,Object> getProperties(String id) { + assert id != null; + assert Protocol.isDispatchThread(); + return breakpoints.get(id); + } + + public Map<String,Object> getStatus(IBreakpoint bp) { + try { + String id = TCFBreakpointsModel.getBreakpointID(bp); + if (id == null) return status_not_supported; + return getStatus(id); + } + catch (CoreException e) { + return status_not_supported; + } + } + + public void addListener(ITCFBreakpointListener listener) { + assert Protocol.isDispatchThread(); + listeners.add(listener); + } + + public void removeListener(ITCFBreakpointListener listener) { + assert Protocol.isDispatchThread(); + listeners.remove(listener); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFContextState.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFContextState.java new file mode 100644 index 000000000..62eca91b2 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFContextState.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.model; + +import java.util.Map; + +public class TCFContextState { + public boolean is_suspended; + + public String suspend_pc; + public String suspend_reason; + public Map<String,Object> suspend_params; +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFError.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFError.java new file mode 100644 index 000000000..967b658ac --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFError.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.debug.core.DebugException; +import org.eclipse.tm.internal.tcf.debug.Activator; + + +public class TCFError extends DebugException { + + private static final long serialVersionUID = -4261097789666829020L; + + public TCFError(Throwable exception) { + super(new Status(exception)); + } + + private static class Status implements IStatus { + + private final Throwable exception; + + private Status(Throwable exception) { + this.exception = exception; + } + + public IStatus[] getChildren() { + return null; + } + + public int getCode() { + return 1; + } + + public Throwable getException() { + return exception; + } + + public String getMessage() { + return exception.getMessage(); + } + + public String getPlugin() { + return Activator.PLUGIN_ID; + } + + public int getSeverity() { + return ERROR; + } + + public boolean isMultiStatus() { + return false; + } + + public boolean isOK() { + return false; + } + + public boolean matches(int severityMask) { + return false; + } + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFFunctionRef.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFFunctionRef.java new file mode 100644 index 000000000..e76b721d3 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFFunctionRef.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.model; + +import java.math.BigInteger; + +/** + * Objects of this class represent a mapping between an address and a function. + */ +public class TCFFunctionRef { + public String context_id; + public int address_size; + public BigInteger address; + public String symbol_id; + public Throwable error; + + public String toString() { + StringBuffer bf = new StringBuffer(); + bf.append('['); + bf.append(context_id); + bf.append(','); + bf.append(address_size); + bf.append(','); + bf.append(address); + bf.append(','); + bf.append(symbol_id); + bf.append(','); + bf.append(error); + bf.append(']'); + return bf.toString(); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFLaunch.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFLaunch.java new file mode 100644 index 000000000..885b8b18f --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFLaunch.java @@ -0,0 +1,1318 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.model; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.Launch; +import org.eclipse.tm.internal.tcf.debug.Activator; +import org.eclipse.tm.internal.tcf.debug.actions.TCFAction; +import org.eclipse.tm.internal.tcf.debug.launch.TCFLaunchDelegate; +import org.eclipse.tm.internal.tcf.debug.launch.TCFLaunchDelegate.PathMapRule; +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IPeer; +import org.eclipse.tm.tcf.protocol.IService; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IFileSystem; +import org.eclipse.tm.tcf.services.IFileSystem.FileSystemException; +import org.eclipse.tm.tcf.services.IFileSystem.IFileHandle; +import org.eclipse.tm.tcf.services.IMemory; +import org.eclipse.tm.tcf.services.IMemory.MemoryContext; +import org.eclipse.tm.tcf.services.IMemoryMap; +import org.eclipse.tm.tcf.services.IPathMap; +import org.eclipse.tm.tcf.services.IProcesses; +import org.eclipse.tm.tcf.services.IProcesses.ProcessContext; +import org.eclipse.tm.tcf.services.IProcessesV1; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IStreams; +import org.eclipse.tm.tcf.util.TCFTask; + +public class TCFLaunch extends Launch { + + public interface LaunchListener { + + public void onCreated(TCFLaunch launch); + + public void onConnected(TCFLaunch launch); + + public void onDisconnected(TCFLaunch launch); + + public void onProcessOutput(TCFLaunch launch, String process_id, int stream_id, byte[] data); + + public void onProcessStreamError( + TCFLaunch launch, String process_id, int stream_id, + Exception error, int lost_size); + } + + public interface ActionsListener { + + public void onContextActionStart(TCFAction action); + + public void onContextActionResult(String id, String result); + + public void onContextActionDone(TCFAction action); + } + + private abstract class LaunchStep implements Runnable { + + LaunchStep() { + launch_steps.add(this); + } + + abstract void start() throws Exception; + + void done() { + if (channel.getState() != IChannel.STATE_OPEN) return; + try { + launch_steps.removeFirst().start(); + } + catch (Throwable x) { + channel.terminate(x); + } + } + + public void run() { + done(); + } + } + + private static final Collection<LaunchListener> listeners = new ArrayList<LaunchListener>(); + private static LaunchListener[] listeners_array; + + private final Collection<ActionsListener> action_listeners = new ArrayList<ActionsListener>(); + + private IChannel channel; + private Throwable error; + private TCFBreakpointsStatus breakpoints_status; + private String mode; + private boolean connecting; + private boolean disconnecting; + private boolean disconnected; + private boolean shutdown; + private boolean last_context_exited; + private long actions_interval; + + private final HashSet<Object> pending_clients = new HashSet<Object>(); + private long pending_clients_timestamp; + + private String peer_name; + + private Runnable update_memory_maps; + + private ProcessContext process; + private Collection<Map<String,Object>> process_signals; + private IToken process_start_command; + private String process_input_stream_id; + private boolean process_exited; + private int process_exit_code; + private final HashMap<String,String> process_env = new HashMap<String,String>(); + + private final HashMap<String,TCFAction> active_actions = new HashMap<String,TCFAction>(); + private final HashMap<String,LinkedList<TCFAction>> context_action_queue = new HashMap<String,LinkedList<TCFAction>>(); + private final HashMap<String,Long> context_action_timestamps = new HashMap<String,Long>(); + private final HashMap<String,String> stream_ids = new HashMap<String,String>(); + private final LinkedList<LaunchStep> launch_steps = new LinkedList<LaunchStep>(); + private final LinkedList<String> redirection_path = new LinkedList<String>(); + + private ArrayList<PathMapRule> filepath_map; + + private Set<String> context_filter; + + private boolean supports_memory_map_preloading; + + private final IStreams.StreamsListener streams_listener = new IStreams.StreamsListener() { + + public void created(String stream_type, String stream_id, String context_id) { + stream_ids.put(stream_id, context_id); + if (process_start_command == null) { + disconnectStream(stream_id); + } + } + + public void disposed(String stream_type, String stream_id) { + } + }; + + private final IProcesses.ProcessesListener prs_listener = new IProcesses.ProcessesListener() { + + public void exited(String process_id, int exit_code) { + if (process_id.equals(process.getID())) { + process_exit_code = exit_code; + process_exited = true; + } + } + }; + + private static LaunchListener[] getListeners() { + if (listeners_array != null) return listeners_array; + return listeners_array = listeners.toArray(new LaunchListener[listeners.size()]); + } + + public TCFLaunch(ILaunchConfiguration launchConfiguration, String mode) { + super(launchConfiguration, mode, null); + for (LaunchListener l : getListeners()) l.onCreated(TCFLaunch.this); + } + + private void onConnected() throws Exception { + // The method is called when TCF channel is successfully connected. + + final ILaunchConfiguration cfg = getLaunchConfiguration(); + if (cfg != null) { + // Send file path map: + if (getService(IPathMap.class) != null) { + new LaunchStep() { + @Override + void start() throws Exception { + downloadPathMaps(cfg, this); + } + }; + } + } + + if (redirection_path.size() > 0) { + // Connected to intermediate peer (value-add). + // Redirect to next peer: + new LaunchStep() { + @Override + void start() throws Exception { + channel.redirect(redirection_path.removeFirst()); + } + }; + } + else { + final IStreams streams = getService(IStreams.class); + if (streams != null) { + // Subscribe Streams service: + new LaunchStep() { + @Override + void start() { + final Set<IToken> cmds = new HashSet<IToken>(); + String[] nms = { IProcesses.NAME, IProcessesV1.NAME }; + for (String s : nms) { + if (channel.getRemoteService(s) == null) continue; + cmds.add(streams.subscribe(s, streams_listener, new IStreams.DoneSubscribe() { + public void doneSubscribe(IToken token, Exception error) { + cmds.remove(token); + if (error != null) channel.terminate(error); + if (cmds.size() == 0) done(); + } + })); + } + if (cmds.size() == 0) done(); + } + }; + } + + if (mode.equals(ILaunchManager.DEBUG_MODE)) { + String attach_to_context = getAttribute("attach_to_context"); + if (attach_to_context != null) { + context_filter = new HashSet<String>(); + context_filter.add(attach_to_context); + } + final IMemoryMap mem_map = channel.getRemoteService(IMemoryMap.class); + if (mem_map != null) { + // Send manual memory map items: + new LaunchStep() { + @Override + void start() throws Exception { + final Runnable done = this; + // Check if preloading is supported + mem_map.set("\001", null, new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + try { + supports_memory_map_preloading = error == null; + if (!supports_memory_map_preloading) { + // Older agents (up to ver. 0.4) don't support preloading of memory maps. + updateMemoryMapsOnProcessCreation(cfg, done); + } + else { + downloadMemoryMaps(cfg, done); + } + } + catch (Exception x) { + channel.terminate(x); + } + } + }); + } + }; + } + // Send breakpoints: + new LaunchStep() { + @Override + void start() throws Exception { + breakpoints_status = new TCFBreakpointsStatus(TCFLaunch.this); + Activator.getBreakpointsModel().downloadBreakpoints(channel, this); + } + }; + } + + // Call client launch sequence: + new LaunchStep() { + @Override + void start() { + runLaunchSequence(this); + } + }; + + if (cfg != null) startRemoteProcess(cfg); + + // Final launch step. + // Notify clients: + new LaunchStep() { + @Override + void start() { + connecting = false; + for (LaunchListener l : getListeners()) l.onConnected(TCFLaunch.this); + fireChanged(); + } + }; + } + + launch_steps.removeFirst().start(); + } + + private void onDisconnected(Throwable error) { + // The method is called when TCF channel is closed. + assert !disconnected; + assert !shutdown; + this.error = error; + breakpoints_status = null; + connecting = false; + disconnected = true; + for (LaunchListener l : getListeners()) l.onDisconnected(this); + if (DebugPlugin.getDefault() != null) fireChanged(); + runShutdownSequence(new Runnable() { + public void run() { + shutdown = true; + if (DebugPlugin.getDefault() != null) fireTerminate(); + } + }); + } + + protected void runLaunchSequence(Runnable done) { + done.run(); + } + + private void downloadMemoryMaps(final ILaunchConfiguration cfg, final Runnable done) throws Exception { + final IMemoryMap mmap = channel.getRemoteService(IMemoryMap.class); + if (mmap == null) { + done.run(); + return; + } + final HashMap<String,ArrayList<IMemoryMap.MemoryRegion>> maps = new HashMap<String,ArrayList<IMemoryMap.MemoryRegion>>(); + TCFLaunchDelegate.getMemMapsAttribute(maps, cfg); + final HashSet<IToken> cmds = new HashSet<IToken>(); // Pending commands + final Runnable done_all = new Runnable() { + boolean launch_done; + public void run() { + if (launch_done) return; + done.run(); + launch_done = true; + } + }; + final IMemoryMap.DoneSet done_set_mmap = new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + assert cmds.contains(token); + cmds.remove(token); + if (error != null) Activator.log("Cannot update context memory map", error); + if (cmds.isEmpty()) done_all.run(); + } + }; + for (String id : maps.keySet()) { + ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id); + TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]); + cmds.add(mmap.set(id, arr, done_set_mmap)); + } + update_memory_maps = new Runnable() { + public void run() { + try { + Set<String> set = new HashSet<String>(maps.keySet()); + maps.clear(); + TCFLaunchDelegate.getMemMapsAttribute(maps, cfg); + for (String id : maps.keySet()) { + ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id); + TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]); + cmds.add(mmap.set(id, arr, done_set_mmap)); + } + for (String id : set) { + if (maps.get(id) != null) continue; + cmds.add(mmap.set(id, null, done_set_mmap)); + } + } + catch (Throwable x) { + channel.terminate(x); + } + } + }; + if (cmds.isEmpty()) done_all.run(); + } + + private void updateMemoryMapsOnProcessCreation(ILaunchConfiguration cfg, final Runnable done) throws Exception { + final IMemory mem = channel.getRemoteService(IMemory.class); + final IMemoryMap mmap = channel.getRemoteService(IMemoryMap.class); + if (mem == null || mmap == null) { + done.run(); + return; + } + final HashSet<String> deleted_maps = new HashSet<String>(); + final HashMap<String,ArrayList<IMemoryMap.MemoryRegion>> maps = new HashMap<String,ArrayList<IMemoryMap.MemoryRegion>>(); + TCFLaunchDelegate.getMemMapsAttribute(maps, cfg); + final HashSet<String> mems = new HashSet<String>(); // Already processed memory IDs + final HashSet<IToken> cmds = new HashSet<IToken>(); // Pending commands + final HashMap<String,String> mem2map = new HashMap<String,String>(); + final Runnable done_all = new Runnable() { + boolean launch_done; + public void run() { + mems.clear(); + deleted_maps.clear(); + if (launch_done) return; + done.run(); + launch_done = true; + } + }; + final IMemoryMap.DoneSet done_set_mmap = new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + cmds.remove(token); + if (error != null) Activator.log("Cannot update context memory map", error); + if (cmds.isEmpty()) done_all.run(); + } + }; + final IMemory.DoneGetContext done_get_context = new IMemory.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, MemoryContext context) { + cmds.remove(token); + if (context != null && mems.add(context.getID())) { + String id = context.getName(); + if (id == null) id = context.getID(); + if (id != null) { + ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id); + if (map != null) { + TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]); + cmds.add(mmap.set(context.getID(), arr, done_set_mmap)); + mem2map.put(context.getID(), id); + } + else if (deleted_maps.contains(id)) { + cmds.add(mmap.set(context.getID(), null, done_set_mmap)); + mem2map.remove(context.getID()); + } + } + } + if (cmds.isEmpty()) done_all.run(); + } + }; + final IMemory.DoneGetChildren done_get_children = new IMemory.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] ids) { + cmds.remove(token); + if (ids != null) { + for (String id : ids) { + cmds.add(mem.getChildren(id, this)); + cmds.add(mem.getContext(id, done_get_context)); + } + } + if (cmds.isEmpty()) done_all.run(); + } + }; + cmds.add(mem.getChildren(null, done_get_children)); + mem.addListener(new IMemory.MemoryListener() { + public void memoryChanged(String context_id, Number[] addr, long[] size) { + } + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + mems.remove(id); + mem2map.remove(id); + } + } + public void contextChanged(MemoryContext[] contexts) { + for (MemoryContext context : contexts) { + String id = context.getName(); + if (id == null) id = context.getID(); + if (id == null) continue; + if (id.equals(mem2map.get(context.getID()))) continue; + ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id); + if (map == null) continue; + TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]); + cmds.add(mmap.set(context.getID(), arr, done_set_mmap)); + mem2map.put(context.getID(), id); + } + } + public void contextAdded(MemoryContext[] contexts) { + for (MemoryContext context : contexts) { + if (!mems.add(context.getID())) continue; + String id = context.getName(); + if (id == null) id = context.getID(); + if (id == null) continue; + ArrayList<IMemoryMap.MemoryRegion> map = maps.get(id); + if (map == null) continue; + TCFMemoryRegion[] arr = map.toArray(new TCFMemoryRegion[map.size()]); + cmds.add(mmap.set(context.getID(), arr, done_set_mmap)); + mem2map.put(context.getID(), id); + } + } + }); + update_memory_maps = new Runnable() { + public void run() { + try { + maps.clear(); + mems.clear(); + TCFLaunchDelegate.getMemMapsAttribute(maps, getLaunchConfiguration()); + for (String id : mem2map.values()) { + if (maps.get(id) == null) deleted_maps.add(id); + } + cmds.add(mem.getChildren(null, done_get_children)); + } + catch (Throwable x) { + channel.terminate(x); + } + } + }; + } + + private void readPathMapConfiguration(ILaunchConfiguration cfg) throws CoreException { + String s = cfg.getAttribute(TCFLaunchDelegate.ATTR_PATH_MAP, ""); + filepath_map = TCFLaunchDelegate.parsePathMapAttribute(s); + s = cfg.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, ""); + filepath_map.addAll(TCFLaunchDelegate.parseSourceLocatorMemento(s)); + } + + private void downloadPathMaps(ILaunchConfiguration cfg, final Runnable done) throws Exception { + readPathMapConfiguration(cfg); + final IPathMap path_map_service = getService(IPathMap.class); + path_map_service.set(filepath_map.toArray(new IPathMap.PathMapRule[filepath_map.size()]), new IPathMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) channel.terminate(error); + else done.run(); + } + }); + } + + private String[] toArgsArray(String file, String cmd) { + // Create arguments list from a command line. + int i = 0; + int l = cmd.length(); + List<String> arr = new ArrayList<String>(); + arr.add(file); + for (;;) { + while (i < l && cmd.charAt(i) == ' ') i++; + if (i >= l) break; + String s = null; + if (cmd.charAt(i) == '"') { + i++; + StringBuffer bf = new StringBuffer(); + while (i < l) { + char ch = cmd.charAt(i++); + if (ch == '"') break; + if (ch == '\\' && i < l) ch = cmd.charAt(i++); + bf.append(ch); + } + s = bf.toString(); + } + else { + int i0 = i; + while (i < l && cmd.charAt(i) != ' ') i++; + s = cmd.substring(i0, i); + } + arr.add(s); + } + return arr.toArray(new String[arr.size()]); + } + + private void copyFileToRemoteTarget(String local_file, String remote_file, final Runnable done) { + if (local_file == null) { + channel.terminate(new Exception("Program does not exist")); + return; + } + final IFileSystem fs = channel.getRemoteService(IFileSystem.class); + if (fs == null) { + channel.terminate(new Exception( + "Cannot download program file: target does not provide File System service")); + return; + } + try { + final InputStream inp = new FileInputStream(local_file); + int flags = IFileSystem.TCF_O_WRITE | IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC; + fs.open(remote_file, flags, null, new IFileSystem.DoneOpen() { + + IFileHandle handle; + long offset = 0; + final Set<IToken> cmds = new HashSet<IToken>(); + final byte[] buf = new byte[0x1000]; + + public void doneOpen(IToken token, FileSystemException error, IFileHandle handle) { + this.handle = handle; + if (error != null) { + TCFLaunch.this.error = new Exception("Cannot download program file", error); + fireChanged(); + done.run(); + } + else { + write_next(); + } + } + + private void write_next() { + try { + while (cmds.size() < 8) { + int rd = inp.read(buf); + if (rd < 0) { + close(); + break; + } + cmds.add(fs.write(handle, offset, buf, 0, rd, new IFileSystem.DoneWrite() { + + public void doneWrite(IToken token, FileSystemException error) { + cmds.remove(token); + if (error != null) channel.terminate(error); + else write_next(); + } + })); + offset += rd; + } + } + catch (Throwable x) { + channel.terminate(x); + } + } + + private void close() { + if (cmds.size() > 0) return; + try { + inp.close(); + fs.close(handle, new IFileSystem.DoneClose() { + + public void doneClose(IToken token, FileSystemException error) { + if (error != null) channel.terminate(error); + else done.run(); + } + }); + } + catch (Throwable x) { + channel.terminate(x); + } + } + }); + } + catch (Throwable x) { + channel.terminate(x); + } + } + + @SuppressWarnings("unchecked") + private void startRemoteProcess(final ILaunchConfiguration cfg) throws Exception { + final String project = cfg.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_NAME, ""); + final String local_file = cfg.getAttribute(TCFLaunchDelegate.ATTR_LOCAL_PROGRAM_FILE, ""); + final String remote_file = cfg.getAttribute(TCFLaunchDelegate.ATTR_REMOTE_PROGRAM_FILE, ""); + if (local_file.length() != 0 && remote_file.length() != 0) { + // Download executable file + new LaunchStep() { + @Override + void start() throws Exception { + copyFileToRemoteTarget(TCFLaunchDelegate.getProgramPath(project, local_file), remote_file, this); + } + }; + } + final String attach_to_process = getAttribute("attach_to_process"); + if (attach_to_process != null) { + final IProcesses ps = channel.getRemoteService(IProcesses.class); + if (ps == null) throw new Exception("Target does not provide Processes service"); + // Attach the process + new LaunchStep() { + @Override + void start() { + IProcesses.DoneGetContext done = new IProcesses.DoneGetContext() { + public void doneGetContext(IToken token, final Exception error, final ProcessContext process) { + if (error != null) { + channel.terminate(error); + } + else { + process.attach(new IProcesses.DoneCommand() { + public void doneCommand(IToken token, final Exception error) { + if (error != null) { + channel.terminate(error); + } + else { + context_filter = new HashSet<String>(); + context_filter.add(process.getID()); + TCFLaunch.this.process = process; + ps.addListener(prs_listener); + connectProcessStreams(); + done(); + } + } + }); + } + } + }; + ps.getContext(attach_to_process, done); + } + }; + } + else if (local_file.length() != 0 || remote_file.length() != 0) { + final IProcesses ps = channel.getRemoteService(IProcesses.class); + if (ps == null) throw new Exception("Target does not provide Processes service"); + final boolean append = cfg.getAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true); + if (append) { + // Get system environment variables + new LaunchStep() { + @Override + void start() throws Exception { + ps.getEnvironment(new IProcesses.DoneGetEnvironment() { + public void doneGetEnvironment(IToken token, Exception error, Map<String,String> env) { + if (error != null) { + channel.terminate(error); + } + else { + if (env != null) process_env.putAll(env); + done(); + } + } + }); + } + }; + } + final String dir = cfg.getAttribute(TCFLaunchDelegate.ATTR_WORKING_DIRECTORY, ""); + final String args = cfg.getAttribute(TCFLaunchDelegate.ATTR_PROGRAM_ARGUMENTS, ""); + final Map<String,String> env = cfg.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, (Map<String,String>)null); + final boolean attach_children = cfg.getAttribute(TCFLaunchDelegate.ATTR_ATTACH_CHILDREN, true); + final boolean use_terminal = cfg.getAttribute(TCFLaunchDelegate.ATTR_USE_TERMINAL, true); + // Start the process + new LaunchStep() { + @Override + void start() { + if (env != null) process_env.putAll(env); + String file = remote_file; + if (file == null || file.length() == 0) file = TCFLaunchDelegate.getProgramPath(project, local_file); + if (file == null || file.length() == 0) { + channel.terminate(new Exception("Program file does not exist")); + return; + } + IProcesses.DoneStart done = new IProcesses.DoneStart() { + public void doneStart(IToken token, final Exception error, ProcessContext process) { + process_start_command = null; + if (error != null) { + for (String id : new HashSet<String>(stream_ids.keySet())) disconnectStream(id); + Protocol.sync(new Runnable() { + public void run() { + channel.terminate(error); + } + }); + } + else { + context_filter = new HashSet<String>(); + context_filter.add(process.getID()); + TCFLaunch.this.process = process; + ps.addListener(prs_listener); + connectProcessStreams(); + done(); + } + } + }; + String[] args_arr = toArgsArray(file, args); + IProcessesV1 ps_v1 = channel.getRemoteService(IProcessesV1.class); + if (ps_v1 != null) { + Map<String,Object> params = new HashMap<String,Object>(); + if (mode.equals(ILaunchManager.DEBUG_MODE)) { + params.put(IProcessesV1.START_ATTACH, true); + if (attach_children) params.put(IProcessesV1.START_ATTACH_CHILDREN, true); + } + if (use_terminal) params.put(IProcessesV1.START_USE_TERMINAL, true); + process_start_command = ps_v1.start(dir, file, args_arr, process_env, params, done); + } + else { + boolean attach = mode.equals(ILaunchManager.DEBUG_MODE); + process_start_command = ps.start(dir, file, args_arr, process_env, attach, done); + } + } + }; + if (mode.equals(ILaunchManager.DEBUG_MODE)) { + // Get process signal list + new LaunchStep() { + @Override + void start() { + ps.getSignalList(process.getID(), new IProcesses.DoneGetSignalList() { + public void doneGetSignalList(IToken token, Exception error, Collection<Map<String,Object>> list) { + if (error != null) Activator.log("Can't get process signal list", error); + process_signals = list; + done(); + } + }); + } + }; + // Set process signal masks + String dont_stop = cfg.getAttribute(TCFLaunchDelegate.ATTR_SIGNALS_DONT_STOP, ""); + String dont_pass = cfg.getAttribute(TCFLaunchDelegate.ATTR_SIGNALS_DONT_PASS, ""); + final int no_stop = dont_stop.length() > 0 ? Integer.parseInt(dont_stop, 16) : 0; + final int no_pass = dont_pass.length() > 0 ? Integer.parseInt(dont_pass, 16) : 0; + if (no_stop != 0 || no_pass != 0) { + new LaunchStep() { + @Override + void start() { + final HashSet<IToken> cmds = new HashSet<IToken>(); + final IProcesses.DoneCommand done_set_mask = new IProcesses.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + cmds.remove(token); + if (error != null) channel.terminate(error); + else if (cmds.size() == 0) done(); + } + }; + cmds.add(ps.setSignalMask(process.getID(), no_stop, no_pass, done_set_mask)); + final IRunControl rc = channel.getRemoteService(IRunControl.class); + if (rc != null) { + final IRunControl.DoneGetChildren done_get_children = new IRunControl.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + if (context_ids != null) { + for (String id : context_ids) { + cmds.add(ps.setSignalMask(id, no_stop, no_pass, done_set_mask)); + cmds.add(rc.getChildren(id, this)); + } + } + cmds.remove(token); + if (error != null) channel.terminate(error); + else if (cmds.size() == 0) done(); + } + }; + cmds.add(rc.getChildren(process.getID(), done_get_children)); + } + } + }; + } + } + } + } + + private void connectProcessStreams() { + assert process_start_command == null; + final IStreams streams = getService(IStreams.class); + if (streams == null) return; + final String inp_id = (String)process.getProperties().get(IProcesses.PROP_STDIN_ID); + final String out_id = (String)process.getProperties().get(IProcesses.PROP_STDOUT_ID); + final String err_id = (String)process.getProperties().get(IProcesses.PROP_STDERR_ID); + for (final String id : stream_ids.keySet().toArray(new String[stream_ids.size()])) { + if (id.equals(inp_id)) { + process_input_stream_id = id; + } + else if (id.equals(out_id)) { + connectStream(id, 0); + } + else if (id.equals(err_id)) { + connectStream(id, 1); + } + else { + disconnectStream(id); + } + } + } + + private void connectStream(final String id, final int no) { + final String peocess_id = process.getID(); + final IStreams streams = getService(IStreams.class); + IStreams.DoneRead done = new IStreams.DoneRead() { + public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) { + if (stream_ids.get(id) == null) return; + if (lost_size > 0) { + Exception x = new IOException("Process output data lost due buffer overflow"); + for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, peocess_id, no, x, lost_size); + } + if (data != null && data.length > 0) { + for (LaunchListener l : getListeners()) l.onProcessOutput(TCFLaunch.this, peocess_id, no, data); + } + if (error != null) { + for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, peocess_id, no, error, 0); + } + if (eos || error != null) { + disconnectStream(id); + } + else { + streams.read(id, 0x1000, this); + } + } + }; + streams.read(id, 0x1000, done); + streams.read(id, 0x1000, done); + streams.read(id, 0x1000, done); + streams.read(id, 0x1000, done); + } + + private void disconnectStream(String id) { + assert stream_ids.get(id) != null; + stream_ids.remove(id); + if (channel.getState() != IChannel.STATE_OPEN) return; + IStreams streams = getService(IStreams.class); + streams.disconnect(id, new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + if (channel.getState() != IChannel.STATE_OPEN) return; + if (error != null) channel.terminate(error); + } + }); + } + + protected void runShutdownSequence(final Runnable done) { + done.run(); + } + + /*--------------------------------------------------------------------------------------------*/ + + public Throwable getError() { + return error; + } + + public void setError(Throwable x) { + error = x; + if (x != null) { + if (channel != null && channel.getState() == IChannel.STATE_OPEN) { + channel.terminate(x); + } + else if (!connecting) { + disconnected = true; + } + } + fireChanged(); + } + + public TCFBreakpointsStatus getBreakpointsStatus() { + return breakpoints_status; + } + + /** + * Check if the agent supports setting of user defined memory map entries + * for a context that does not exits yet. + * @return true if memory map preloading is supported. + */ + public boolean isMemoryMapPreloadingSupported() { + return supports_memory_map_preloading; + } + + public static void addListener(LaunchListener listener) { + assert Protocol.isDispatchThread(); + listeners.add(listener); + listeners_array = null; + } + + public static void removeListener(LaunchListener listener) { + assert Protocol.isDispatchThread(); + listeners.remove(listener); + listeners_array = null; + } + + public void launchConfigurationChanged(final ILaunchConfiguration cfg) { + super.launchConfigurationChanged(cfg); + if (!cfg.equals(getLaunchConfiguration())) return; + if (channel != null && channel.getState() == IChannel.STATE_OPEN) { + new TCFTask<Boolean>(channel) { + public void run() { + try { + if (update_memory_maps != null) update_memory_maps.run(); + if (filepath_map != null) { + readPathMapConfiguration(cfg); + final IPathMap path_map_service = getService(IPathMap.class); + path_map_service.set(filepath_map.toArray(new IPathMap.PathMapRule[filepath_map.size()]), new IPathMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) channel.terminate(error); + done(false); + } + }); + } + else { + done(true); + } + } + catch (Throwable x) { + channel.terminate(x); + done(false); + } + } + }.getE(); + // TODO: update signal masks when launch configuration changes + } + } + + /** Thread safe method */ + public IChannel getChannel() { + return channel; + } + + public IProcesses.ProcessContext getProcessContext() { + return process; + } + + public void writeProcessInputStream(byte[] buf, int pos, final int len) throws Exception { + assert Protocol.isDispatchThread(); + final String id = process_input_stream_id; + if (channel.getState() != IChannel.STATE_OPEN) throw new IOException("Connection closed"); + if (process == null) throw new IOException("No target process"); + final String prs = process.getID(); + IStreams streams = getService(IStreams.class); + if (streams == null) throw new IOException("Streams service not available"); + if (stream_ids.get(id) == null) throw new IOException("Input stream not available"); + streams.write(id, buf, pos, len, new IStreams.DoneWrite() { + public void doneWrite(IToken token, Exception error) { + if (error == null) return; + if (stream_ids.get(id) == null) return; + for (LaunchListener l : getListeners()) l.onProcessStreamError(TCFLaunch.this, prs, 0, error, len); + disconnectStream(id); + } + }); + } + + public boolean isConnecting() { + return connecting; + } + + public void onLastContextRemoved() { + ILaunchConfiguration cfg = getLaunchConfiguration(); + try { + if (cfg.getAttribute(TCFLaunchDelegate.ATTR_DISCONNECT_ON_CTX_EXIT, true)) { + last_context_exited = true; + closeChannel(); + } + } + catch (Throwable e) { + Activator.log("Cannot access launch configuration", e); + } + } + + public void closeChannel() { + assert Protocol.isDispatchThread(); + if (channel == null) return; + if (channel.getState() == IChannel.STATE_CLOSED) return; + if (disconnecting) return; + disconnecting = true; + final Set<IToken> cmds = new HashSet<IToken>(); + if (process != null && !process_exited) { + cmds.add(process.terminate(new IProcesses.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + cmds.remove(token); + if (error != null) channel.terminate(error); + else if (cmds.isEmpty()) channel.close(); + } + })); + } + IStreams streams = getService(IStreams.class); + for (String id : stream_ids.keySet()) { + cmds.add(streams.disconnect(id, new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + cmds.remove(token); + if (error != null) channel.terminate(error); + else if (cmds.isEmpty()) channel.close(); + } + })); + } + stream_ids.clear(); + process_input_stream_id = null; + if (cmds.isEmpty()) channel.close(); + } + + public IPeer getPeer() { + assert Protocol.isDispatchThread(); + return channel.getRemotePeer(); + } + + public String getPeerName() { + // Safe to call from any thread. + return peer_name; + } + + public <V extends IService> V getService(Class<V> cls) { + assert Protocol.isDispatchThread(); + return channel.getRemoteService(cls); + } + + public boolean canDisconnect() { + return !disconnected; + } + + public boolean isDisconnected() { + return disconnected; + } + + public void disconnect() throws DebugException { + try { + new TCFTask<Boolean>() { + public void run() { + closeChannel(); + done(true); + } + }.get(); + } + catch (IllegalStateException x) { + // Don't report this exception - it means Eclipse is being shut down + disconnected = true; + } + catch (Exception x) { + throw new TCFError(x); + } + } + + public boolean canTerminate() { + return false; + } + + public boolean isTerminated() { + return disconnected; + } + + public void terminate() throws DebugException { + } + + public boolean isExited() { + return last_context_exited; + } + + public int getExitCode() { + return process_exit_code; + } + + public Collection<Map<String,Object>> getSignalList() { + return process_signals; + } + + public ArrayList<PathMapRule> getFilePathMap() { + assert Protocol.isDispatchThread(); + return filepath_map; + } + + /** + * Activate TCF launch: open communication channel and perform all necessary launch steps. + * @param mode - on of launch mode constants defined in ILaunchManager. + * @param id - TCF peer ID. + */ + public void launchTCF(String mode, String id) { + assert Protocol.isDispatchThread(); + this.mode = mode; + try { + if (id == null || id.length() == 0) throw new IOException("Invalid peer ID"); + redirection_path.clear(); + for (;;) { + int i = id.indexOf('/'); + if (i <= 0) { + redirection_path.add(id); + break; + } + redirection_path.add(id.substring(0, i)); + id = id.substring(i + 1); + } + String id0 = redirection_path.removeFirst(); + IPeer peer = Protocol.getLocator().getPeers().get(id0); + if (peer == null) throw new Exception("Cannot locate peer " + id0); + peer_name = peer.getName(); + channel = peer.openChannel(); + channel.addChannelListener(new IChannel.IChannelListener() { + + public void onChannelOpened() { + try { + peer_name = getPeer().getName(); + onConnected(); + } + catch (Throwable x) { + channel.terminate(x); + } + } + + public void congestionLevel(int level) { + } + + public void onChannelClosed(Throwable error) { + channel.removeChannelListener(this); + onDisconnected(error); + } + + }); + assert channel.getState() == IChannel.STATE_OPENING; + connecting = true; + } + catch (Throwable e) { + onDisconnected(e); + } + } + + /****************************************************************************************************************/ + + private long getActionTimeStamp(String id) { + Long l = context_action_timestamps.get(id); + if (l == null) return 0; + return l.longValue(); + } + + private void startAction(final String id) { + if (active_actions.get(id) != null) return; + LinkedList<TCFAction> list = context_action_queue.get(id); + if (list == null || list.size() == 0) return; + final TCFAction action = list.removeFirst(); + if (list.size() == 0) context_action_queue.remove(id); + active_actions.put(id, action); + final long timestamp = getActionTimeStamp(id); + long time = System.currentTimeMillis(); + Protocol.invokeLater(timestamp + actions_interval - time, new Runnable() { + public void run() { + if (active_actions.get(id) != action) return; + long time = System.currentTimeMillis(); + synchronized (pending_clients) { + if (pending_clients.size() > 0) { + if (time - timestamp < actions_interval + 1000) { + Protocol.invokeLater(20, this); + return; + } + pending_clients.clear(); + } + else if (time < pending_clients_timestamp + 10) { + Protocol.invokeLater(pending_clients_timestamp + 10 - time, this); + return; + } + } + context_action_timestamps.put(id, time); + for (ActionsListener l : action_listeners) l.onContextActionStart(action); + action.run(); + } + }); + } + + /** + * Add an object to the set of pending clients. + * Actions execution will be delayed until the set is empty, + * but not longer then 1 second. + * @param client + */ + public void addPendingClient(Object client) { + synchronized (pending_clients) { + pending_clients.add(client); + pending_clients_timestamp = System.currentTimeMillis(); + } + } + + /** + * Remove an object from the set of pending clients. + * Actions execution resumes when the set becomes empty. + * @param client + */ + public void removePendingClient(Object client) { + synchronized (pending_clients) { + if (pending_clients.remove(client) && pending_clients.size() == 0) { + pending_clients_timestamp = System.currentTimeMillis(); + } + } + } + + /** + * Set minimum interval between context actions execution. + * @param interval - minimum interval in milliseconds. + */ + public void setContextActionsInterval(long interval) { + actions_interval = interval; + } + + /** + * Add a context action to actions queue. + * Examples of context actions are resume/suspend/step commands, + * which were requested by a user. + * @param action + */ + public void addContextAction(TCFAction action) { + assert Protocol.isDispatchThread(); + String id = action.getContextID(); + LinkedList<TCFAction> list = context_action_queue.get(id); + if (list == null) context_action_queue.put(id, list = new LinkedList<TCFAction>()); + int priority = action.getPriority(); + for (ListIterator<TCFAction> i = list.listIterator();;) { + if (i.hasNext()) { + if (priority <= i.next().getPriority()) continue; + i.previous(); + } + i.add(action); + break; + } + startAction(id); + } + + /** + * Set action result for given context ID. + * Action results are usually presented to a user same way as context suspend reasons. + * @param id - debug context ID. + * @param result - a string to be shown to user. + */ + public void setContextActionResult(String id, String result) { + assert Protocol.isDispatchThread(); + for (ActionsListener l : action_listeners) l.onContextActionResult(id, result); + } + + /** + * Remove an action from the queue. + * The method should be called when the action execution is done. + * @param action + */ + public void removeContextAction(TCFAction action) { + assert Protocol.isDispatchThread(); + String id = action.getContextID(); + assert active_actions.get(id) == action; + active_actions.remove(id); + for (ActionsListener l : action_listeners) l.onContextActionDone(action); + startAction(id); + } + + /** + * Remove all actions from the queue of a debug context. + * @param id - debug context ID. + */ + public void removeContextActions(String id) { + assert Protocol.isDispatchThread(); + context_action_queue.remove(id); + context_action_timestamps.remove(id); + } + + /** + * Get action queue size of a debug context. + * @param id - debug context ID. + * @return count of pending actions. + */ + public int getContextActionsCount(String id) { + assert Protocol.isDispatchThread(); + LinkedList<TCFAction> list = context_action_queue.get(id); + int n = list == null ? 0 : list.size(); + if (active_actions.get(id) != null) n++; + return n; + } + + /** + * Add a listener that will be notified when an action execution is started or finished, + * or when an action result is posted. + * @param l - action listener. + */ + public void addActionsListener(ActionsListener l) { + action_listeners.add(l); + } + + /** + * Remove an action listener that was registered with addActionsListener(). + * @param l - action listener. + */ + public void removeActionsListener(ActionsListener l) { + action_listeners.remove(l); + } + + public Set<String> getContextFilter() { + return context_filter; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFMemoryRegion.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFMemoryRegion.java new file mode 100644 index 000000000..8017fe254 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFMemoryRegion.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.model; + +import java.math.BigInteger; +import java.util.Map; + +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.services.IMemoryMap; +import org.eclipse.tm.tcf.services.IMemoryMap.MemoryRegion; + +public class TCFMemoryRegion implements MemoryRegion, Comparable<TCFMemoryRegion> { + + private final Map<String,Object> props; + + public final BigInteger addr; + public final BigInteger size; + + public TCFMemoryRegion(Map<String,Object> props) { + this.props = props; + this.addr = JSON.toBigInteger((Number)props.get(IMemoryMap.PROP_ADDRESS)); + this.size = JSON.toBigInteger((Number)props.get(IMemoryMap.PROP_SIZE)); + } + + public Number getAddress() { + return addr; + } + + public Number getSize() { + return size; + } + + public Number getOffset() { + return (Number)props.get(IMemoryMap.PROP_OFFSET); + } + + public String getFileName() { + return (String)props.get(IMemoryMap.PROP_FILE_NAME); + } + + public String getSectionName() { + return (String)props.get(IMemoryMap.PROP_SECTION_NAME); + } + + public int getFlags() { + Number n = (Number)props.get(IMemoryMap.PROP_FLAGS); + if (n != null) return n.intValue(); + return 0; + } + + public Map<String,Object> getProperties() { + return props; + } + + public int compareTo(TCFMemoryRegion r) { + if (addr == null && r.addr == null) return 0; + if (addr == null) return -1; + if (r.addr == null) return +1; + return addr.compareTo(r.addr); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFSourceRef.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFSourceRef.java new file mode 100644 index 000000000..79c93a428 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/model/TCFSourceRef.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.model; + +import java.math.BigInteger; + +import org.eclipse.tm.tcf.services.ILineNumbers; + +/** + * Objects of this class represent a mapping between an address and source code area. + */ +public class TCFSourceRef { + public String context_id; + public int address_size; + public BigInteger address; + public ILineNumbers.CodeArea area; + public Throwable error; + + public String toString() { + StringBuffer bf = new StringBuffer(); + bf.append('['); + bf.append(context_id); + bf.append(','); + bf.append(address_size); + bf.append(','); + bf.append(address); + bf.append(','); + bf.append(area); + bf.append(','); + bf.append(error); + bf.append(']'); + return bf.toString(); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/ITCFTest.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/ITCFTest.java new file mode 100644 index 000000000..1fc67adbd --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/ITCFTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +/** + * Each (sub)test in TCF Test Suite should implement this interface. + */ +interface ITCFTest { + + /** + * Start execution of the test. + */ + void start(); + + /** + * Check if the test don't need the context to stay suspended. + * @param id - run control context ID. + * @return true if it is OK to resume the context. + */ + boolean canResume(String id); +}
\ No newline at end of file diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/Main.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/Main.java new file mode 100644 index 000000000..f5f0e27ec --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/Main.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.eclipse.tm.tcf.core.TransientPeer; +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IEventQueue; +import org.eclipse.tm.tcf.protocol.IPeer; +import org.eclipse.tm.tcf.protocol.Protocol; + +/** + * This class is user to run TCF test suite from command line. + */ +public class Main { + + private static class EventQueue extends Thread implements IEventQueue { + + private final LinkedList<Runnable> queue = new LinkedList<Runnable>(); + + EventQueue() { + setName("TCF Event Dispatcher"); + start(); + } + + public void run() { + try { + while (true) { + Runnable r = null; + synchronized (this) { + while (queue.size() == 0) wait(); + r = queue.removeFirst(); + } + r.run(); + } + } + catch (Throwable x) { + x.printStackTrace(); + System.exit(1); + } + } + + public synchronized int getCongestion() { + int n = queue.size() - 100; + if (n > 100) n = 100; + return n; + } + + public synchronized void invokeLater(Runnable runnable) { + queue.add(runnable); + notify(); + } + + public boolean isDispatchThread() { + return Thread.currentThread() == this; + } + } + + 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); + } + + private static void runTestSuite(IPeer peer) { + TCFTestSuite.TestListener listener = new TCFTestSuite.TestListener() { + + public void done(Collection<Throwable> errors) { + if (errors == null || errors.isEmpty()) { + System.out.println("No errors detected."); + System.exit(0); + } + for (Throwable x : errors) { + x.printStackTrace(System.out); + } + System.exit(3); + } + + public void progress(String label, int done, int total) { + if (label != null) System.out.println(label); + } + + }; + try { + new TCFTestSuite(peer, listener, null, null); + } + catch (Throwable x) { + System.err.println("Cannot start test suite:"); + x.printStackTrace(); + System.exit(2); + } + } + + /** + * Command line should contain peer description string, for example: + * "ID=Test:TransportName=TCP:Host=127.0.0.1:Port=1534" + */ + public static void main(final String[] args) { + if (args.length < 1) { + System.err.println("Missing command line argument - peer identification string"); + System.exit(4); + } + Protocol.setEventQueue(new EventQueue()); + Protocol.invokeLater(new Runnable() { + public void run() { + runTestSuite(getPeer(args)); + } + }); + Protocol.invokeLater(10 * 60 * 1000, new Runnable() { + public void run() { + System.err.println("Error: timeout - test's not finished in 10 min"); + System.exit(5); + } + }); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/RunControl.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/RunControl.java new file mode 100644 index 000000000..e111a5907 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/RunControl.java @@ -0,0 +1,280 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.tests; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IDiagnostics; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; + +class RunControl { + + private final TCFTestSuite test_suite; + private final IChannel channel; + private int channel_id; + private final IRunControl rc_service; + private final HashSet<String> suspended_ctx_ids = new HashSet<String>(); + private final HashSet<IToken> get_state_cmds = new HashSet<IToken>(); + private final HashMap<String,IToken> resume_cmds = new HashMap<String,IToken>(); + private final HashSet<String> pending_resume_ids = new HashSet<String>(); + private final HashMap<String,IRunControl.RunControlContext> ctx_map = new HashMap<String,IRunControl.RunControlContext>(); + private final Random rnd = new Random(); + + private boolean enable_trace; + + private boolean sync_pending; + + private final IRunControl.RunControlListener listener = new IRunControl.RunControlListener() { + + public void contextAdded(RunControlContext[] contexts) { + for (IRunControl.RunControlContext ctx : contexts) { + ctx_map.put(ctx.getID(), ctx); + } + } + + public void contextChanged(RunControlContext[] contexts) { + for (IRunControl.RunControlContext ctx : contexts) { + ctx_map.put(ctx.getID(), ctx); + } + } + + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + ctx_map.remove(id); + test_suite.getCanceledTests().remove(id); + suspended_ctx_ids.remove(id); + } + } + + public void contextSuspended(final String id, String pc, String reason, Map<String,Object> params) { + if (enable_trace) System.out.println("" + channel_id + " suspended " + id); + suspended_ctx_ids.add(id); + Protocol.invokeLater(new Runnable() { + public void run() { + resume(id, IRunControl.RM_RESUME); + } + }); + } + + public void contextResumed(String id) { + if (enable_trace) System.out.println("" + channel_id + " resumed " + id); + suspended_ctx_ids.remove(id); + pending_resume_ids.remove(id); + } + + public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, String[] suspended_ids) { + if (enable_trace) { + StringBuffer bf = new StringBuffer(); + for (String id : suspended_ids) { + if (bf.length() > 0) bf.append(','); + bf.append(id); + } + System.out.println("" + channel_id + " suspended " + bf); + } + for (String id : suspended_ids) { + suspended_ctx_ids.add(id); + resume(id, IRunControl.RM_RESUME); + } + } + + public void containerResumed(String[] context_ids) { + if (enable_trace) { + StringBuffer bf = new StringBuffer(); + for (String id : context_ids) { + if (bf.length() > 0) bf.append(','); + bf.append(id); + } + System.out.println("" + channel_id + " resumed " + bf); + } + for (String id : context_ids) { + suspended_ctx_ids.remove(id); + pending_resume_ids.remove(id); + } + } + + public void contextException(String context, String msg) { + } + }; + + RunControl(TCFTestSuite test_suite, IChannel channel, int channel_id) { + this.test_suite = test_suite; + this.channel = channel; + this.channel_id = channel_id; + rc_service = channel.getRemoteService(IRunControl.class); + if (rc_service != null) { + rc_service.addListener(listener); + getState(); + startTimer(); + } + enable_trace = System.getProperty("org.eclipse.tm.tcf.debug.tracing.tests.runcontrol") != null; + } + + private void getState() { + get_state_cmds.add(rc_service.getChildren(null, new IRunControl.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + get_state_cmds.remove(token); + if (error != null) { + exit(error); + } + else { + for (final String id : context_ids) { + get_state_cmds.add(rc_service.getChildren(id, this)); + get_state_cmds.add(rc_service.getContext(id, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, RunControlContext context) { + get_state_cmds.remove(token); + if (error != null) { + exit(error); + } + else { + ctx_map.put(id, context); + if (context.hasState()) { + get_state_cmds.add(context.getState(new IRunControl.DoneGetState() { + public void doneGetState(IToken token, Exception error, boolean suspended, + String pc, String reason, Map<String, Object> params) { + get_state_cmds.remove(token); + if (error != null) { + if (ctx_map.get(id) != null) exit(new Exception( + "Cannot get context state", error)); + } + else { + if (suspended) suspended_ctx_ids.add(id); + getStateDone(); + } + } + })); + } + getStateDone(); + } + } + })); + } + getStateDone(); + } + } + })); + } + + private void getStateDone() { + if (get_state_cmds.size() > 0) return; + if (channel.getState() != IChannel.STATE_OPEN) return; + if (suspended_ctx_ids.size() > 0) { + String[] arr = suspended_ctx_ids.toArray(new String[suspended_ctx_ids.size()]); + resume(arr[rnd.nextInt(arr.length)], IRunControl.RM_RESUME); + } + } + + private void startTimer() { + Protocol.invokeLater(50, new Runnable() { + public void run() { + if (channel.getState() != IChannel.STATE_OPEN) return; + if (test_suite.cancel) return; + Protocol.invokeLater(50, this); + Set<String> s = test_suite.getCanceledTests().keySet(); + if (s.size() > 0 || suspended_ctx_ids.size() > 0) { + Set<String> ids = new HashSet<String>(s); + ids.addAll(suspended_ctx_ids); + String[] arr = ids.toArray(new String[ids.size()]); + resume(arr[rnd.nextInt(arr.length)], IRunControl.RM_RESUME); + } + } + }); + } + + private void exit(Throwable error) { + Collection<ITCFTest> c = test_suite.getActiveTests(); + ITCFTest[] arr = c.toArray(new ITCFTest[c.size()]); + for (ITCFTest t : arr) test_suite.done(t, error); + } + + IRunControl.RunControlContext getContext(String id) { + return ctx_map.get(id); + } + + boolean canResume(String id) { + if (sync_pending) return false; + if (get_state_cmds.size() > 0) return false; + if (resume_cmds.get(id) != null) return false; + if (test_suite.getCanceledTests().get(id) == null && !suspended_ctx_ids.contains(id)) return false; + IRunControl.RunControlContext ctx = ctx_map.get(id); + if (ctx == null) return false; + String grp = ctx.getRCGroup(); + if (grp != null) { + for (String s : resume_cmds.keySet()) { + IRunControl.RunControlContext c = ctx_map.get(s); + if (c == null) return false; + if (grp.equals(c.getRCGroup())) return false; + } + } + return true; + } + + void resume(final String id, final int mode) { + if (!test_suite.canResume(id)) return; + assert !sync_pending; + sync_pending = true; + Protocol.sync(new Runnable() { + public void run() { + sync_pending = false; + if (test_suite.canResume(id)) { + assert resume_cmds.get(id) == null; + final String test_id = test_suite.getCanceledTests().get(id); + if (test_id != null) { + if (enable_trace) System.out.println("" + channel_id + " cancel " + id); + IDiagnostics diag = channel.getRemoteService(IDiagnostics.class); + resume_cmds.put(id, diag.cancelTest(test_id, new IDiagnostics.DoneCancelTest() { + public void doneCancelTest(IToken token, Throwable error) { + assert resume_cmds.get(id) == token; + resume_cmds.remove(id); + if (enable_trace) System.out.println("" + channel_id + " done cancel " + error); + if (error != null && ctx_map.get(test_id) != null) exit(error); + } + })); + } + else { + IRunControl.RunControlContext ctx = ctx_map.get(id); + if (ctx != null) { + pending_resume_ids.add(id); + if (enable_trace) System.out.println("" + channel_id + " resume " + mode + " " + id); + resume_cmds.put(id, ctx.resume(mode, 1, new IRunControl.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + assert resume_cmds.get(id) == token; + resume_cmds.remove(id); + if (enable_trace) System.out.println("" + channel_id + " done resume " + error); + if (error != null) { + pending_resume_ids.remove(id); + if (suspended_ctx_ids.contains(id)) exit(error); + } + else if (pending_resume_ids.contains(id)) { + exit(new Exception("Missing contextResumed event")); + } + } + })); + } + } + } + } + }); + } + + void cancel(String thread_id, String test_id) { + test_suite.getCanceledTests().put(thread_id, test_id); + resume(thread_id, 0); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TCFTestSuite.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TCFTestSuite.java new file mode 100644 index 000000000..9ffec13c4 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TCFTestSuite.java @@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.tm.internal.tcf.debug.tests; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IPeer; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IMemoryMap; +import org.eclipse.tm.tcf.services.IPathMap; + +/** + * TCF Test Suite implements stress testing of communication channels and capabilities of remote peer. + * It is intended to be used before starting a debug session for a first time to make sure the selected + * target is stable and reliable. + */ +public class TCFTestSuite { + + private final static int NUM_CHANNELS = 4; + + private final TestListener listener; + private final IChannel[] channels; + private final Map<IChannel,RunControl> run_controls = new HashMap<IChannel, RunControl>(); + private final LinkedList<Runnable> pending_tests = new LinkedList<Runnable>(); + private final Collection<Throwable> errors = new ArrayList<Throwable>(); + private final Map<ITCFTest,IChannel> active_tests = new HashMap<ITCFTest,IChannel>(); + private final static HashMap<String,String> cancel_test_ids = new HashMap<String,String>(); + + private int count_total; + private int count_done; + + boolean cancel; + boolean canceled; + boolean target_lock; + + public interface TestListener { + public void progress(String label, int done, int total); + public void done(Collection<Throwable> errors); + } + + public TCFTestSuite(final IPeer peer, final TestListener listener, final List<IPathMap.PathMapRule> path_map, + final Map<String,ArrayList<IMemoryMap.MemoryRegion>> mem_map) throws IOException { + this.listener = listener; + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Echo Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestEcho(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Echo FP Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestEchoFP(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Echo ERR Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestEchoERR(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Debugger Attach/Terminate Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestAttachTerminate(TCFTestSuite.this, run_controls.get(channel), channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Path Map Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestPathMap(TCFTestSuite.this, channel, path_map), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Expressions Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestExpressions(TCFTestSuite.this, run_controls.get(channel), channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Streams Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestStreams(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Sys monitor Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestSysMonitor(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Terminals Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestTerminals(TCFTestSuite.this, channel), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + int i = 0; + listener.progress("Running Run Control Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestRCBP1(TCFTestSuite.this, run_controls.get(channel), + channel, i++, path_map, mem_map), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + int i = 0; + listener.progress("Running File System Test...", ++count_done, count_total); + for (IChannel channel : channels) { + active_tests.put(new TestFileSystem(TCFTestSuite.this, channel, i++), channel); + } + } + }); + pending_tests.add(new Runnable() { + public void run() { + listener.progress("Running Interability Test...", ++count_done, count_total); + Random rnd = new Random(); + for (int i = 0; i < channels.length; i++) { + IChannel channel = channels[i]; + ITCFTest test = null; + switch (rnd.nextInt(11)) { + case 0: test = new TestEcho(TCFTestSuite.this, channel); break; + case 1: test = new TestEchoERR(TCFTestSuite.this, channel); break; + case 2: test = new TestEchoFP(TCFTestSuite.this, channel); break; + case 3: test = new TestAttachTerminate(TCFTestSuite.this, run_controls.get(channel), channel); break; + case 4: test = new TestExpressions(TCFTestSuite.this, run_controls.get(channel), channel); break; + case 5: test = new TestRCBP1(TCFTestSuite.this, run_controls.get(channel), channel, i, path_map, mem_map); break; + case 6: test = new TestFileSystem(TCFTestSuite.this, channel, i); break; + case 7: test = new TestPathMap(TCFTestSuite.this, channel, path_map); break; + case 8: test = new TestStreams(TCFTestSuite.this, channel); break; + case 9: test = new TestSysMonitor(TCFTestSuite.this, channel); break; + case 10: test = new TestTerminals(TCFTestSuite.this, channel); break; + } + active_tests.put(test, channel); + } + } + }); + count_total = pending_tests.size() * 2; + channels = new IChannel[NUM_CHANNELS]; + Protocol.invokeLater(new Runnable() { + public void run() { + try { + openChannels(peer); + } + 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) listener.done(errors); + } + } + }); + } + + private void openChannels(IPeer peer) { + listener.progress("Opening communication channels...", count_done, count_total); + for (int i = 0; i < channels.length; i++) { + final IChannel channel = channels[i] = peer.openChannel(); + channel.addChannelListener(new IChannel.IChannelListener() { + + public void onChannelOpened() { + for (int i = 0; i < channels.length; i++) { + if (channels[i] == null) return; + if (channels[i].getState() != IChannel.STATE_OPEN) return; + } + for (int i = 0; i < channels.length; i++) { + run_controls.put(channels[i], new RunControl(TCFTestSuite.this, channels[i], i)); + } + runNextTest(); + } + + public void congestionLevel(int level) { + } + + public void onChannelClosed(Throwable error) { + channel.removeChannelListener(this); + if (error == null && errors.isEmpty() && (!active_tests.isEmpty() || !pending_tests.isEmpty()) && !cancel) { + error = new IOException("Remote peer closed connection before all tests finished"); + } + int cnt = 0; + for (int i = 0; i < channels.length; i++) { + if (channels[i] == channel) { + channels[i] = null; + if (error != null && errors.isEmpty()) errors.add(error); + for (Iterator<ITCFTest> n = active_tests.keySet().iterator(); n.hasNext();) { + if (active_tests.get(n.next()) == channel) n.remove(); + } + } + if (channels[i] == null) continue; + if ((error != null || active_tests.isEmpty() && pending_tests.isEmpty()) && + channels[i].getState() != IChannel.STATE_CLOSED) channels[i].close(); + cnt++; + } + if (cnt == 0) listener.done(errors); + } + }); + } + } + + public void cancel() { + cancel = true; + if (canceled) return; + for (final ITCFTest t : active_tests.keySet()) { + if (t instanceof TestRCBP1) { + ((TestRCBP1)t).cancel(new Runnable() { + public void run() { + assert active_tests.get(t) == null; + cancel(); + } + }); + return; + } + } + canceled = true; + for (IChannel c : channels) { + if (c != null && c.getState() != IChannel.STATE_CLOSED) c.close(); + } + } + + public boolean isCanceled() { + return canceled; + } + + boolean isActive(ITCFTest test) { + return active_tests.get(test) != null; + } + + Collection<ITCFTest> getActiveTests() { + return active_tests.keySet(); + } + + Map<String,String> getCanceledTests() { + return cancel_test_ids; + } + + boolean canResume(String id) { + for (RunControl r : run_controls.values()) { + if (!r.canResume(id)) return false; + } + for (ITCFTest t : active_tests.keySet()) { + if (!t.canResume(id)) return false; + } + return true; + } + + void done(ITCFTest test, Throwable error) { + assert active_tests.get(test) != null; + if (error != null && !canceled) errors.add(error); + active_tests.remove(test); + if (active_tests.isEmpty()) runNextTest(); + } + + private void runNextTest() { + while (active_tests.isEmpty()) { + if (cancel || errors.size() > 0 || pending_tests.size() == 0) { + for (IChannel channel : channels) { + if (channel != null && channel.getState() != IChannel.STATE_CLOSED) { + if (errors.size() > 0) channel.terminate(new Exception("Test failed")); + else channel.close(); + } + } + return; + } + listener.progress(null, ++count_done, count_total); + pending_tests.removeFirst().run(); + ITCFTest[] lst = active_tests.keySet().toArray(new ITCFTest[active_tests.size()]); + for (ITCFTest test : lst) test.start(); + } + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestAttachTerminate.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestAttachTerminate.java new file mode 100644 index 000000000..b258f0035 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestAttachTerminate.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.util.HashSet; +import java.util.Map; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IDiagnostics; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; + +class TestAttachTerminate implements ITCFTest, IRunControl.RunControlListener { + + private final TCFTestSuite test_suite; + private final RunControl test_rc; + private final IDiagnostics diag; + private final IRunControl rc; + private final Random rnd = new Random(); + + private int cnt = 0; + + private final HashSet<String> test_ctx_ids = new HashSet<String>(); + + TestAttachTerminate(TCFTestSuite test_suite, RunControl test_rc, IChannel channel) { + this.test_suite = test_suite; + this.test_rc = test_rc; + diag = channel.getRemoteService(IDiagnostics.class); + rc = channel.getRemoteService(IRunControl.class); + } + + public void start() { + if (diag == null || rc == null) { + test_suite.done(this, null); + } + else { + rc.addListener(this); + diag.getTestList(new IDiagnostics.DoneGetTestList() { + public void doneGetTestList(IToken token, Throwable error, String[] list) { + if (!test_suite.isActive(TestAttachTerminate.this)) return; + if (error != null) { + exit(error); + } + else if (list.length > 0) { + startTestContext(list[rnd.nextInt(list.length)]); + Protocol.invokeLater(100, new Runnable() { + int cnt = 0; + public void run() { + if (!test_suite.isActive(TestAttachTerminate.this)) return; + cnt++; + if (test_suite.cancel) { + exit(null); + } + else if (cnt < 300) { + Protocol.invokeLater(100, this); + } + else if (test_ctx_ids.isEmpty()) { + exit(new Error("Timeout waiting for 'contextAdded' event")); + } + else { + exit(new Error("Timeout waiting for 'contextRemoved' event. Context: " + test_ctx_ids)); + } + } + }); + return; + } + exit(null); + } + }); + } + } + + public boolean canResume(String id) { + return true; + } + + private void startTestContext(String test_name) { + for (int i = 0; i < 4; i++) { + diag.runTest(test_name, new IDiagnostics.DoneRunTest() { + public void doneRunTest(IToken token, Throwable error, final String id) { + cnt--; + if (error != null) { + exit(error); + } + else { + assert id != null; + if (test_rc.getContext(id) == null) { + exit(new Error("Missing 'contextAdded' event for context " + id)); + } + else { + test_ctx_ids.add(id); + test_rc.cancel(id, id); + } + } + } + }); + cnt++; + } + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + rc.removeListener(this); + test_suite.done(this, x); + } + + public void containerResumed(String[] context_ids) { + for (String id : context_ids) contextResumed(id); + } + + public void containerSuspended(String main_context, String pc, + String reason, Map<String, Object> params, + String[] suspended_ids) { + for (String context : suspended_ids) { + assert context != null; + contextSuspended(context, null, null, null); + } + } + + public void contextAdded(RunControlContext[] contexts) { + } + + public void contextChanged(RunControlContext[] contexts) { + } + + public void contextException(String context, String msg) { + } + + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + test_ctx_ids.remove(id); + } + if (cnt == 0 && test_ctx_ids.isEmpty()) exit(null); + } + + public void contextResumed(String context) { + } + + public void contextSuspended(String context, String pc, String reason, Map<String,Object> params) { + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEcho.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEcho.java new file mode 100644 index 000000000..eb4a3ff33 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEcho.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.util.LinkedList; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IErrorReport; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.IDiagnostics; + +class TestEcho implements ITCFTest, IDiagnostics.DoneEcho { + + private final TCFTestSuite test_suite; + private final IDiagnostics diag; + private final LinkedList<String> msgs = new LinkedList<String>(); + private int count = 0; + private long start_time; + + TestEcho(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + diag = channel.getRemoteService(IDiagnostics.class); + } + + public void start() { + if (diag == null) { + test_suite.done(this, null); + } + else { + diag.not_implemented_command(new IDiagnostics.DoneNotImplementedCommand() { + + public void doneNotImplementedCommand(IToken token, Throwable error) { + if (!(error instanceof IErrorReport)) { + Throwable x = new Exception("Invalid responce to unimplemented command", error); + test_suite.done(TestEcho.this, x); + return; + } + if (((IErrorReport)error).getErrorCode() != IErrorReport.TCF_ERROR_INV_COMMAND) { + test_suite.done(TestEcho.this, new Exception("Invalid error code in responce to unimplemented command")); + return; + } + start_time = System.currentTimeMillis(); + for (int i = 0; i < 32; i++) sendMessage(); + } + }); + } + } + + private void sendMessage() { + StringBuffer buf = new StringBuffer(); + buf.append(Integer.toHexString(count)); + for (int i = 0; i < 64; i++) { + buf.append('-'); + buf.append((char)(0x400 * i + count)); + } + String s = buf.toString(); + msgs.add(s); + diag.echo(s, this); + count++; + } + + public void doneEcho(IToken token, Throwable error, String b) { + String s = msgs.removeFirst(); + if (!test_suite.isActive(this)) return; + if (error != null) { + test_suite.done(this, error); + } + else if (!s.equals(b)) { + test_suite.done(this, new Exception("Echo test failed: " + s + " != " + b)); + } + else if (count < 0x400) { + sendMessage(); + // Don't run the test much longer then 4 seconds + if (count % 0x10 == 0 && System.currentTimeMillis() - start_time >= 4000) { + count = 0x400; + } + } + else if (msgs.isEmpty()){ + test_suite.done(this, null); + } + } + + public boolean canResume(String id) { + return true; + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoERR.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoERR.java new file mode 100644 index 000000000..c855fb90a --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoERR.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2010, 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.tm.internal.tcf.debug.tests; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.eclipse.tm.tcf.core.Command; +import org.eclipse.tm.tcf.core.ErrorReport; +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IErrorReport; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.IDiagnostics; + +class TestEchoERR implements ITCFTest, IDiagnostics.DoneEchoERR { + + private final TCFTestSuite test_suite; + private final IDiagnostics diag; + + private final Number[] numbers = { + 1, + 4, + new BigDecimal("0.5") + }; + + private final String[] strings = { + "", + "abc", + "a\u1134c", + "a\u0003c", + }; + + private final String[] formats = { + "", + "{0}", + "{0,number}", + "{0,number,integer}", + "{0,number,percent}", + "{1}", + "{0} abcde {1}", + "{1} '' {0}", + "{1} 'abcde{}' {1}", + }; + + private final LinkedList<ErrorReport> list = new LinkedList<ErrorReport>(); + + TestEchoERR(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + diag = channel.getRemoteService(IDiagnostics.class); + } + + public void start() { + for (Number n : numbers) { + for (String s : strings) { + for (String f : formats) { + ArrayList<Object> params = new ArrayList<Object>(); + params.add(n); + params.add(s); + Map<String,Object> map = new HashMap<String,Object>(); + map.put(IErrorReport.ERROR_TIME, new Long(System.currentTimeMillis())); + map.put(IErrorReport.ERROR_CODE, new Integer(IErrorReport.TCF_ERROR_OTHER)); + map.put(IErrorReport.ERROR_FORMAT, f); + map.put(IErrorReport.ERROR_PARAMS, params); + map.put(s, s); // non-standard attribute + ErrorReport e = new ErrorReport("TCF error", map); + list.add(e); + diag.echoERR(e, this); + } + } + } + } + + public void doneEchoERR(IToken token, Throwable error, Throwable error_obj, String error_msg) { + ErrorReport e = list.removeFirst(); + if (!test_suite.isActive(this)) return; + Map<String,Object> map0 = e.getAttributes(); + Map<String,Object> map1 = null; + + if (error_obj instanceof IErrorReport) { + map1 = ((IErrorReport)error_obj).getAttributes(); + } + + String msg = Command.toErrorString(map0); + + if (error instanceof IErrorReport && ((IErrorReport)error).getErrorCode() == IErrorReport.TCF_ERROR_INV_COMMAND) { + // Older agent: the command is not available. Just exit the test. + test_suite.done(this, null); + } + else if (error != null) { + test_suite.done(this, error); + } + else if (!map0.equals(map1)) { + test_suite.done(this, new Exception("Invalid error report attributes")); + } + else if (!msg.equals(error_msg)) { + test_suite.done(this, new Exception("Invalid error report text")); + } + else if (list.size() == 0) { + test_suite.done(this, null); + } + } + + public boolean canResume(String id) { + return true; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoFP.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoFP.java new file mode 100644 index 000000000..69e1d3212 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestEchoFP.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010, 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.tm.internal.tcf.debug.tests; + +import java.math.BigDecimal; +import java.util.LinkedList; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.IDiagnostics; + +class TestEchoFP implements ITCFTest, IDiagnostics.DoneEchoFP { + + private final TCFTestSuite test_suite; + private final IDiagnostics diag; + private final LinkedList<BigDecimal> msgs = new LinkedList<BigDecimal>(); + private final Random rnd = new Random(); + + private int count = 0; + private long start_time; + + TestEchoFP(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + diag = channel.getRemoteService(IDiagnostics.class); + } + + public void start() { + if (diag == null) { + test_suite.done(this, null); + } + else { + start_time = System.currentTimeMillis(); + for (int i = 0; i < 32; i++) sendMessage(); + } + } + + private void sendMessage() { + BigDecimal n = BigDecimal.valueOf(rnd.nextInt(), rnd.nextInt(61) - 30); + msgs.add(n); + diag.echoFP(n, this); + count++; + } + + private boolean cmp(double x, double y) { + return (float)x == (float)y; + } + + public void doneEchoFP(IToken token, Throwable error, BigDecimal b) { + BigDecimal s = msgs.removeFirst(); + if (!test_suite.isActive(this)) return; + if (error != null) { + test_suite.done(this, error); + } + else if (!cmp(s.doubleValue(), b.doubleValue())) { + test_suite.done(this, new Exception("EchoFP test failed: " + s + " != " + b)); + } + else if (count < 0x800) { + sendMessage(); + // Don't run the test much longer then 4 seconds + if (count % 0x10 == 0 && System.currentTimeMillis() - start_time >= 4000) { + count = 0x800; + } + } + else if (msgs.isEmpty()){ + test_suite.done(this, null); + } + } + + public boolean canResume(String id) { + return true; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestExpressions.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestExpressions.java new file mode 100644 index 000000000..d59f94b5c --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestExpressions.java @@ -0,0 +1,650 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; +import org.eclipse.tm.tcf.services.IDiagnostics; +import org.eclipse.tm.tcf.services.IExpressions; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.IStackTrace; +import org.eclipse.tm.tcf.services.ISymbols; + +class TestExpressions implements ITCFTest, + IRunControl.RunControlListener, IExpressions.ExpressionsListener, IBreakpoints.BreakpointsListener { + + private final TCFTestSuite test_suite; + private final RunControl test_rc; + private final IDiagnostics diag; + private final IExpressions expr; + private final ISymbols syms; + private final IStackTrace stk; + private final IRunControl rc; + private final IBreakpoints bp; + private final Random rnd = new Random(); + + private String test_id; + private String bp_id; + private boolean bp_ok; + private IDiagnostics.ISymbol sym_func3; + private String test_ctx_id; + private String process_id; + private String thread_id; + private boolean run_to_bp_done; + private boolean test_done; + private boolean cancel_test_sent; + private IRunControl.RunControlContext test_ctx; + private IRunControl.RunControlContext thread_ctx; + private String suspended_pc; + private boolean waiting_suspend; + private String[] stack_trace; + private IStackTrace.StackTraceContext[] stack_frames; + private String[] local_vars; + private final Map<String,IExpressions.Expression> expr_ctx = new HashMap<String,IExpressions.Expression>(); + private final Map<String,IExpressions.Value> expr_val = new HashMap<String,IExpressions.Value>(); + private final Map<String,ISymbols.Symbol> expr_sym = new HashMap<String,ISymbols.Symbol>(); + private final Map<String,String[]> expr_chld = new HashMap<String,String[]>(); + private final Set<String> expr_to_dispose = new HashSet<String>(); + + private static String[] test_expressions = { + "func2_local1", + "func2_local2", + "func2_local3", + "func2_local1 == func2_local1", + "func2_local1 != func2_local2", + "1.34 == 1.34", + "1.34 != 1.35", + "1 ? 1 : 0", + "!func2_local1 ? 0 : 1", + "(0 || 0) == 0", + "(0 || func2_local1) == 1", + "(func2_local1 || 0) == 1", + "(func2_local1 || func2_local1) == 1", + "(0 && 0) == 0", + "(0 && func2_local1) == 0", + "(func2_local1 && 0) == 0", + "(func2_local1 && func2_local1) == 1", + "(func2_local1 | func2_local2) == 3", + "(func2_local1 & func2_local2) == 0", + "(func2_local1 ^ func2_local2) == 3", + "(func2_local1 < func2_local2)", + "(func2_local1 <= func2_local2)", + "!(func2_local1 > func2_local2)", + "!(func2_local1 >= func2_local2)", + "(func2_local1 < 1.1)", + "(func2_local1 <= 1.1)", + "!(func2_local1 > 1.1)", + "!(func2_local1 >= 1.1)", + "(func2_local2 << 2) == 8", + "(func2_local2 >> 1) == 1", + "+func2_local2 == 2", + "-func2_local2 == -2", + "(short)(int)(long)((char *)func2_local2 + 1) == 3", + "((func2_local1 + func2_local2) * 2 - 2) / 2 == 2", + "func2_local3.f_struct->f_struct->f_struct == &func2_local3", + "(char *)func2_local3.f_struct", + "(char[4])func2_local3.f_struct", + "&((test_struct *)0)->f_float", + "&((struct test_struct *)0)->f_float", + "tcf_test_func3", + "&tcf_test_func3", + "tcf_test_array + 10", + "*(tcf_test_array + 10) | 1", + "&*(char *)(int *)0 == 0", + }; + + TestExpressions(TCFTestSuite test_suite, RunControl test_rc, IChannel channel) { + this.test_suite = test_suite; + this.test_rc = test_rc; + diag = channel.getRemoteService(IDiagnostics.class); + expr = channel.getRemoteService(IExpressions.class); + syms = channel.getRemoteService(ISymbols.class); + stk = channel.getRemoteService(IStackTrace.class); + rc = channel.getRemoteService(IRunControl.class); + bp = channel.getRemoteService(IBreakpoints.class); + } + + public void start() { + if (diag == null || expr == null || stk == null || rc == null || bp == null) { + test_suite.done(this, null); + } + else { + expr.addListener(this); + rc.addListener(this); + bp.addListener(this); + diag.getTestList(new IDiagnostics.DoneGetTestList() { + public void doneGetTestList(IToken token, Throwable error, String[] list) { + if (!test_suite.isActive(TestExpressions.this)) return; + if (error != null) { + exit(error); + } + else { + if (list.length > 0) { + test_id = list[rnd.nextInt(list.length)]; + runTest(); + Protocol.invokeLater(100, new Runnable() { + int cnt = 0; + public void run() { + if (!test_suite.isActive(TestExpressions.this)) return; + cnt++; + if (test_suite.cancel) { + exit(null); + } + else if (cnt < 600) { + if (test_done && !cancel_test_sent) { + test_rc.cancel(thread_id, test_ctx_id); + cancel_test_sent = true; + } + Protocol.invokeLater(100, this); + } + else if (test_ctx_id == null) { + exit(new Error("Timeout waiting for reply of Diagnostics.runTest command")); + } + else { + exit(new Error("Missing 'contextRemoved' event for " + test_ctx_id)); + } + } + }); + return; + } + exit(null); + } + } + }); + } + } + + public boolean canResume(String id) { + if (test_ctx_id != null && thread_ctx == null) return false; + if (thread_ctx != null && !test_done) { + assert thread_ctx.getID().equals(thread_id); + IRunControl.RunControlContext ctx = test_rc.getContext(id); + if (ctx == null) return false; + String grp = ctx.getRCGroup(); + if (id.equals(thread_id) || grp != null && grp.equals(thread_ctx.getRCGroup())) { + if (run_to_bp_done) return false; + if (sym_func3 == null) return false; + if (suspended_pc == null) return false; + BigInteger pc0 = JSON.toBigInteger(sym_func3.getValue()); + BigInteger pc1 = new BigInteger(suspended_pc); + if (pc0.equals(pc1)) return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private void runTest() { + if (bp_id == null) { + bp.set(null, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + bp_id = "TestExpressionsBP"; + runTest(); + } + } + }); + return; + } + if (!bp_ok) { + 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, "tcf_test_func3"); + bp.set(new Map[]{ m }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + bp_ok = true; + runTest(); + } + } + }); + return; + } + if (test_ctx_id == null) { + diag.runTest(test_id, new IDiagnostics.DoneRunTest() { + public void doneRunTest(IToken token, Throwable error, String id) { + if (error != null) { + exit(error); + } + else if (id == null) { + exit(new Exception("Test context ID must not be null")); + } + else if (test_rc.getContext(id) == null) { + exit(new Exception("Missing context added event")); + } + else { + test_ctx_id = id; + runTest(); + } + } + }); + return; + } + if (test_ctx == null) { + rc.getContext(test_ctx_id, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, IRunControl.RunControlContext ctx) { + if (error != null) { + exit(error); + } + else if (ctx == null) { + exit(new Exception("Invalid test execution context")); + } + else { + test_ctx = ctx; + process_id = test_ctx.getProcessID(); + if (test_ctx.hasState()) thread_id = test_ctx_id; + runTest(); + } + } + }); + return; + } + if (thread_id == null) { + rc.getChildren(process_id, new IRunControl.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] ids) { + if (error != null) { + exit(error); + } + else if (ids == null || ids.length == 0) { + exit(new Exception("Test process has no threads")); + } + else if (ids.length != 1) { + exit(new Exception("Test process has too many threads")); + } + else { + thread_id = ids[0]; + runTest(); + } + } + }); + return; + } + if (thread_ctx == null) { + rc.getContext(thread_id, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, IRunControl.RunControlContext ctx) { + if (error != null) { + exit(error); + } + else if (ctx == null || !ctx.hasState()) { + exit(new Exception("Invalid thread context")); + } + else { + thread_ctx = ctx; + runTest(); + } + } + }); + return; + } + if (suspended_pc == null) { + thread_ctx.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(new Exception("Cannot get context state", error)); + } + else if (!suspended) { + waiting_suspend = true; + } + else if (pc == null || pc.length() == 0 || pc.equals("0")) { + exit(new Exception("Invalid context PC")); + } + else { + suspended_pc = pc; + runTest(); + } + } + }); + return; + } + if (sym_func3 == null) { + diag.getSymbol(process_id, "tcf_test_func3", new IDiagnostics.DoneGetSymbol() { + public void doneGetSymbol(IToken token, Throwable error, IDiagnostics.ISymbol symbol) { + if (error != null) { + exit(error); + } + else if (symbol == null) { + exit(new Exception("Symbol must not be null: tcf_test_func3")); + } + else { + sym_func3 = symbol; + runTest(); + } + } + }); + return; + } + if (!run_to_bp_done) { + BigInteger pc0 = JSON.toBigInteger(sym_func3.getValue()); + BigInteger pc1 = new BigInteger(suspended_pc); + if (!pc0.equals(pc1)) { + waiting_suspend = true; + test_rc.resume(thread_id, IRunControl.RM_RESUME); + return; + } + run_to_bp_done = true; + } + assert test_done || !canResume(thread_id); + if (stack_trace == null) { + stk.getChildren(thread_id, new IStackTrace.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + if (error != null) { + exit(error); + } + else if (context_ids == null || context_ids.length < 2) { + exit(new Exception("Invalid stack trace")); + } + else { + stack_trace = context_ids; + runTest(); + } + } + }); + return; + } + if (stack_frames == null) { + stk.getContext(stack_trace, new IStackTrace.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, IStackTrace.StackTraceContext[] frames) { + if (error != null) { + exit(error); + } + else if (frames == null || frames.length != stack_trace.length) { + exit(new Exception("Invalid stack trace")); + } + else { + stack_frames = frames; + runTest(); + } + } + }); + return; + } + if (local_vars == null) { + expr.getChildren(stack_trace[stack_trace.length - 2], new IExpressions.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + if (error != null || context_ids == null) { + // Need to continue tests even if local variables info is not available. + // TODO: need to distinguish absence of debug info from other errors. + local_vars = new String[0]; + runTest(); + } + else { + local_vars = context_ids; + runTest(); + } + } + }); + return; + } + for (final String id : local_vars) { + if (expr_ctx.get(id) == null) { + expr.getContext(id, new IExpressions.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, IExpressions.Expression ctx) { + if (error != null) { + exit(error); + } + else { + expr_ctx.put(id, ctx); + runTest(); + } + } + }); + return; + } + } + for (final String txt : test_expressions) { + if (local_vars.length == 0) { + // Debug info not available + if (txt.indexOf("local") >= 0) continue; + if (txt.indexOf("test_struct") >= 0) continue; + if (txt.indexOf("tcf_test_array") >= 0) continue; + if (txt.indexOf("(char *)") >= 0) continue; + } + if (expr_ctx.get(txt) == null) { + expr.create(stack_trace[stack_trace.length - 2], null, txt, new IExpressions.DoneCreate() { + public void doneCreate(IToken token, Exception error, IExpressions.Expression ctx) { + if (error != null) { + exit(error); + } + else { + expr_to_dispose.add(ctx.getID()); + expr_ctx.put(txt, ctx); + runTest(); + } + } + }); + return; + } + } + for (final String id : local_vars) { + if (expr_val.get(id) == null) { + expr.evaluate(id, new IExpressions.DoneEvaluate() { + public void doneEvaluate(IToken token, Exception error, IExpressions.Value ctx) { + if (error != null) { + exit(error); + } + else { + expr_val.put(id, ctx); + runTest(); + } + } + }); + return; + } + } + for (final String id : expr_ctx.keySet()) { + if (expr_val.get(id) == null) { + expr.evaluate(expr_ctx.get(id).getID(), new IExpressions.DoneEvaluate() { + public void doneEvaluate(IToken token, Exception error, IExpressions.Value ctx) { + if (error != null) { + exit(error); + } + else { + expr_val.put(id, ctx); + byte[] arr = ctx.getValue(); + boolean b = false; + for (byte x : arr) { + if (x != 0) b = true; + } + if (!b) exit(new Exception("Invalid value of expression \"" + id + "\"")); + runTest(); + } + } + }); + return; + } + } + if (syms != null) { + for (final String id : expr_val.keySet()) { + if (expr_sym.get(id) == null) { + IExpressions.Value v = expr_val.get(id); + String type_id = v.getTypeID(); + if (type_id != null) { + syms.getContext(type_id, new ISymbols.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, ISymbols.Symbol ctx) { + if (error != null) { + exit(error); + } + else if (ctx == null) { + exit(new Exception("Symbol.getContext returned null")); + } + else { + expr_sym.put(id, ctx); + runTest(); + } + } + }); + return; + } + } + } + for (final String id : expr_sym.keySet()) { + if (expr_chld.get(id) == null) { + ISymbols.Symbol sym = expr_sym.get(id); + syms.getChildren(sym.getID(), new ISymbols.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + if (error != null) { + exit(error); + } + else { + if (context_ids == null) context_ids = new String[0]; + expr_chld.put(id, context_ids); + runTest(); + } + } + }); + return; + } + } + } + for (final String id : expr_to_dispose) { + expr.dispose(id, new IExpressions.DoneDispose() { + public void doneDispose(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + expr_to_dispose.remove(id); + runTest(); + } + } + }); + return; + } + test_done = true; + test_rc.resume(thread_id, IRunControl.RM_RESUME); + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + expr.removeListener(this); + bp.removeListener(this); + rc.removeListener(this); + test_suite.done(this, x); + } + + //--------------------------- Run Control listener ---------------------------// + + public void containerResumed(String[] context_ids) { + for (String id : context_ids) contextResumed(id); + } + + public void containerSuspended(String context, String pc, String reason, + Map<String,Object> params, String[] suspended_ids) { + for (String id : suspended_ids) { + contextSuspended(id, null, null, null); + } + } + + public void contextAdded(IRunControl.RunControlContext[] contexts) { + } + + public void contextChanged(IRunControl.RunControlContext[] contexts) { + } + + public void contextException(String context, String msg) { + if (test_done) return; + IRunControl.RunControlContext ctx = test_rc.getContext(context); + if (ctx != null) { + String p = ctx.getParentID(); + String c = ctx.getCreatorID(); + if (!test_ctx_id.equals(c) && !test_ctx_id.equals(p)) return; + } + exit(new Exception("Context exception: " + msg)); + } + + public void contextRemoved(String[] context_ids) { + for (String id : context_ids) { + 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 contextResumed(String id) { + if (id.equals(thread_id)) { + if (run_to_bp_done && !test_done) { + assert thread_ctx != null; + assert !canResume(thread_id); + exit(new Exception("Unexpected contextResumed event: " + id)); + } + suspended_pc = null; + } + } + + public void contextSuspended(String id, String pc, String reason, Map<String,Object> params) { + assert id != null; + if (id.equals(thread_id) && waiting_suspend) { + suspended_pc = pc; + waiting_suspend = false; + runTest(); + } + } + + //--------------------------- Expressions listener ---------------------------// + + public void valueChanged(String id) { + } + + //--------------------------- Breakpoints listener ---------------------------// + + @SuppressWarnings("unchecked") + 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) { + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestFileSystem.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestFileSystem.java new file mode 100644 index 000000000..7c601e7e2 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestFileSystem.java @@ -0,0 +1,482 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IFileSystem; +import org.eclipse.tm.tcf.services.IFileSystem.DirEntry; +import org.eclipse.tm.tcf.services.IFileSystem.FileAttrs; +import org.eclipse.tm.tcf.services.IFileSystem.FileSystemException; +import org.eclipse.tm.tcf.services.IFileSystem.IFileHandle; +import org.eclipse.tm.tcf.util.TCFFileInputStream; +import org.eclipse.tm.tcf.util.TCFFileOutputStream; + +class TestFileSystem implements ITCFTest, IFileSystem.DoneStat, + IFileSystem.DoneOpen, IFileSystem.DoneClose, + IFileSystem.DoneWrite, IFileSystem.DoneRead, + IFileSystem.DoneRename, IFileSystem.DoneRealPath, + IFileSystem.DoneRemove, IFileSystem.DoneRoots, + IFileSystem.DoneReadDir { + + private final TCFTestSuite test_suite; + private final int channel_id; + + private static final int + STATE_PRE = 0, + STATE_RD_DIR = 1, + STATE_WRITE = 2, + STATE_READ = 3, + STATE_OUT = 4, + STATE_INP = 5, + STATE_EXIT = 6; + + private final IFileSystem files; + private final Random rnd = new Random(); + private final LinkedList<String> tmp_files = new LinkedList<String>(); + private final HashMap<IToken,Object> cmds = new HashMap<IToken,Object>(); + private byte[] data; + private String root; + private String tmp_path; + private String file_name; + private IFileHandle handle; + private int state = STATE_PRE; + private boolean async_close; + + private static class ReadCmd { + int offs; + int size; + } + + TestFileSystem(TCFTestSuite test_suite, IChannel channel, int channel_id) { + this.test_suite = test_suite; + this.channel_id = channel_id; + files = channel.getRemoteService(IFileSystem.class); + } + + public void start() { + if (files == null) { + test_suite.done(this, null); + } + else { + files.roots(this); + } + } + + public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) { + assert state == STATE_PRE; + if (error != null) { + exit(error); + } + else if (entries == null || entries.length == 0) { + exit(new Exception("Invalid FileSysrem.roots responce: empty roots array")); + } + else { + for (DirEntry d : entries) { + if (d.filename.startsWith("A:")) continue; + if (d.filename.startsWith("B:")) continue; + root = d.filename; + break; + } + if (root == null) exit(new Exception("Invalid FileSystem.roots responce: no suitable root")); + else files.opendir(root, this); + } + } + + public void doneReadDir(IToken token, FileSystemException error, + DirEntry[] entries, boolean eof) { + if (error != null) { + exit(error); + } + else if (state == STATE_PRE) { + if (entries != null && tmp_path == null) { + for (DirEntry e : entries) { + if (e.filename.equals("tmp") || e.filename.equalsIgnoreCase("temp")) { + tmp_path = root; + if (!tmp_path.endsWith("/")) tmp_path += "/"; + tmp_path += e.filename; + break; + } + } + } + if (eof) { + if (tmp_path == null) { + exit(new Exception("File system test filed: cannot find temporary directory")); + return; + } + files.close(handle, this); + } + else { + files.readdir(handle, this); + } + } + else if (state == STATE_RD_DIR) { + if (entries != null) { + for (DirEntry e : entries) { + if (e.filename.startsWith("tcf-test-" + channel_id + "-")) { + tmp_files.add(e.filename); + } + } + } + if (eof) { + files.close(handle, this); + } + else { + files.readdir(handle, this); + } + } + else { + assert false; + } + } + + public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) { + if (error != null) { + exit(error); + } + else if (state == STATE_READ) { + if (attrs.size != data.length) { + exit(new Exception("Invalid FileSysrem.fstat responce: wrong file size")); + } + else { + files.close(handle, this); + } + } + else if (state == STATE_WRITE) { + char[] bf = new char[64]; + for (int i = 0; i < bf.length; i++) { + char ch = (char)(rnd.nextInt(0x4000) + 0x20); + switch (ch) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + case '~': + ch = '-'; + break; + } + bf[i] = ch; + } + file_name = tmp_path + "/tcf-test-" + channel_id + "-" + new String(bf) + ".tmp"; + files.open(file_name, IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC | IFileSystem.TCF_O_WRITE, null, this); + } + else { + assert false; + } + } + + public void doneOpen(IToken token, FileSystemException error, final IFileHandle handle) { + if (error != null) { + exit(error); + } + else { + this.handle = handle; + if (state == STATE_READ) { + for (int i = 0; i < rnd.nextInt(8); i++) { + ReadCmd cmd = new ReadCmd(); + cmd.offs = rnd.nextInt(data.length); + cmd.size = rnd.nextInt(data.length - cmd.offs) + 2; + cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd); + } + if (rnd.nextBoolean()) { + ReadCmd cmd = new ReadCmd(); + cmd.offs = 0; + cmd.size = data.length + 1; + cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd); + } + else { + int pos = 0; + while (pos < data.length) { + int size = rnd.nextInt(data.length - pos) + 1; + ReadCmd cmd = new ReadCmd(); + cmd.offs = pos; + cmd.size = size + 1; + cmds.put(files.read(handle, cmd.offs, cmd.size, this), cmd); + pos += size; + } + } + async_close = rnd.nextBoolean(); + if (async_close) files.close(handle, this); + } + else if (state == STATE_WRITE) { + data = new byte[rnd.nextInt(0x1000) + 1]; + rnd.nextBytes(data); + if (rnd.nextBoolean()) { + cmds.put(files.write(handle, 0, data, 0, data.length, this), null); + } + else { + int pos = 0; + while (pos < data.length) { + int size = rnd.nextInt(data.length - pos) + 1; + cmds.put(files.write(handle, pos, data, pos, size, this), null); + pos += size; + } + } + async_close = rnd.nextBoolean(); + if (async_close) files.close(handle, this); + } + else if (state == STATE_INP) { + Thread thread = new Thread() { + public void run() { + try { + int pos = 0; + int len = data.length * 16; + byte[] buf = new byte[333]; + int buf_pos = 0; + int buf_len = 0; + boolean mark = true; + boolean reset = true; + int mark_pos = rnd.nextInt(len - 1); + int reset_pos = mark_pos + rnd.nextInt(len - mark_pos); + assert reset_pos >= mark_pos; + InputStream inp = new TCFFileInputStream(handle, 133); + for (;;) { + if (mark && pos == mark_pos) { + inp.mark(len); + mark = false; + } + if (reset && pos == reset_pos) { + inp.reset(); + reset = false; + pos = mark_pos; + buf_pos = buf_len = 0; + } + int ch = 0; + if (buf_pos >= buf_len && (pos >= mark_pos || pos + buf.length <= mark_pos)) { + buf_len = inp.read(buf, buf_pos = 0, buf.length); + if (buf_len < 0) break; + } + if (buf_pos < buf_len) { + ch = buf[buf_pos++] & 0xff; + } + else { + ch = inp.read(); + if (ch < 0) break; + } + int dt = data[pos % data.length] & 0xff; + if (ch != dt) { + error(new Exception("Invalid TCFFileInputStream.read responce: wrong data at offset " + pos + + ", expected " + dt + ", actual " + ch)); + } + pos++; + } + if (pos != data.length * 16) { + error(new Exception("Invalid TCFFileInputStream.read responce: wrong file length: " + + "expected " + data.length + ", actual " + pos)); + } + inp.close(); + Protocol.invokeLater(new Runnable() { + public void run() { + state = STATE_EXIT; + files.rename(file_name, file_name + ".rnm", TestFileSystem.this); + } + }); + } + catch (Throwable x) { + error(x); + } + } + private void error(final Throwable x) { + Protocol.invokeLater(new Runnable() { + public void run() { + exit(x); + } + }); + } + }; + thread.setName("TCF FileSystem Test"); + thread.start(); + } + else if (state == STATE_OUT) { + rnd.nextBytes(data); + Thread thread = new Thread() { + public void run() { + try { + int pos = 0; + int len = data.length * 16; + OutputStream out = new TCFFileOutputStream(handle, 121); + while (pos < len) { + int m = pos % data.length; + int n = rnd.nextInt(1021); + if (n > data.length - m) n = data.length - m; + out.write(data, m, n); + pos += n; + if (pos == len) break; + out.write(data[pos % data.length] & 0xff); + pos++; + } + out.close(); + Protocol.invokeLater(new Runnable() { + public void run() { + state = STATE_INP; + files.open(file_name, IFileSystem.TCF_O_READ, null, TestFileSystem.this); + } + }); + } + catch (Throwable x) { + error(x); + } + } + private void error(final Throwable x) { + Protocol.invokeLater(new Runnable() { + public void run() { + exit(x); + } + }); + } + }; + thread.setName("TCF FileSystem Test"); + thread.start(); + } + else { + assert state == STATE_PRE || state == STATE_RD_DIR; + files.readdir(handle, this); + } + } + } + + public void doneWrite(IToken token, FileSystemException error) { + assert cmds.containsKey(token); + cmds.remove(token); + if (error != null) { + exit(error); + } + else if (cmds.size() == 0 && !async_close) { + files.close(handle, this); + } + } + + public void doneRead(IToken token, FileSystemException error, byte[] data, boolean eof) { + assert cmds.containsKey(token); + ReadCmd cmd = (ReadCmd)cmds.remove(token); + if (error != null) { + exit(error); + } + else { + if (cmd.offs + cmd.size > this.data.length && !eof) { + exit(new Exception("Invalid FileSysrem.read responce: EOF expected")); + } + else if (data.length != (eof ? cmd.size - 1 : cmd.size)) { + exit(new Exception("Invalid FileSysrem.read responce: wrong data array size")); + } + else { + for (int i = 0; i < data.length; i++) { + if (data[i] != this.data[i + cmd.offs]) { + exit(new Exception("Invalid FileSysrem.read responce: wrong data at offset " + i + + ", expected " + this.data[i + cmd.offs] + ", actual " + data[i])); + return; + } + } + if (cmds.size() == 0 && !async_close) files.fstat(handle, this); + } + } + } + + public void doneClose(IToken token, FileSystemException error) { + if (error != null) { + exit(error); + } + else { + handle = null; + if (state == STATE_PRE) { + state = STATE_RD_DIR; + files.opendir(tmp_path, this); + } + else if (state == STATE_RD_DIR) { + state = STATE_WRITE; + files.realpath(tmp_path, this); + } + else if (state == STATE_WRITE) { + state = STATE_READ; + files.open(file_name, IFileSystem.TCF_O_READ, null, this); + } + else if (state == STATE_READ) { + state = STATE_OUT; + files.open(file_name, IFileSystem.TCF_O_WRITE, null, this); + } + else { + assert false; + } + } + } + + public void doneRename(IToken token, FileSystemException error) { + assert state == STATE_EXIT; + if (error != null) { + exit(error); + } + else { + files.realpath(file_name + ".rnm", this); + } + } + + public void doneRealPath(IToken token, FileSystemException error, String path) { + if (error != null) { + exit(error); + } + else if (state == STATE_WRITE) { + tmp_path = path; + if (tmp_files.size() > 0) { + files.remove(tmp_path + "/" + tmp_files.removeFirst(), this); + } + else { + files.stat(tmp_path, this); + } + } + else if (!path.equals(file_name + ".rnm")) { + exit(new Exception("Invalid FileSysrem.realpath responce: " + path)); + } + else { + files.remove(file_name + ".rnm", this); + } + } + + public void doneRemove(IToken token, FileSystemException error) { + if (error != null) { + exit(error); + } + else if (state == STATE_WRITE) { + if (tmp_files.size() > 0) { + files.remove(tmp_path + "/" + tmp_files.removeFirst(), this); + } + else { + files.stat(tmp_path, this); + } + } + else if (state == STATE_EXIT) { + exit(null); + } + else { + assert false; + } + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + test_suite.done(this, x); + } + + public boolean canResume(String id) { + return true; + } +}
\ No newline at end of file diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestPathMap.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestPathMap.java new file mode 100644 index 000000000..c120565de --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestPathMap.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2010, 1011 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.tm.internal.tcf.debug.tests; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.IPathMap; +import org.eclipse.tm.tcf.services.IPathMap.PathMapRule; + +class TestPathMap implements ITCFTest { + + private final TCFTestSuite test_suite; + private final List<PathMapRule> map; + private final IPathMap service; + + private final Random rnd = new Random(); + + private static final String[] prop_names = { + IPathMap.PROP_SOURCE, + IPathMap.PROP_DESTINATION, + IPathMap.PROP_HOST, + IPathMap.PROP_PROTOCOL, + }; + + private int cnt = 0; + + private static class Rule implements IPathMap.PathMapRule { + + final Map<String,Object> props; + + public Rule(Map<String,Object> props) { + this.props = props; + } + + public Map<String,Object> getProperties() { + return props; + } + + public String getID() { + return (String)props.get(IPathMap.PROP_ID); + } + + public String getSource() { + return (String)props.get(IPathMap.PROP_SOURCE); + } + + public String getDestination() { + return (String)props.get(IPathMap.PROP_DESTINATION); + } + + public String getHost() { + return (String)props.get(IPathMap.PROP_HOST); + } + + public String getProtocol() { + return (String)props.get(IPathMap.PROP_PROTOCOL); + } + + public String toString() { + return props.toString(); + } + } + + TestPathMap(TCFTestSuite test_suite, IChannel channel, List<PathMapRule> map) { + this.test_suite = test_suite; + this.map = map; + service = channel.getRemoteService(IPathMap.class); + } + + public void start() { + if (service == null) { + exit(null); + } + else { + test_map(); + } + } + + private void test_map() { + if (cnt >= 40) { + if (map == null) { + exit(null); + } + else { + service.set(map.toArray(new PathMapRule[map.size()]), new IPathMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + exit(error); + } + }); + } + } + else { + cnt++; + final IPathMap.PathMapRule[] map_out = new IPathMap.PathMapRule[rnd.nextInt(12)]; + for (int i = 0; i < map_out.length; i++) { + Map<String,Object> props = new HashMap<String,Object>(); + props.put(IPathMap.PROP_ID, "PM" + i); + for (int l = 0; l < 2; l++) { + String nm = prop_names[rnd.nextInt(prop_names.length)]; + StringBuffer bf = new StringBuffer(); + int n = rnd.nextInt(1024); + for (int j = 0; j < n; j++) { + char ch = (char)(rnd.nextInt(0xfff0) + 1); + bf.append(ch); + } + String val = bf.toString(); + props.put(nm, val); + } + map_out[i] = new Rule(props); + } + service.set(map_out, new IPathMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + service.get(new IPathMap.DoneGet() { + public void doneGet(IToken token, Exception error, PathMapRule[] map_inp) { + if (error != null) { + exit(error); + } + else if (map_inp == null) { + exit(new Exception("PathMap.get returned null")); + } + else if (map_out.length != map_inp.length) { + exit(new Exception("PathMap.get error: wrong map size")); + } + else { + for (int i = 0; i < map_out.length; i++) { + if (!map_equ(map_out[i].getProperties(), map_inp[i].getProperties())) { + return; + } + } + test_map(); + } + } + }); + } + } + }); + } + } + + private boolean map_equ(Map<String,Object> x, Map<String,Object> y) { + for (String key : x.keySet()) { + if (!obj_equ(key, x.get(key), y.get(key))) return false; + } + for (String key : y.keySet()) { + if (!obj_equ(key, x.get(key), y.get(key))) return false; + } + return true; + } + + private boolean obj_equ(String nm, Object x, Object y) { + if (x == y) return true; + if (x != null && x.equals(y)) return true; + exit(new Exception("PathMap.get: wrong map data, " + nm + ": " + x + " != " + y)); + return false; + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + test_suite.done(this, x); + } + + public boolean canResume(String id) { + return false; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestRCBP1.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestRCBP1.java new file mode 100644 index 000000000..56ed77663 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestRCBP1.java @@ -0,0 +1,1729 @@ +/******************************************************************************* + * 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.tm.internal.tcf.debug.tests; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IErrorReport; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.JSON; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IBreakpoints; +import org.eclipse.tm.tcf.services.IDiagnostics; +import org.eclipse.tm.tcf.services.IDisassembly; +import org.eclipse.tm.tcf.services.ILineNumbers; +import org.eclipse.tm.tcf.services.IMemory; +import org.eclipse.tm.tcf.services.IMemoryMap; +import org.eclipse.tm.tcf.services.IPathMap; +import org.eclipse.tm.tcf.services.IRegisters; +import org.eclipse.tm.tcf.services.IRunControl; +import org.eclipse.tm.tcf.services.ISymbols; +import org.eclipse.tm.tcf.services.IDiagnostics.ISymbol; +import org.eclipse.tm.tcf.services.IDisassembly.IDisassemblyLine; +import org.eclipse.tm.tcf.services.ILineNumbers.CodeArea; +import org.eclipse.tm.tcf.services.IMemory.MemoryContext; +import org.eclipse.tm.tcf.services.IMemory.MemoryError; +import org.eclipse.tm.tcf.services.IMemoryMap.MemoryRegion; +import org.eclipse.tm.tcf.services.IPathMap.PathMapRule; +import org.eclipse.tm.tcf.services.IRegisters.RegistersContext; +import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; +import org.eclipse.tm.tcf.services.ISymbols.Symbol; + +class TestRCBP1 implements ITCFTest, IRunControl.RunControlListener { + + private final TCFTestSuite test_suite; + private final RunControl test_rc; + private final int channel_id; + private final List<PathMapRule> path_map; + private final Map<String,ArrayList<MemoryRegion>> mem_map; + private final IDiagnostics srv_diag; + private final ISymbols srv_syms; + private final IMemory srv_memory; + private final IRunControl srv_run_ctrl; + private final IRegisters srv_registers; + private final IBreakpoints srv_breakpoints; + private final ILineNumbers srv_line_numbers; + private final IDisassembly srv_disassembly; + private final IPathMap srv_path_map; + private final IMemoryMap srv_memory_map; + private final Map<String,IRunControl.RunControlContext> threads = new HashMap<String,IRunControl.RunControlContext>(); + private final Map<String,SuspendedContext> suspended = new HashMap<String,SuspendedContext>(); + private final Map<String,SuspendedContext> suspended_prev = new HashMap<String,SuspendedContext>(); + private final Map<String,IDisassemblyLine[]> disassembly_lines = new HashMap<String,IDisassemblyLine[]>(); + private final Map<String,Map<String,Object>[]> disassembly_capabilities = new HashMap<String,Map<String,Object>[]>(); + private final Set<String> running = new HashSet<String>(); + private final Set<IToken> get_state_cmds = new HashSet<IToken>(); + private final Map<String,Map<String,IRegisters.RegistersContext>> regs = + new HashMap<String,Map<String,IRegisters.RegistersContext>>(); + private final Map<String,Map<String,Object>> bp_list = new HashMap<String,Map<String,Object>>(); + private final Map<String,IDiagnostics.ISymbol> sym_list = new HashMap<String,IDiagnostics.ISymbol>(); + private final Random rnd = new Random(); + + private String[] test_list; + private String test_id; + private boolean path_map_done; + private boolean mem_map_done; + private String test_ctx_id; // Test context ID + private IRunControl.RunControlContext test_context; + private String main_thread_id; + private Map<String,Object> bp_capabilities; + private Runnable pending_cancel; + private int bp_cnt; + private boolean done_get_state; + private boolean done_disassembly; + private int resume_cnt = 0; + private IToken cancel_test_cmd; + private boolean bp_reset_done; + private boolean bp_set_done; + private boolean bp_change_done; + private boolean bp_sync_done; + private boolean data_bp_area_done; + private ILineNumbers.CodeArea data_bp_area; + private String data_bp_id; + private int data_bp_cnt; + private boolean mem_map_test_running; + private boolean mem_map_test_done; + private boolean all_setup_done; + + private static int mem_map_region_id = 0; + + private static class SuspendedContext { + final String id; + final String pc; + final String reason; + final Map<String,Object> params; + + boolean get_state_pending; + boolean ok_to_resume; + + SuspendedContext(String id, String pc, String reason, Map<String,Object> params) { + this.id = id; + this.pc = pc; + this.reason = reason; + this.params = params; + } + } + + private static class MemRegion implements MemoryRegion { + + private final Map<String,Object> props; + + MemRegion(Map<String,Object> props) { + this.props = props; + } + + public Number getAddress() { + return (Number)props.get(IMemoryMap.PROP_ADDRESS); + } + + public Number getSize() { + return (Number)props.get(IMemoryMap.PROP_SIZE); + } + + public Number getOffset() { + return (Number)props.get(IMemoryMap.PROP_OFFSET); + } + + public String getFileName() { + return (String)props.get(IMemoryMap.PROP_FILE_NAME); + } + + public String getSectionName() { + return (String)props.get(IMemoryMap.PROP_SECTION_NAME); + } + + public int getFlags() { + Number n = (Number)props.get(IMemoryMap.PROP_FLAGS); + if (n != null) return n.intValue(); + return 0; + } + + public Map<String,Object> getProperties() { + return props; + } + } + + private final IBreakpoints.BreakpointsListener bp_listener = new IBreakpoints.BreakpointsListener() { + + @SuppressWarnings("unchecked") + public void breakpointStatusChanged(String id, Map<String,Object> status) { + if (bp_list.get(id) != null && test_context != null && bp_cnt < 40) { + if (test_context != null) { + String prs = test_context.getProcessID(); + if (prs != null) { + for (ITCFTest test : test_suite.getActiveTests()) { + if (test instanceof TestRCBP1) { + TestRCBP1 rcbp = (TestRCBP1)test; + if (!rcbp.mem_map_test_running) continue; + if (prs.equals(rcbp.test_context.getProcessID())) return; + } + } + } + } + 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 (test_context.getProcessID().equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null) + err = (String)map.get(IBreakpoints.INSTANCE_ERROR); + } + if (err != null) { + if (bp_cnt == 0 && id.equals(data_bp_id)) return; + exit(new Exception("Invalid BP status: " + err)); + } + } + } + + public void contextAdded(Map<String,Object>[] bps) { + for (Map<String,Object> m0 : bps) { + String id = (String)m0.get(IBreakpoints.PROP_ID); + Map<String,Object> m1 = bp_list.get(id); + if (!checkBPData(m0, m1)) return; + } + } + + public void contextChanged(Map<String,Object>[] bps) { + for (Map<String,Object> m0 : bps) { + String id = (String)m0.get(IBreakpoints.PROP_ID); + Map<String,Object> m1 = bp_list.get(id); + if (!checkBPData(m0, m1)) return; + } + } + + public void contextRemoved(String[] ids) { + if (!bp_change_done) return; + for (String id : ids) { + if (bp_list.get(id) != null) { + exit(new Exception("Invalid Breakpoints.contextRemoved event")); + return; + } + } + } + + private boolean checkBPData(Map<String,Object> m0, Map<String,Object> m1) { + if (m1 == null) return true; + m0 = new HashMap<String,Object>(m0); + if (m0.get(IBreakpoints.PROP_ENABLED) == null) m0.put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + if (m1.get(IBreakpoints.PROP_ENABLED) == null) m1.put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + if (!m1.equals(m0)) { + exit(new Exception("Invalid data in Breakpoints event: " + m0 + " != " + m1)); + return false; + } + return true; + } + }; + + TestRCBP1(TCFTestSuite test_suite, RunControl test_rc, IChannel channel, int channel_id, + List<PathMapRule> path_map, Map<String,ArrayList<MemoryRegion>> mem_map) { + this.test_suite = test_suite; + this.test_rc = test_rc; + this.channel_id = channel_id; + this.path_map = path_map; + this.mem_map = mem_map; + srv_diag = channel.getRemoteService(IDiagnostics.class); + srv_syms = channel.getRemoteService(ISymbols.class); + srv_memory = channel.getRemoteService(IMemory.class); + srv_run_ctrl = channel.getRemoteService(IRunControl.class); + srv_registers = channel.getRemoteService(IRegisters.class); + srv_breakpoints = channel.getRemoteService(IBreakpoints.class); + srv_line_numbers = channel.getRemoteService(ILineNumbers.class); + srv_disassembly = channel.getRemoteService(IDisassembly.class); + srv_path_map = channel.getRemoteService(IPathMap.class); + srv_memory_map = channel.getRemoteService(IMemoryMap.class); + } + + public void start() { + if (srv_run_ctrl == null) { + test_suite.done(this, null); + } + else { + if (srv_breakpoints != null) srv_breakpoints.addListener(bp_listener); + runTest(); + } + } + + private void runTest() { + if (!test_suite.isActive(this)) return; + if (!path_map_done) { + setPathMap(); + return; + } + if (!mem_map_done) { + setMemMap(); + return; + } + if (test_list == null) { + getTestList(); + return; + } + if (!bp_reset_done) { + resetBreakpoints(); + return; + } + if (test_id != null) { + if (test_ctx_id == null) { + startTestContext(); + return; + } + if (test_context == null) { + getTestContext(); + return; + } + if (sym_list.isEmpty()) { + getSymbols(); + return; + } + if (!data_bp_area_done) { + getDataBPFile(); + return; + } + if (bp_capabilities == null) { + getBreakpointCapabilities(); + return; + } + if (!bp_set_done) { + iniBreakpoints(); + return; + } + } + if (!done_get_state) { + assert get_state_cmds.isEmpty(); + assert threads.isEmpty(); + assert running.isEmpty(); + assert suspended.isEmpty(); + getContextState(test_ctx_id); + return; + } + if (srv_disassembly != null && !done_disassembly) { + assert get_state_cmds.isEmpty(); + assert disassembly_lines.isEmpty(); + getDisassemlyLines(); + return; + } + if (test_id != null) { + if (!bp_change_done) { + changeBreakpoints(); + return; + } + if (!mem_map_test_done) { + runMemoryMapTest(); + return; + } + assert resume_cnt == 0; + assert !all_setup_done; + all_setup_done = true; + for (SuspendedContext s : suspended.values()) resume(s.id); + } + else if (suspended.size() > 0) { + final int test_cnt = suspended.size(); + Runnable done = new Runnable() { + int done_cnt; + public void run() { + done_cnt++; + if (done_cnt == test_cnt) { + exit(null); + } + } + }; + for (SuspendedContext sc : suspended.values()) runRegistersTest(sc, done); + } + else { + exit(null); + } + } + + private void getTestList() { + if (srv_diag == null) { + test_list = new String[0]; + runTest(); + return; + } + srv_diag.getTestList(new IDiagnostics.DoneGetTestList() { + public void doneGetTestList(IToken token, Throwable error, String[] list) { + if (error != null) { + exit(error); + } + else { + if (list == null) list = new String[0]; + if (list.length > 0) test_id = list[rnd.nextInt(list.length)]; + test_list = list; + runTest(); + } + } + }); + } + + private void setPathMap() { + if (srv_path_map == null || path_map == null) { + path_map_done = true; + runTest(); + return; + } + srv_path_map.set(path_map.toArray(new PathMapRule[path_map.size()]), new IPathMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + path_map_done = true; + runTest(); + } + } + }); + } + + private void setMemMap() { + if (mem_map == null || mem_map.size() == 0) { + mem_map_done = true; + runTest(); + return; + } + final Set<IToken> cmds = new HashSet<IToken>(); + for (String id : mem_map.keySet()) { + ArrayList<MemoryRegion> l = mem_map.get(id); + cmds.add(srv_memory_map.set(id, l.toArray(new MemoryRegion[l.size()]), new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + cmds.remove(token); + if (error instanceof IErrorReport) { + IErrorReport e = (IErrorReport)error; + if (e.getErrorCode() == IErrorReport.TCF_ERROR_INV_CONTEXT) error = null; + } + if (error != null) { + exit(error); + } + else if (cmds.size() == 0) { + mem_map_done = true; + runTest(); + } + } + })); + } + assert cmds.size() > 0; + } + + private void resetBreakpoints() { + if (srv_breakpoints == null) { + bp_reset_done = true; + runTest(); + return; + } + // Reset breakpoint list (previous tests might left breakpoints) + srv_breakpoints.set(null, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) { + exit(error); + return; + } + bp_reset_done = true; + runTest(); + } + }); + } + + private void getBreakpointCapabilities() { + if (srv_breakpoints == null) { + bp_capabilities = new HashMap<String,Object>(); + runTest(); + return; + } + srv_breakpoints.getCapabilities(test_ctx_id, new IBreakpoints.DoneGetCapabilities() { + public void doneGetCapabilities(IToken token, Exception error, Map<String,Object> capabilities) { + if (!test_suite.isActive(TestRCBP1.this)) return; + if (error != null) { + exit(error); + return; + } + Boolean l = (Boolean)capabilities.get(IBreakpoints.CAPABILITY_LOCATION); + Boolean c = (Boolean)capabilities.get(IBreakpoints.CAPABILITY_CONDITION); + if (l == null || !l) { + exit(new Exception("Breakpoints service does not support \"Location\" attribute")); + return; + } + if (c == null || !c) { + exit(new Exception("Breakpoints service does not support \"Condition\" attribute")); + return; + } + bp_capabilities = capabilities; + runTest(); + } + }); + } + + private void startTestContext() { + srv_diag.runTest(test_id, new IDiagnostics.DoneRunTest() { + public void doneRunTest(IToken token, Throwable error, String context_id) { + if (error != null) { + exit(error); + } + else if (test_suite.isActive(TestRCBP1.this)) { + assert test_ctx_id == null; + test_ctx_id = context_id; + if (pending_cancel != null) { + exit(null); + } + else { + runTest(); + } + } + } + }); + } + + private void getTestContext() { + srv_run_ctrl.getContext(test_ctx_id, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, RunControlContext context) { + if (test_suite.cancel) return; + if (error != null) { + exit(error); + return; + } + test_context = context; + assert test_ctx_id.equals(context.getID()); + srv_run_ctrl.addListener(TestRCBP1.this); + runTest(); + } + }); + } + + private void getSymbols() { + final HashMap<IToken,String> cmds = new HashMap<IToken,String>(); + IDiagnostics.DoneGetSymbol done = new IDiagnostics.DoneGetSymbol() { + public void doneGetSymbol(IToken token, Throwable error, ISymbol symbol) { + String name = cmds.remove(token); + if (error != null) { + exit(error); + return; + } + if (!test_suite.isActive(TestRCBP1.this)) return; + assert test_ctx_id != null; + if (symbol == null) { + exit(new Exception("Symbol must not be NULL: " + name)); + } + else if (!symbol.isAbs()) { + exit(new Exception("Symbol must be absolute: " + name)); + } + else if (symbol.getValue() == null || symbol.getValue().longValue() == 0) { + exit(new Exception("Symbol value must not be NULL: " + name)); + } + else { + sym_list.put(name, symbol); + if (cmds.isEmpty()) runTest(); + } + } + }; + String[] syms = { + "tcf_test_func0", + "tcf_test_func1", + "tcf_test_func2", + "tcf_test_func3", + "tcf_test_array" + }; + String prs = test_context.getProcessID(); + for (String name : syms) cmds.put(srv_diag.getSymbol(prs, name, done), name); + } + + private void getDataBPFile() { + ISymbol sym = sym_list.get("tcf_test_func3"); + if (sym == null || srv_line_numbers == null) { + data_bp_area_done = true; + runTest(); + return; + } + srv_line_numbers.mapToSource(test_ctx_id, sym.getValue(), sym.getValue().longValue() + 1, new ILineNumbers.DoneMapToSource() { + public void doneMapToSource(IToken token, Exception error, CodeArea[] areas) { + if (error != null) { + exit(error); + } + else { + if (areas != null && areas.length > 0) data_bp_area = areas[0]; + data_bp_area_done = true; + runTest(); + } + } + }); + } + + @SuppressWarnings("unchecked") + private void iniBreakpoints() { + assert !bp_set_done; + assert bp_list.isEmpty(); + Map<String,Object> m[] = new Map[8]; + for (int i = 0; i < m.length; i++) { + m[i] = new HashMap<String,Object>(); + m[i].put(IBreakpoints.PROP_ID, "TcfTestBP" + i + "" + channel_id); + m[i].put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + switch (i) { + case 0: + m[i].put(IBreakpoints.PROP_LOCATION, sym_list.get("tcf_test_func0").getValue().toString()); + // Condition is always true + m[i].put(IBreakpoints.PROP_CONDITION, "$thread!=\"\""); + break; + case 1: + m[i].put(IBreakpoints.PROP_LOCATION, sym_list.get("tcf_test_func0").getValue().toString()); + // Condition is always false + m[i].put(IBreakpoints.PROP_CONDITION, "$thread==\"\""); + break; + case 2: + // Second breakpoint at same address + m[i].put(IBreakpoints.PROP_LOCATION, "tcf_test_func0"); + break; + case 3: + // Location is an expression + m[i].put(IBreakpoints.PROP_LOCATION, "(31+1)/16+tcf_test_func1-2"); + // Condition is always true + m[i].put(IBreakpoints.PROP_CONDITION, "tcf_test_func0!=tcf_test_func1"); + break; + case 4: + // Disabled breakpoint + m[i].put(IBreakpoints.PROP_LOCATION, "tcf_test_func2"); + m[i].put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + break; + case 5: + // Breakpoint that will be enabled with "enable" command + m[i].put(IBreakpoints.PROP_LOCATION, "tcf_test_func2"); + m[i].put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + break; + case 6: + m[i].put(IBreakpoints.PROP_LOCATION, "tcf_test_func3"); + break; + case 7: + // Data breakpoint + m[i].put(IBreakpoints.PROP_LOCATION, "&tcf_test_char"); + m[i].put(IBreakpoints.PROP_ACCESSMODE, IBreakpoints.ACCESSMODE_WRITE); + Number ca = (Number)bp_capabilities.get(IBreakpoints.CAPABILITY_ACCESSMODE); + if (data_bp_area != null && ca != null && (ca.intValue() & IBreakpoints.ACCESSMODE_WRITE) != 0) { + m[i].put(IBreakpoints.PROP_FILE, data_bp_area.file); + m[i].put(IBreakpoints.PROP_LINE, data_bp_area.start_line); + data_bp_id = (String)m[i].get(IBreakpoints.PROP_ID); + } + else { + m[i].put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + } + break; + } + bp_list.put((String)m[i].get(IBreakpoints.PROP_ID), m[i]); + } + srv_breakpoints.set(m, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + assert !bp_set_done; + bp_set_done = true; + if (error != null) { + exit(error); + return; + } + runTest(); + } + }); + } + + private void getContextState(final String id) { + get_state_cmds.add(srv_run_ctrl.getChildren(id, new IRunControl.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] contexts) { + get_state_cmds.remove(token); + if (test_suite.cancel) return; + if (error != null) { + exit(error); + return; + } + for (String s : contexts) getContextState(s); + if (get_state_cmds.isEmpty()) doneContextState(); + } + })); + if (id == null) return; + get_state_cmds.add(srv_run_ctrl.getContext(id, new IRunControl.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, RunControlContext ctx) { + get_state_cmds.remove(token); + if (test_suite.cancel) return; + if (error != null) { + exit(error); + return; + } + if (test_id != null) { + assert test_ctx_id != null; + assert isMyContext(ctx); + for (ITCFTest t : test_suite.getActiveTests()) { + if (t != TestRCBP1.this && t instanceof TestRCBP1 && ((TestRCBP1)t).threads.get(id) != null) { + exit(new Exception("Invalid or missing 'CreatorID' context attribute.\nContext: " + ctx)); + return; + } + } + } + if (ctx.hasState()) { + threads.put(id, ctx); + get_state_cmds.add(ctx.getState(new IRunControl.DoneGetState() { + public void doneGetState(IToken token, Exception error, + boolean susp, String pc, String reason, + Map<String, Object> params) { + get_state_cmds.remove(token); + if (test_suite.cancel) return; + if (error != null) { + exit(new Exception("Cannot get context state", error)); + return; + } + if (!susp) { + if (suspended.get(id) != null) { + exit(new Exception("Invalid result of getState command")); + return; + } + running.add(id); + } + else { + assert threads.get(id) != null; + if (running.contains(id)) { + exit(new Exception("Invalid result of getState command")); + return; + } + SuspendedContext sc = suspended.get(id); + if (sc != null && sc.pc != null && !sc.pc.equals(pc)) { + exit(new Exception("Invalid result of getState command: invalid PC. Context: " + id)); + return; + } + if (sc != null && sc.reason != null && !sc.reason.equals(reason)) { + exit(new Exception("Invalid result of getState command: invalid suspend reason. Context: " + id)); + return; + } + if (test_id != null && "Breakpoint".equals(reason)) { + exit(new Exception("Invalid suspend reason of main thread " + + id + " after test start: " + reason + " " + pc)); + return; + } + assert !done_get_state; + suspended.put(id, new SuspendedContext(id, pc, reason, params)); + } + if (get_state_cmds.isEmpty()) doneContextState(); + } + })); + } + if (get_state_cmds.isEmpty()) doneContextState(); + } + })); + } + + private void doneContextState() { + assert !done_get_state; + assert get_state_cmds.isEmpty(); + assert resume_cnt == 0; + assert threads.size() == suspended.size() + running.size(); + done_get_state = true; + runTest(); + } + + private void getDisassemlyLines() { + for (final String id : suspended.keySet()) { + SuspendedContext sc = suspended.get(id); + get_state_cmds.add(srv_disassembly.getCapabilities(id, new IDisassembly.DoneGetCapabilities() { + public void doneGetCapabilities(IToken token, Throwable error, Map<String,Object>[] arr) { + get_state_cmds.remove(token); + if (error != null) { + exit(error); + } + else { + disassembly_capabilities.put(id, arr); + if (get_state_cmds.isEmpty()) doneDisassembly(); + } + } + })); + if (sc.pc == null) { + disassembly_lines.put(id, new IDisassemblyLine[0]); + continue; + } + BigInteger pc = new BigInteger(sc.pc); + get_state_cmds.add(srv_disassembly.disassemble(id, pc, 1, null, new IDisassembly.DoneDisassemble() { + public void doneDisassemble(IToken token, Throwable error, IDisassemblyLine[] arr) { + get_state_cmds.remove(token); + if (error != null) { + exit(error); + } + else { + disassembly_lines.put(id, arr); + if (get_state_cmds.isEmpty()) doneDisassembly(); + } + } + })); + } + if (get_state_cmds.isEmpty()) doneDisassembly(); + } + + private void doneDisassembly() { + assert !done_disassembly; + assert get_state_cmds.isEmpty(); + if (!test_suite.isActive(TestRCBP1.this)) return; + assert suspended.size() == disassembly_lines.size(); + done_disassembly = true; + runTest(); + } + + private void changeBreakpoints() { + assert !bp_change_done; + final String bp_id = "TcfTestBP5" + channel_id; + final Map<String,Object> m = bp_list.get(bp_id); + ArrayList<String> l = new ArrayList<String>(); + l.add(test_context.getProcessID()); + Boolean ci = (Boolean)bp_capabilities.get(IBreakpoints.CAPABILITY_CONTEXTIDS); + if (ci != null && ci) m.put(IBreakpoints.PROP_CONTEXTIDS, l); + Boolean sg = (Boolean)bp_capabilities.get(IBreakpoints.CAPABILITY_STOP_GROUP); + if (sg != null && sg) m.put(IBreakpoints.PROP_STOP_GROUP, l); + StringBuffer bf = new StringBuffer(); + for (String id : threads.keySet()) { + if (bf.length() > 0) bf.append(" || "); + bf.append("$thread==\""); + bf.append(id); + bf.append('"'); + } + m.put(IBreakpoints.PROP_CONDITION, bf.toString()); + bp_list.put(bp_id, m); + srv_breakpoints.change(m, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + bp_change_done = true; + if (error != null) exit(error); + } + }); + srv_breakpoints.getIDs(new IBreakpoints.DoneGetIDs() { + public void doneGetIDs(IToken token, Exception error, String[] ids) { + if (error != null) { + exit(error); + return; + } + if (!bp_change_done) { + exit(new Exception("Invalid responce order")); + return; + } + HashSet<String> s = new HashSet<String>(); + for (String id : ids) s.add(id); + if (ids.length != s.size()) { + exit(new Exception("Invalis BP list: " + ids)); + return; + } + for (String id : bp_list.keySet()) { + if (!s.contains(id)) { + exit(new Exception("BP is not listed by Breakpoints.getIDs: " + id)); + return; + } + } + } + }); + for (final String id : bp_list.keySet()) { + srv_breakpoints.getProperties(id, new IBreakpoints.DoneGetProperties() { + public void doneGetProperties(IToken token, Exception error, Map<String,Object> properties) { + if (error != null) { + exit(error); + return; + } + HashMap<String,Object> m0 = new HashMap<String,Object>(properties); + HashMap<String,Object> m1 = (HashMap<String,Object>)bp_list.get(id); + if (m0.get(IBreakpoints.PROP_ENABLED) == null) m0.put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + if (m1.get(IBreakpoints.PROP_ENABLED) == null) m1.put(IBreakpoints.PROP_ENABLED, Boolean.FALSE); + if (!m1.equals(m0)) { + exit(new Exception("Invalid data returned by Breakpoints.getProperties: " + m0 + " != " + m1)); + return; + } + } + }); + srv_breakpoints.getStatus(id, new IBreakpoints.DoneGetStatus() { + public void doneGetStatus(IToken token, Exception error, Map<String,Object> status) { + if (error != null) { + exit(error); + return; + } + } + }); + } + Protocol.sync(new Runnable() { + public void run() { + if (!test_suite.isActive(TestRCBP1.this)) return; + if (!bp_change_done) { + exit(new Exception("Protocol.sync() test failed")); + return; + } + m.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE); + srv_breakpoints.enable(new String[]{ bp_id }, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + bp_sync_done = true; + runTest(); + } + }); + } + + public void containerResumed(String[] context_ids) { + for (String id : context_ids) contextResumed(id); + } + + public void containerSuspended(String context, String pc, + String reason, Map<String, Object> params, + String[] suspended_ids) { + for (String id : suspended_ids) { + if (id.equals(context)) continue; + contextSuspended(id, null, null, null); + } + contextSuspended(context, pc, reason, params); + } + + public void contextAdded(RunControlContext[] contexts) { + for (RunControlContext ctx : contexts) { + String id = ctx.getID(); + if (threads.get(id) != null) { + exit(new Exception("Invalid contextAdded event:\nContext: " + ctx)); + return; + } + if (isMyContext(ctx)) { + for (ITCFTest t : test_suite.getActiveTests()) { + if (t instanceof TestRCBP1 && ((TestRCBP1)t).threads.get(id) != null) { + exit(new Exception("Invalid or missing 'CreatorID' context attribute.\nContext: " + ctx)); + return; + } + } + if (threads.size() > 0 && !all_setup_done) { + assert !canResume(id); + exit(new Exception("Unexpected contextAdded event\nContext: " + ctx)); + return; + } + if (ctx.hasState()) { + threads.put(id, ctx); + if (!done_get_state) { + getContextState(id); + } + else { + running.add(id); + } + } + } + } + } + + public void contextChanged(RunControlContext[] contexts) { + for (RunControlContext ctx : contexts) { + String id = ctx.getID(); + if (id.equals(test_ctx_id)) test_context = ctx; + if (threads.get(id) != null) { + assert isMyContext(ctx); + threads.put(id, ctx); + } + } + } + + public void contextException(String id, String msg) { + RunControlContext ctx = threads.get(id); + if (ctx != null) { + assert isMyContext(ctx); + exit(new Exception("Context exception: " + msg)); + } + } + + public void contextRemoved(String[] contexts) { + for (String id : contexts) { + if (suspended.get(id) != null) { + exit(new Exception("Invalid contextRemoved event")); + return; + } + running.remove(id); + if (threads.remove(id) != null && threads.isEmpty()) { + if (bp_cnt != 40) { + exit(new Exception("Test main thread breakpoint count = " + bp_cnt + ", expected 40")); + } + if (data_bp_id != null && data_bp_cnt != 10) { + exit(new Exception("Test main thread data breakpoint count = " + data_bp_cnt + ", expected 10")); + } + srv_run_ctrl.removeListener(this); + // Reset breakpoint list + bp_list.clear(); + srv_breakpoints.set(null, new IBreakpoints.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + exit(error); + } + }); + } + } + } + + public void contextResumed(String id) { + IRunControl.RunControlContext ctx = threads.get(id); + if (ctx == null) return; + assert isMyContext(ctx); + if (!ctx.hasState()) { + exit(new Exception("Resumed event for context that HasState = false")); + return; + } + SuspendedContext sc = suspended.remove(id); + if (!done_get_state || sc == null || !sc.ok_to_resume || sc.get_state_pending && ctx.getRCGroup() == null) { + assert !canResume(id); + exit(new Exception("Unexpected contextResumed event: " + id)); + return; + } + if (isMyBreakpoint(sc)) suspended_prev.put(id, sc); + running.add(id); + } + + private long getSymAddr(String sym) { + return sym_list.get(sym).getValue().longValue(); + } + + private String toSymName(long addr) { + for (String name : sym_list.keySet()) { + if (getSymAddr(name) == addr) return name; + } + return "0x" + Long.toHexString(addr); + } + + private void checkSuspendedContext(SuspendedContext sc, String sym) { + long pc = Long.parseLong(sc.pc); + long ss = getSymAddr(sym); + if (pc != ss || !"Breakpoint".equals(sc.reason)) { + exit(new Exception("Invalid contextSuspended event: " + + sc.id + " '" + toSymName(pc) + "' " + sc.pc + " " + sc.reason + + ", expected breakpoint at '" + sym + "' " + ss)); + } + String bp_id = null; + if (sc.params != null) { + Object ids = sc.params.get(IRunControl.STATE_BREAKPOINT_IDS); + if (ids != null) { + @SuppressWarnings("unchecked") + Collection<String> c = (Collection<String>)ids; + HashSet<String> set = new HashSet<String>(); + for (String id : c) { + if (!set.add(id)) { + exit(new Exception("Invalid value of 'BPs' attribute: duplicate items")); + return; + } + if (bp_list.get(id) != null) { + bp_id = id; + break; + } + } + if (bp_id == null) { + exit(new Exception("Invalid value of 'BPs' attribute in a context state")); + } + } + } + } + + private void checkSuspendedContext(final SuspendedContext sc) { + boolean my_breakpoint = isMyBreakpoint(sc); + if (main_thread_id == null && my_breakpoint) { + // Process main thread should be the first to hit a breakpoint in the test + if (!done_get_state) { + exit(new Exception("Unexpeceted breakpoint hit")); + return; + } + main_thread_id = sc.id; + } + if (main_thread_id == null) { + if (all_setup_done) resume(sc.id); + return; + } + if (my_breakpoint) { + if (sc.id.equals(main_thread_id)) bp_cnt++; + SuspendedContext sp = suspended_prev.get(sc.id); + String sp_sym = sp == null ? null : toSymName(Long.parseLong(sp.pc)); + if (sp == null) { + checkSuspendedContext(sc, "tcf_test_func0"); + } + else if ("tcf_test_func0".equals(sp_sym)) { + checkSuspendedContext(sc, "tcf_test_func1"); + } + else if ("tcf_test_func1".equals(sp_sym)) { + if (sc.id.equals(main_thread_id)) { + checkSuspendedContext(sc, "tcf_test_func2"); + } + else { + checkSuspendedContext(sc, "tcf_test_func3"); + } + } + else if ("tcf_test_func2".equals(sp_sym)) { + checkSuspendedContext(sc, "tcf_test_func3"); + } + else if ("tcf_test_func3".equals(sp_sym)) { + checkSuspendedContext(sc, "tcf_test_func0"); + } + } + else if (isMyDataBreakpoint(sc)) { + if (sc.id.equals(main_thread_id)) data_bp_cnt++; + } + if (!all_setup_done) return; + if (!test_suite.isActive(this)) return; + Runnable done = new Runnable() { + public void run() { + if (suspended.get(sc.id) == sc) resume(sc.id); + } + }; + if (my_breakpoint) { + switch (rnd.nextInt(5)) { + case 0: + runMemoryTest(sc, done); + break; + case 1: + runRegistersTest(sc, done); + break; + case 2: + runLineNumbersTest(sc, done); + break; + case 3: + runSymbolsTest(sc, done); + break; + default: + done.run(); + break; + } + } + else { + done.run(); + } + } + + private boolean isMyContext(IRunControl.RunControlContext ctx) { + // Check if the context was created by this test + if (test_ctx_id == null) return false; + return test_ctx_id.equals(ctx.getID()) || + test_ctx_id.equals(ctx.getParentID()) || + test_ctx_id.equals(ctx.getCreatorID()); + } + + private boolean isMyBreakpoint(SuspendedContext sc) { + // Check if the context is suspended by one of our breakpoints + if (!"Breakpoint".equals(sc.reason)) return false; + long pc = Long.parseLong(sc.pc); + for (IDiagnostics.ISymbol sym : sym_list.values()) { + if (pc == sym.getValue().longValue()) return true; + } + return false; + } + + private boolean isMyDataBreakpoint(SuspendedContext sc) { + // Check if the context is suspended by our data breakpoints + if (data_bp_id == null) return false; + if (!"Breakpoint".equals(sc.reason)) return false; + if (sc.params == null) return false; + Object ids = sc.params.get(IRunControl.STATE_BREAKPOINT_IDS); + if (ids != null) { + @SuppressWarnings("unchecked") + Collection<String> c = (Collection<String>)ids; + if (c.contains(data_bp_id)) return true; + } + return false; + } + + public void contextSuspended(final String id, String pc, String reason, Map<String, Object> params) { + IRunControl.RunControlContext ctx = threads.get(id); + if (ctx == null) return; + assert isMyContext(ctx); + if (!ctx.hasState()) { + exit(new Exception("Suspended event for context that HasState = false")); + return; + } + running.remove(id); + SuspendedContext sc = suspended.get(id); + if (sc != null) { + if (done_get_state || pc != null && !sc.pc.equals(pc) || reason != null && !sc.reason.equals(reason)) { + exit(new Exception("Invalid contextSuspended event")); + return; + } + } + else { + sc = new SuspendedContext(id, pc, reason, params); + assert !done_get_state || done_disassembly || srv_disassembly == null; + suspended.put(id, sc); + } + if (!all_setup_done) return; + assert get_state_cmds.size() == 0; + assert suspended.get(id) == sc; + assert !sc.get_state_pending; + sc.get_state_pending = true; + final SuspendedContext sc0 = sc; + ctx.getState(new IRunControl.DoneGetState() { + public void doneGetState(IToken token, Exception error, boolean susp, + String pc, String reason, Map<String, Object> params) { + if (error != null) { + exit(new Exception("Cannot get context state", error)); + } + else if (suspended.get(id) != sc0) { + exit(new Exception("Context resumed before RunControl.getState result")); + } + else if (!susp) { + exit(new Exception("Invalid RunControl.getState result")); + } + else if (pc == null || pc.equals("0")) { + exit(new Exception("Invalid PC returned by RunControl.getState")); + } + else if (test_suite.isActive(TestRCBP1.this)) { + SuspendedContext sc = suspended.get(id); + assert sc.get_state_pending; + sc.get_state_pending = false; + if (sc.pc == null || sc.reason == null) { + sc = new SuspendedContext(id, pc, reason, params); + assert !done_get_state || done_disassembly || srv_disassembly == null; + suspended.put(id, sc); + } + else if (!sc.pc.equals(pc) || !sc.reason.equals(reason)) { + exit(new Exception("Invalid RunControl.getState result")); + return; + } + checkSuspendedContext(sc); + } + } + }); + } + + public boolean canResume(String id) { + if (test_ctx_id != null && threads.size() == 0) + // Don't know yet neither my thread IDs nor my RC group. + return false; + IRunControl.RunControlContext ctx = test_rc.getContext(id); + if (ctx == null) return false; + if (isMyContext(ctx) && (!all_setup_done || threads.get(id) == null)) + // My threads should stay suspended until all_setup_done + return false; + String grp = ctx.getRCGroup(); + for (IRunControl.RunControlContext x : threads.values()) { + if (x.getID().equals(id) || grp != null && grp.equals(x.getRCGroup())) { + SuspendedContext sc = suspended.get(x.getID()); + if (sc == null) return false; + if (!sc.ok_to_resume) return false; + } + } + return true; + } + + private void resume(String id) { + assert done_get_state || resume_cnt == 0; + assert bp_sync_done; + assert mem_map_test_done; + resume_cnt++; + SuspendedContext sc = suspended.get(id); + IRunControl.RunControlContext ctx = threads.get(id); + if (ctx != null && sc != null) { + assert !sc.get_state_pending; + assert !sc.ok_to_resume; + sc.ok_to_resume = true; + int rm = IRunControl.RM_RESUME; + if (isMyBreakpoint(sc)) { + rm = rnd.nextInt(6); + if (!ctx.canResume(rm)) rm = IRunControl.RM_RESUME; + } + test_rc.resume(id, rm); + } + } + + private void runMemoryTest(final SuspendedContext sc, final Runnable done) { + if (srv_memory == null || test_suite.target_lock) { + Protocol.invokeLater(done); + return; + } + test_suite.target_lock = true; + srv_memory.getContext(test_context.getProcessID(), new IMemory.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, final MemoryContext mem_ctx) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + if (!test_context.getProcessID().equals(mem_ctx.getID())) { + exit(new Exception("Bad memory context data: invalid ID")); + } + final boolean big_endian = mem_ctx.isBigEndian(); + final int addr_size = mem_ctx.getAddressSize(); + final byte[] buf = new byte[0x1000]; + mem_ctx.get(sym_list.get("tcf_test_array").getValue(), 1, buf, 0, addr_size, 0, new IMemory.DoneMemory() { + public void doneMemory(IToken token, MemoryError error) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + byte[] tmp = new byte[addr_size + 1]; + tmp[0] = 0; // Extra byte to avoid sign extension by BigInteger + if (big_endian) { + System.arraycopy(buf, 0, tmp, 1, addr_size); + } + else { + for (int i = 0; i < addr_size; i++) { + tmp[i + 1] = buf[addr_size - i - 1]; + } + } + Number mem_address = new BigInteger(tmp); + if (mem_address.longValue() == 0) { + exit(new Exception("Bad value of 'tcf_test_array': " + mem_address)); + } + testSetMemoryCommand(sc, mem_ctx, mem_address, buf, done); + } + }); + } + }); + } + + private void testSetMemoryCommand(final SuspendedContext sc, + final IMemory.MemoryContext mem_ctx, + final Number addr, final byte[] buf, + final Runnable done) { + final byte[] data = new byte[buf.length]; + rnd.nextBytes(data); + mem_ctx.set(addr, 1, data, 0, data.length, 0, new IMemory.DoneMemory() { + public void doneMemory(IToken token, MemoryError error) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + mem_ctx.get(addr, 1, buf, 0, buf.length, 0, new IMemory.DoneMemory() { + public void doneMemory(IToken token, MemoryError error) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + for (int i = 0; i < data.length; i++) { + if (data[i] != buf[i]) { + exit(new Exception( + "Invalid Memory.get responce: wrong data at offset " + i + + ", expected " + data[i] + ", actual " + buf[i])); + return; + } + } + testFillMemoryCommand(sc, mem_ctx, addr, buf, done); + } + }); + } + }); + } + + private void testFillMemoryCommand(final SuspendedContext sc, + final IMemory.MemoryContext mem_ctx, + final Number addr, final byte[] buf, + final Runnable done) { + final byte[] data = new byte[buf.length / 7]; + rnd.nextBytes(data); + mem_ctx.fill(addr, 1, data, buf.length, 0, new IMemory.DoneMemory() { + public void doneMemory(IToken token, MemoryError error) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + mem_ctx.get(addr, 1, buf, 0, buf.length, 0, new IMemory.DoneMemory() { + public void doneMemory(IToken token, MemoryError error) { + if (suspended.get(sc.id) != sc) { + test_suite.target_lock = false; + return; + } + if (error != null) { + exit(error); + return; + } + for (int i = 0; i < data.length; i++) { + if (data[i % data.length] != buf[i]) { + exit(new Exception( + "Invalid Memory.get responce: wrong data at offset " + i + + ", expected " + data[i % data.length] + ", actual " + buf[i])); + return; + } + } + test_suite.target_lock = false; + done.run(); + } + }); + } + }); + } + + private void runRegistersTest(final SuspendedContext sc, final Runnable done) { + if (srv_registers == null) { + Protocol.invokeLater(done); + return; + } + if (regs.get(sc.id) == null) { + final Map<String,IRegisters.RegistersContext> reg_map = + new HashMap<String,IRegisters.RegistersContext>(); + regs.put(sc.id, reg_map); + final Set<IToken> cmds = new HashSet<IToken>(); + cmds.add(srv_registers.getChildren(sc.id, new IRegisters.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) { + regs.remove(sc.id); + return; + } + if (error != null) { + exit(error); + return; + } + for (final String id : context_ids) { + cmds.add(srv_registers.getChildren(id, this)); + cmds.add(srv_registers.getContext(id, new IRegisters.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, RegistersContext context) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) { + regs.remove(sc.id); + return; + } + if (error != null) { + exit(error); + return; + } + reg_map.put(id, context); + if (cmds.isEmpty()) { + testGetSetRegisterCommands(sc, done); + } + } + })); + } + } + })); + } + else { + testGetSetRegisterCommands(sc, done); + } + } + + private void testGetSetRegisterCommands(final SuspendedContext sc, final Runnable done) { + Map<String,IRegisters.RegistersContext> reg_map = regs.get(sc.id); + final Set<IToken> cmds = new HashSet<IToken>(); + for (final IRegisters.RegistersContext ctx : reg_map.values()) { + if (!ctx.isReadable()) continue; + if (ctx.isReadOnce()) continue; + cmds.add(ctx.get(new IRegisters.DoneGet() { + public void doneGet(IToken token, Exception error, final byte[] value) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + exit(error); + return; + } + if (ctx.getSize() != value.length) { + exit(new Exception("Invalid register value size")); + return; + } + if (ctx.isWriteable() && !ctx.isWriteOnce()) { + cmds.add(ctx.set(value, new IRegisters.DoneSet() { + public void doneSet(IToken token, Exception error) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + exit(error); + return; + } + cmds.add(ctx.get(new IRegisters.DoneGet() { + public void doneGet(IToken token, Exception error, byte[] value1) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + exit(error); + return; + } + for (int i = 0; i < value.length; i++) { + if (value[i] != value1[i]) { + exit(new Exception("Invalid register value")); + return; + } + } + if (cmds.isEmpty()) { + done.run(); + } + } + })); + } + })); + } + if (cmds.isEmpty()) { + done.run(); + } + } + })); + } + if (!reg_map.isEmpty()) { + int data_size = 0; + List<IRegisters.Location> locs = new ArrayList<IRegisters.Location>(); + String[] ids = reg_map.keySet().toArray(new String[reg_map.size()]); + for (int i = 0; i < rnd.nextInt(32); i++) { + String id = ids[rnd.nextInt(ids.length)]; + IRegisters.RegistersContext ctx = reg_map.get(id); + if (!ctx.isReadable()) continue; + if (!ctx.isWriteable()) continue; + if (ctx.isReadOnce()) continue; + if (ctx.isWriteOnce()) continue; + if (ctx.getSize() == 0) continue; + int offs = rnd.nextInt(ctx.getSize()); + int size = rnd.nextInt(ctx.getSize() - offs) + 1; + locs.add(new IRegisters.Location(id, offs, size)); + data_size += size; + } + final int total_size = data_size; + final IRegisters.Location[] loc_arr = locs.toArray(new IRegisters.Location[locs.size()]); + cmds.add(srv_registers.getm(loc_arr, new IRegisters.DoneGet() { + public void doneGet(IToken token, Exception error, byte[] value) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error == null && value.length != total_size) { + error = new Exception("Invalid data size in Registers.getm reply"); + } + if (error != null) { + exit(error); + return; + } + cmds.add(srv_registers.setm(loc_arr, value, new IRegisters.DoneSet() { + public void doneSet(IToken token, Exception error) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + exit(error); + return; + } + if (cmds.isEmpty()) { + done.run(); + } + } + })); + } + })); + } + if (cmds.isEmpty()) { + done.run(); + } + } + + private void runLineNumbersTest(SuspendedContext sc, final Runnable done) { + if (srv_line_numbers != null && sc.pc != null) { + BigInteger x = new BigInteger(sc.pc); + BigInteger y = x.add(BigInteger.valueOf(1)); + srv_line_numbers.mapToSource(sc.id, x, y, new ILineNumbers.DoneMapToSource() { + public void doneMapToSource(IToken token, Exception error, CodeArea[] areas) { + if (error != null) { + exit(error); + return; + } + done.run(); + } + }); + } + else { + done.run(); + } + } + + private void runSymbolsTest(final SuspendedContext sc, final Runnable done) { + if (srv_syms != null && sc.pc != null) { + final BigInteger x = new BigInteger(sc.pc); + srv_syms.findByAddr(sc.id, x, new ISymbols.DoneFind() { + public void doneFind(IToken token, Exception error, String symbol_id) { + if (error != null) { + int code = IErrorReport.TCF_ERROR_OTHER; + if (error instanceof IErrorReport) code = ((IErrorReport)error).getErrorCode(); + switch (code) { + case IErrorReport.TCF_ERROR_INV_COMMAND: + case IErrorReport.TCF_ERROR_SYM_NOT_FOUND: + done.run(); + return; + default: + exit(error); + return; + } + } + srv_syms.getContext(symbol_id, new ISymbols.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, Symbol context) { + if (error != null) { + exit(error); + return; + } + Number addr = context.getAddress(); + int size = context.getSize(); + if (addr == null) { + exit(new Exception("Missing symbol address attribute")); + return; + } + if (size <= 0) { + exit(new Exception("Invalid symbol size attribute")); + return; + } + BigInteger y = JSON.toBigInteger(addr); + BigInteger z = y.add(BigInteger.valueOf(size)); + if (x.compareTo(y) < 0 || x.compareTo(z) >= 0) { + exit(new Exception("Invalid symbol address attribute")); + return; + } + String name = context.getName(); + if (name == null) { + done.run(); + return; + } + srv_syms.find(sc.id, 0, name, new ISymbols.DoneFind() { + public void doneFind(IToken token, Exception error, String symbol_id) { + if (error != null) { + exit(error); + return; + } + done.run(); + } + }); + } + }); + } + }); + } + else { + done.run(); + } + } + + private String getRandomString() { + int l = rnd.nextInt(512) + 1; + StringBuffer bf = new StringBuffer(l); + for (int i = 0; i < l; i++) { + bf.append((char)(rnd.nextInt(0xffff) + 1)); + } + return bf.toString(); + } + + private void runMemoryMapTest() { + assert !mem_map_test_running; + if (srv_memory_map == null || test_context == null || test_context.getProcessID() == null) { + mem_map_test_done = true; + runTest(); + return; + } + mem_map_test_running = true; + final String prs_id = test_context.getProcessID(); + srv_memory_map.get(prs_id, new IMemoryMap.DoneGet() { + public void doneGet(IToken token, Exception error, MemoryRegion[] map) { + if (error != null) { + exit(error); + return; + } + final Map<String,Object> props = new HashMap<String,Object>(); + final String test_id = "TestRCBP1." + mem_map_region_id++; + props.put(IMemoryMap.PROP_ID, test_id); + if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_ADDRESS, rnd.nextInt(0x10000000)); + if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_SIZE, rnd.nextInt(0x10000000)); + if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_FLAGS, rnd.nextInt(0x7)); + if (rnd.nextBoolean()) { + props.put(IMemoryMap.PROP_FILE_NAME, getRandomString()); + if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_SECTION_NAME, getRandomString()); + else if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_OFFSET, rnd.nextInt(0x10000000)); + if (rnd.nextBoolean()) props.put(IMemoryMap.PROP_BSS, true); + } + List<MemoryRegion> list = new ArrayList<MemoryRegion>(); + for (MemoryRegion r : map) { + String id = (String)r.getProperties().get(IMemoryMap.PROP_ID); + if (id != null) list.add(r); + } + final List<MemoryRegion> org_list = new ArrayList<MemoryRegion>(list); + list.add(new MemRegion(props)); + srv_memory_map.set(prs_id, list.toArray(new MemoryRegion[list.size()]), new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) { + exit(error); + return; + } + srv_memory_map.get(prs_id, new IMemoryMap.DoneGet() { + public void doneGet(IToken token, Exception error, MemoryRegion[] map) { + if (error != null) { + exit(error); + return; + } + int cnt = 0; + for (MemoryRegion r : map) { + String id = (String)r.getProperties().get(IMemoryMap.PROP_ID); + if (!test_id.equals(id)) continue; + for (String p : props.keySet()) { + if (!props.get(p).equals(r.getProperties().get(p))) { + exit(new Error("Invalid value returned for Memory Map region property " + p)); + return; + } + } + cnt++; + } + if (cnt != 1) { + exit(new Error("Error adding memory map entry with MemoryMap.set command")); + return; + } + srv_memory_map.set(prs_id, org_list.toArray(new MemoryRegion[org_list.size()]), new IMemoryMap.DoneSet() { + public void doneSet(IToken token, Exception error) { + if (error != null) { + exit(error); + return; + } + mem_map_test_running = false; + mem_map_test_done = true; + runTest(); + } + }); + } + }); + } + }); + } + }); + } + + void cancel(final Runnable done) { + if (srv_run_ctrl != null) srv_run_ctrl.removeListener(this); + if (test_ctx_id == null) { + if (pending_cancel != null) { + exit(null); + } + else { + pending_cancel = done; + } + } + else if (cancel_test_cmd == null) { + cancel_test_cmd = srv_diag.cancelTest(test_ctx_id, new IDiagnostics.DoneCancelTest() { + public void doneCancelTest(IToken token, Throwable error) { + cancel_test_cmd = null; + exit(error); + done.run(); + } + }); + } + else { + exit(new Exception("Cannot terminate remote test process")); + done.run(); + } + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + if (pending_cancel != null) { + Protocol.invokeLater(pending_cancel); + pending_cancel = null; + } + else { + if (srv_run_ctrl != null) srv_run_ctrl.removeListener(this); + } + if (srv_breakpoints != null) srv_breakpoints.removeListener(bp_listener); + test_suite.done(this, x); + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestStreams.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestStreams.java new file mode 100644 index 000000000..e066a4b47 --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestStreams.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 2010, 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.tm.internal.tcf.debug.tests; + +import java.util.HashSet; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.IDiagnostics; +import org.eclipse.tm.tcf.services.IStreams; + +class TestStreams implements ITCFTest, IStreams.StreamsListener { + + private final TCFTestSuite test_suite; + private final IDiagnostics diag; + private final IStreams streams; + private final Random rnd = new Random(); + private final HashSet<String> stream_ids = new HashSet<String>(); + + private String inp_id; + private String out_id; + + private int test_count; + private long start_time; + + TestStreams(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + diag = channel.getRemoteService(IDiagnostics.class); + streams = channel.getRemoteService(IStreams.class); + } + + public void start() { + if (diag == null ||streams == null) { + test_suite.done(this, null); + } + else { + start_time = System.currentTimeMillis(); + connect(); + } + } + + private void connect() { + diag.createTestStreams(1001, 771, new IDiagnostics.DoneCreateTestStreams() { + public void doneCreateTestStreams(IToken token, Throwable error, final String inp_id, final String out_id) { + if (error != null) { + exit(error); + } + else { + TestStreams.this.inp_id = inp_id; + TestStreams.this.out_id = out_id; + if (stream_ids.size() != 0) { + exit(new Exception("Stream events without subscription")); + return; + } + streams.connect(inp_id, new IStreams.DoneConnect() { + public void doneConnect(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + // write some data (zeros) + // this data can be dropped by Streams since we are not connected yet + final byte[] data_out = new byte[rnd.nextInt(10000) + 1000]; + IStreams.DoneWrite done_write = new IStreams.DoneWrite() { + public void doneWrite(IToken token, Exception error) { + if (error != null) exit(error); + } + }; + int offs = 0; + while (offs < data_out.length) { + int size = rnd.nextInt(400); + if (size > data_out.length - offs) size = data_out.length - offs; + streams.write(inp_id, data_out, offs, size, done_write); + offs += size; + } + streams.connect(out_id, new IStreams.DoneConnect() { + public void doneConnect(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + testReadWrite(true, new Runnable() { + public void run() { + TestStreams.this.inp_id = null; + TestStreams.this.out_id = null; + subscribe(); + } + }); + } + } + }); + } + } + }); + } + } + }); + } + + private void subscribe() { + streams.subscribe(IDiagnostics.NAME, this, new IStreams.DoneSubscribe() { + public void doneSubscribe(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + createStreams(); + } + } + }); + } + + private void createStreams() { + diag.createTestStreams(1153, 947, new IDiagnostics.DoneCreateTestStreams() { + public void doneCreateTestStreams(IToken token, Throwable error, String inp_id, String out_id) { + if (error != null) { + exit(error); + } + else { + TestStreams.this.inp_id = inp_id; + TestStreams.this.out_id = out_id; + for (String id : stream_ids) { + if (id.equals(inp_id)) continue; + if (id.equals(out_id)) continue; + streams.disconnect(id, new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + } + testReadWrite(false, new Runnable() { + public void run() { + unsubscribe(); + } + }); + } + } + }); + } + + private void testReadWrite(final boolean skip_zeros, final Runnable done) { + final byte[] data_out = new byte[rnd.nextInt(10000) + 1000]; + new Random().nextBytes(data_out); + if (skip_zeros) data_out[0] = 1; + final HashSet<IToken> cmds = new HashSet<IToken>(); + IStreams.DoneRead done_read = new IStreams.DoneRead() { + + private int offs = 0; + private boolean eos; + + public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) { + cmds.remove(token); + if (error != null) { + if (!this.eos) exit(error); + } + else if (lost_size != 0) { + exit(new Exception("Streams service: unexpected data loss")); + } + else if (this.eos) { + if (!eos || data != null && data.length > 0) { + exit(new Exception("Streams service: unexpected successful read after EOS")); + } + } + else { + if (data != null) { + if (offs + data.length > data_out.length) { + exit(new Exception("Streams service: read returns more data then expected")); + return; + } + for (int n = 0; n < data.length; n++) { + if (!skip_zeros || offs > 0 || data[n] != 0) { + if (data[n] != data_out[offs]) { + exit(new Exception("Streams service: data error: " + data[n] + " != " + data_out[offs])); + return; + } + offs++; + } + } + } + if (eos) { + if (offs != data_out.length) { + exit(new Exception("Streams service: unexpected EOS")); + } + this.eos = true; + } + else if (cmds.size() < 8) { + cmds.add(streams.read(out_id, 241, this)); + } + } + if (cmds.isEmpty()) disposeStreams(done); + } + }; + cmds.add(streams.read(out_id, 223, done_read)); + cmds.add(streams.read(out_id, 227, done_read)); + cmds.add(streams.read(out_id, 229, done_read)); + cmds.add(streams.read(out_id, 233, done_read)); + + IStreams.DoneWrite done_write = new IStreams.DoneWrite() { + public void doneWrite(IToken token, Exception error) { + if (error != null) exit(error); + } + }; + int offs = 0; + while (offs < data_out.length) { + int size = rnd.nextInt(400); + if (size > data_out.length - offs) size = data_out.length - offs; + streams.write(inp_id, data_out, offs, size, done_write); + offs += size; + } + streams.eos(inp_id, new IStreams.DoneEOS() { + public void doneEOS(IToken token, Exception error) { + if (error != null) exit(error); + } + }); + } + + private void disposeStreams(final Runnable done) { + final HashSet<IToken> cmds = new HashSet<IToken>(); + IStreams.DoneDisconnect done_disconnect = new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + cmds.remove(token); + if (cmds.isEmpty() && test_suite.isActive(TestStreams.this)) done.run(); + } + } + }; + IDiagnostics.DoneDisposeTestStream done_dispose = new IDiagnostics.DoneDisposeTestStream() { + public void doneDisposeTestStream(IToken token, Throwable error) { + if (error != null) { + exit(error); + } + else { + cmds.remove(token); + if (cmds.isEmpty() && test_suite.isActive(TestStreams.this)) done.run(); + } + } + }; + cmds.add(streams.disconnect(inp_id, done_disconnect)); + cmds.add(diag.disposeTestStream(inp_id, done_dispose)); + cmds.add(diag.disposeTestStream(out_id, done_dispose)); + cmds.add(streams.disconnect(out_id, done_disconnect)); + } + + private void unsubscribe() { + streams.unsubscribe(IDiagnostics.NAME, this, new IStreams.DoneUnsubscribe() { + public void doneUnsubscribe(IToken token, Exception error) { + if (error != null || test_count >= 10 || System.currentTimeMillis() - start_time >= 4000) { + exit(error); + } + else { + test_count++; + stream_ids.clear(); + inp_id = null; + out_id = null; + connect(); + } + } + }); + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + test_suite.done(this, x); + } + + /************************** StreamsListener **************************/ + + public void created(String stream_type, String stream_id, String context_id) { + if (!IDiagnostics.NAME.equals(stream_type)) exit(new Exception("Invalid stream type in Streams.created event")); + if (stream_ids.contains(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event")); + stream_ids.add(stream_id); + if (inp_id != null) { + if (inp_id.equals(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event")); + if (out_id.equals(stream_id)) exit(new Exception("Invalid stream ID in Streams.created event")); + streams.disconnect(stream_id, new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + if (error != null) { + exit(error); + } + } + }); + } + } + + public void disposed(String stream_type, String stream_id) { + if (!IDiagnostics.NAME.equals(stream_type)) exit(new Exception("Invalid stream type in Streams.disposed event")); + if (!stream_ids.remove(stream_id)) exit(new Exception("Invalid stream ID in Streams.disposed event")); + } + + public boolean canResume(String id) { + return true; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestSysMonitor.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestSysMonitor.java new file mode 100644 index 000000000..74059492c --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestSysMonitor.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2010, 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.tm.internal.tcf.debug.tests; + +import java.util.HashMap; +import java.util.HashSet; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IErrorReport; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.services.ISysMonitor; +import org.eclipse.tm.tcf.services.ISysMonitor.SysMonitorContext; + +class TestSysMonitor implements ITCFTest { + + private final TCFTestSuite test_suite; + private final ISysMonitor sys_mon; + private final HashMap<String,ISysMonitor.SysMonitorContext> procs = + new HashMap<String,ISysMonitor.SysMonitorContext>(); + + TestSysMonitor(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + sys_mon = channel.getRemoteService(ISysMonitor.class); + } + + public void start() { + if (sys_mon == null) { + test_suite.done(this, null); + } + else { + sys_mon.getChildren(null, new ISysMonitor.DoneGetChildren() { + public void doneGetChildren(IToken token, Exception error, String[] context_ids) { + if (error != null) { + exit(error); + } + else if (context_ids == null || context_ids.length == 0) { + exit(new Exception("ISysMonitor.getChildren(null) returned empty list")); + } + else { + final HashSet<IToken> cmds = new HashSet<IToken>(); + for (final String id : context_ids) { + cmds.add(sys_mon.getContext(id, new ISysMonitor.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, SysMonitorContext context) { + cmds.remove(token); + if (error != null) { + // Some errors are expected, like "Access Denied" + if (!(error instanceof IErrorReport)) { + exit(error); + return; + } + } + else { + procs.put(id, context); + } + if (cmds.isEmpty()) getEnvironment(); + } + })); + } + } + } + }); + } + } + + private void getEnvironment() { + final HashSet<IToken> cmds = new HashSet<IToken>(); + for (final String id : procs.keySet()) { + cmds.add(sys_mon.getEnvironment(id, new ISysMonitor.DoneGetEnvironment() { + public void doneGetEnvironment(IToken token, Exception error, String[] environment) { + cmds.remove(token); + if (error != null) { + // Some errors are expected, like "Access Denied" + if (!(error instanceof IErrorReport)) { + exit(error); + return; + } + } + if (cmds.isEmpty()) getCommandLine(); + } + })); + } + } + + private void getCommandLine() { + final HashSet<IToken> cmds = new HashSet<IToken>(); + for (final String id : procs.keySet()) { + cmds.add(sys_mon.getCommandLine(id, new ISysMonitor.DoneGetCommandLine() { + public void doneGetCommandLine(IToken token, Exception error, String[] cmd_line) { + cmds.remove(token); + if (error != null) { + // Some errors are expected, like "Access Denied" + if (!(error instanceof IErrorReport)) { + exit(error); + return; + } + } + if (cmds.isEmpty()) exit(null); + } + })); + } + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + test_suite.done(this, x); + } + + public boolean canResume(String id) { + return true; + } +} diff --git a/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestTerminals.java b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestTerminals.java new file mode 100644 index 000000000..42b1ee6ad --- /dev/null +++ b/plugins/org.eclipse.tcf.debug/src/org/eclipse/tcf/internal/debug/tests/TestTerminals.java @@ -0,0 +1,507 @@ +/******************************************************************************* + * Copyright (c) 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.tm.internal.tcf.debug.tests; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.eclipse.tm.tcf.protocol.IChannel; +import org.eclipse.tm.tcf.protocol.IToken; +import org.eclipse.tm.tcf.protocol.Protocol; +import org.eclipse.tm.tcf.services.IProcesses; +import org.eclipse.tm.tcf.services.IStreams; +import org.eclipse.tm.tcf.services.ITerminals; +import org.eclipse.tm.tcf.services.ITerminals.TerminalContext; + +class TestTerminals implements ITCFTest { + + private final TCFTestSuite test_suite; + private final ITerminals terminals; + private final IProcesses processes; + private final IStreams streams; + private final Random rnd = new Random(); + private final HashSet<String> stream_ids = new HashSet<String>(); + private final StringBuffer stdout_buf = new StringBuffer(); + private final StringBuffer stderr_buf = new StringBuffer(); + private final HashSet<IToken> disconnect_cmds = new HashSet<IToken>(); + private final List<String> echo_tx = new ArrayList<String>(); + private final List<Integer> echo_rx = new ArrayList<Integer>(); + + private final int echo_cnt = 50; + + private IStreams.StreamsListener streams_listener; + private IStreams.DoneRead stdout_read; + private IStreams.DoneRead stderr_read; + private String encoding; + private TerminalContext terminal; + private TerminalContext get_ctx; + private Map<String,String> environment; + private boolean delay_done; + private Collection<Map<String,Object>> signal_list; + private IToken get_signals_cmd; + private IToken signal_cmd; + private boolean signal_sent; + private IToken unsubscribe_cmd; + private boolean unsubscribe_done; + private boolean exited; + private boolean stdout_eos; + private int time_out = 0; + + private final ITerminals.TerminalsListener listener = new ITerminals.TerminalsListener() { + + public void exited(String id, int exit_code) { + if (terminal != null && id.equals(terminal.getID())) { + exited = true; + if (!signal_sent) { + exit(new Exception("Terminal exited with code " + exit_code)); + } + else { + run(); + } + } + } + + public void winSizeChanged(String id, int w, int h) { + } + }; + + private final IStreams.DoneDisconnect disconnect_done = new IStreams.DoneDisconnect() { + public void doneDisconnect(IToken token, Exception error) { + assert disconnect_cmds.contains(token); + disconnect_cmds.remove(token); + if (error != null) exit(error); + if (disconnect_cmds.size() == 0 && unsubscribe_done) exit(null); + } + }; + + TestTerminals(TCFTestSuite test_suite, IChannel channel) { + this.test_suite = test_suite; + terminals = channel.getRemoteService(ITerminals.class); + processes = channel.getRemoteService(IProcesses.class); + streams = channel.getRemoteService(IStreams.class); + } + + public void start() { + if (terminals == null || streams == null) { + test_suite.done(this, null); + } + else { + terminals.addListener(listener); + run(); + } + } + + private void run() { + if (environment == null && processes != null) { + processes.getEnvironment(new IProcesses.DoneGetEnvironment() { + public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) { + if (error != null) { + exit(error); + } + else if (environment == null) { + exit(new Exception("Default process environment must not be null")); + } + else { + TestTerminals.this.environment = environment; + run(); + } + } + }); + return; + } + if (streams_listener == null) { + final IStreams.StreamsListener l = new IStreams.StreamsListener() { + public void created(String stream_type, String stream_id, String context_id) { + if (!terminals.getName().equals(stream_type)) { + exit(new Exception("Invalid stream type in Streams.created event: " + stream_type)); + } + else if (stream_id == null || stream_id.length() == 0 || stream_ids.contains(stream_id)) { + exit(new Exception("Invalid stream ID in Streams.created event: " + stream_id)); + } + else if (terminal != null) { + if (stream_id.equals(terminal.getStdInID()) || + stream_id.equals(terminal.getStdOutID()) || + stream_id.equals(terminal.getStdErrID())) { + exit(new Exception("Invalid stream ID in Streams.created event: " + stream_id)); + } + else { + disconnect_cmds.add(streams.disconnect(stream_id, disconnect_done)); + } + } + else { + stream_ids.add(stream_id); + } + } + public void disposed(String stream_type, String stream_id) { + } + }; + streams.subscribe(terminals.getName(), l, new IStreams.DoneSubscribe() { + public void doneSubscribe(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + streams_listener = l; + run(); + } + } + }); + return; + } + if (terminal == null) { + String[] types = { "ansi", "vt100", null }; + String[] langs = { "en_US", "en_US.UTF-8", null }; + String[] env = null; + if (environment != null && rnd.nextBoolean()) { + int i = 0; + env = new String[environment.size() + 1]; + for (String s : environment.keySet()) { + env[i++] = s + "=" + environment.get(s); + } + env[i++] = "TCF_FOO=BAR"; + } + terminals.launch(types[rnd.nextInt(types.length)], langs[rnd.nextInt(langs.length)], env, new ITerminals.DoneLaunch() { + public void doneLaunch(IToken token, Exception error, final TerminalContext terminal) { + if (error != null) { + exit(error); + } + else if (terminal == null) { + exit(new Exception("Terminal context must not be null")); + } + else if (terminal.getID() == null) { + exit(new Exception("Terminal context ID must not be null")); + } + else { + TestTerminals.this.terminal = terminal; + for (Iterator<String> i = stream_ids.iterator(); i.hasNext();) { + String stream_id = i.next(); + if (stream_id.equals(terminal.getStdInID()) || + stream_id.equals(terminal.getStdOutID()) || + stream_id.equals(terminal.getStdErrID())) { + // keep connected + } + else { + i.remove(); + disconnect_cmds.add(streams.disconnect(stream_id, disconnect_done)); + } + } + Protocol.invokeLater(100, new Runnable() { + public void run() { + if (!test_suite.isActive(TestTerminals.this)) return; + time_out++; + if (test_suite.cancel) { + exit(null); + } + else if (time_out < 200) { + Protocol.invokeLater(100, this); + } + else if (!signal_sent) { + exit(new Error("Timeout waiting for terminal reply. Context: " + terminal.getID())); + } + else if (!exited) { + exit(new Error("Timeout waiting for 'Terminals.exited' event. Context: " + terminal.getID())); + } + else { + exit(new Error("Timeout waiting for end-of-stream. Context: " + terminal.getID())); + } + } + }); + run(); + } + } + }); + return; + } + if (get_ctx == null) { + terminals.getContext(terminal.getID(), new ITerminals.DoneGetContext() { + public void doneGetContext(IToken token, Exception error, TerminalContext terminal) { + if (error != null) { + exit(error); + } + else if (terminal == null) { + exit(new Exception("Terminal context must not be null")); + } + else if (terminal.getID() == null) { + exit(new Exception("Terminal context ID must not be null")); + } + else if (!TestTerminals.this.terminal.getProperties().equals(terminal.getProperties())) { + exit(new Exception("Invalid result of Terminal.getContext")); + } + else { + TestTerminals.this.get_ctx = terminal; + run(); + } + } + }); + return; + } + if (signal_list == null && processes != null && terminal.getProcessID() != null) { + assert get_signals_cmd == null; + get_signals_cmd = processes.getSignalList(terminal.getProcessID(), new IProcesses.DoneGetSignalList() { + public void doneGetSignalList(IToken token, Exception error, Collection<Map<String,Object>> list) { + assert get_signals_cmd == token; + get_signals_cmd = null; + if (error != null) { + exit(error); + } + else if (list == null) { + exit(new Exception("Signal list must not be null")); + } + else { + signal_list = list; + run(); + } + } + }); + return; + } + if (encoding == null) { + String lang = terminal.getEncoding(); + if (lang == null && environment != null) lang = environment.get("LC_ALL"); + if (lang == null && environment != null) lang = environment.get("LANG"); + if (lang == null) lang = "en_US.UTF-8"; + int i = lang.indexOf('.'); + int j = lang.indexOf('@'); + if (i < 0) { + encoding = "UTF-8"; + } + else if (j < i) { + encoding = lang.substring(i + 1); + } + else { + encoding = lang.substring(i + 1, j); + } + } + if (stdout_read == null) { + final String id = terminal.getStdOutID(); + if (id == null) { + exit(new Exception("stdout stream ID is null")); + return; + } + stdout_read = new IStreams.DoneRead() { + public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) { + if (error != null) { + exit(error); + } + else if (lost_size > 0) { + exit(new Exception("Lost bytes in terminal stream")); + } + else { + try { + boolean run = false; + if (data != null) { + stdout_buf.append(new String(data, encoding)); + if (echo_tx.size() > echo_rx.size()) { + String s = echo_tx.get(echo_rx.size()); + String p = "\n" + s.substring(0, 12); + int n = 0; + if (echo_rx.size() > 0) n = echo_rx.get(echo_rx.size() - 1); + int i = stdout_buf.indexOf(p, n); + if (i >= 0 && stdout_buf.length() >= i + s.length() + 4) { + time_out = 0; + echo_rx.add(i + 1); + run = true; + } + } + } + if (!eos) { + streams.read(id, 0x1000, this); + } + else { + stdout_eos = true; + run = true; + } + if (run) run(); + } + catch (Exception x) { + exit(x); + } + } + } + }; + streams.read(id, 0x1000, stdout_read); + } + if (stderr_read == null && terminal.getStdErrID() != null) { + final String id = terminal.getStdErrID(); + stderr_read = new IStreams.DoneRead() { + public void doneRead(IToken token, Exception error, int lost_size, byte[] data, boolean eos) { + if (error != null) { + exit(error); + } + else if (lost_size > 0) { + exit(new Exception("Lost bytes in terminal stream")); + } + else { + try { + if (data != null) stderr_buf.append(new String(data, encoding)); + if (!eos) streams.read(id, 0x1000, this); + } + catch (Exception x) { + exit(x); + } + } + } + }; + int n = rnd.nextInt(4) + 1; + for (int i = 0; i < n; i++) { + streams.read(id, 0x1000, stderr_read); + } + } + if (!delay_done) { + Protocol.invokeLater(rnd.nextInt(250), new Runnable() { + public void run() { + delay_done = true; + TestTerminals.this.run(); + } + }); + return; + } + if (echo_tx.size() < echo_cnt && echo_rx.size() == echo_tx.size()) { + try { + StringBuffer bf = new StringBuffer(); + for (int i = 0; i < 0x100; i++) { + bf.append((char)('A' + rnd.nextInt('Z' - 'A' + 1))); + bf.append((char)('a' + rnd.nextInt('Z' - 'A' + 1))); + } + String s = bf.toString(); + echo_tx.add(s); + s = "echo " + s + '\n'; + byte[] buf = s.getBytes(encoding); + streams.write(terminal.getStdInID(), buf, 0, buf.length, new IStreams.DoneWrite() { + public void doneWrite(IToken token, Exception error) { + if (error != null) { + exit(error); + } + else { + run(); + } + } + }); + } + catch (Exception x) { + exit(x); + } + return; + } + if (!exited && !stdout_eos && echo_rx.size() < echo_cnt) return; + if (!signal_sent) { + assert !exited; + if (signal_cmd == null) { + int code = 0; + if (signal_list != null && rnd.nextBoolean()) { + for (Map<String,Object> m : signal_list) { + String nm = (String)m.get(IProcesses.SIG_NAME); + if (nm != null && nm.equals("SIGKILL")) { + Number n = (Number)m.get(IProcesses.SIG_CODE); + if (n != null) code = n.intValue(); + } + } + if (code == 0) { + for (Map<String,Object> m : signal_list) { + String nm = (String)m.get(IProcesses.SIG_NAME); + if (nm != null && nm.equals("SIGTERM")) { + Number n = (Number)m.get(IProcesses.SIG_CODE); + if (n != null) code = n.intValue(); + } + } + } + } + if (code > 0) { + signal_cmd = processes.signal(terminal.getProcessID(), code, new IProcesses.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + assert signal_cmd == token; + signal_cmd = null; + if (error != null) { + exit(error); + } + else { + signal_sent = true; + run(); + } + } + }); + } + else { + signal_cmd = terminals.exit(terminal.getID(), new ITerminals.DoneCommand() { + public void doneCommand(IToken token, Exception error) { + assert signal_cmd == token; + signal_cmd = null; + if (error != null) { + exit(error); + } + else { + signal_sent = true; + run(); + } + } + }); + } + } + return; + } + if (exited && stdout_eos) { + if (!unsubscribe_done) { + if (unsubscribe_cmd == null) { + unsubscribe_cmd = streams.unsubscribe(terminals.getName(), streams_listener, new IStreams.DoneUnsubscribe() { + public void doneUnsubscribe(IToken token, Exception error) { + unsubscribe_done = true; + if (error != null) exit(error); + else run(); + } + }); + } + return; + } + else { + for (String stream_id : stream_ids) { + disconnect_cmds.add(streams.disconnect(stream_id, disconnect_done)); + } + stream_ids.clear(); + checkTerminalOutput(stdout_buf); + checkTerminalOutput(stderr_buf); + if (echo_rx.size() < echo_cnt) { + exit(new Exception("Terminal exited before test finished")); + } + else { + int n = 0; + for (int i : echo_rx) { + String s = echo_tx.get(n++); + String r = stdout_buf.substring(i, i + s.length()); + if (!s.equals(r)) { + exit(new Exception("Invalid reply: " + r + "\nExpected: " + s)); + } + } + } + } + } + } + + private void checkTerminalOutput(StringBuffer bf) { + if (bf.indexOf("Cannot start") >= 0) { + exit(new Exception("Unexpected terminal output:\n" + bf)); + } + } + + private void exit(Throwable x) { + if (!test_suite.isActive(this)) return; + if (terminals != null) terminals.removeListener(listener); + test_suite.done(this, x); + } + + public boolean canResume(String id) { + return true; + } +} |