/*******************************************************************************
* 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) {
}
}