diff options
author | Ankit Jain <radical@gmail.com> | 2020-02-23 00:16:03 +0300 |
---|---|---|
committer | Ankit Jain <radical@gmail.com> | 2020-02-23 01:49:56 +0300 |
commit | 547e4180df3cc93d1e97a7417687591f64c96849 (patch) | |
tree | f115b2d646f5b978c2980c6dc0e436ab31c881f3 /sdks | |
parent | d02bbe4c32cade26c9cd4e5bf53fb48792ae0315 (diff) |
[wasm][debugger] Fix issue in getting locals for previous frames
`mono_wasm_get_var_info` didn't correctly reset the `data.cur_frame`
required for `mono_walk_stack_with_ctx`, as the stack is walked
repeatedly to get the variables.
Diffstat (limited to 'sdks')
-rw-r--r-- | sdks/wasm/DebuggerTestSuite/Tests.cs | 365 | ||||
-rw-r--r-- | sdks/wasm/debugger-driver.html | 13 | ||||
-rw-r--r-- | sdks/wasm/debugger-test.cs | 44 |
3 files changed, 411 insertions, 11 deletions
diff --git a/sdks/wasm/DebuggerTestSuite/Tests.cs b/sdks/wasm/DebuggerTestSuite/Tests.cs index 32e591fcc40..e5ee59c8ed8 100644 --- a/sdks/wasm/DebuggerTestSuite/Tests.cs +++ b/sdks/wasm/DebuggerTestSuite/Tests.cs @@ -41,9 +41,12 @@ namespace DebuggerTests void CheckLocation (string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location) { - Assert.Equal (script_loc, scripts[location["scriptId"].Value<string>()]); - Assert.Equal (line, location ["lineNumber"].Value<int> ()); - Assert.Equal (column, location ["columnNumber"].Value<int> ()); + var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }" + + $"#{ location ["lineNumber"].Value<int> () }" + + $"#{ location ["columnNumber"].Value<int> () }"; + + var expected_loc_str = $"{script_loc}#{line}#{column}"; + Assert.Equal (expected_loc_str, loc_str); } [Fact] @@ -226,9 +229,23 @@ namespace DebuggerTests Assert.True(false, $"Could not find variable '{name}'"); } + void CheckString (JToken locals, string name, string value) { + foreach (var l in locals) { + if (name != l["name"]?.Value<string> ()) + continue; + var val = l["value"]; + if (value == null) { + Assert.Equal ("object", val ["type"]?.Value<string> ()); + Assert.Equal ("null", val["subtype"]?.Value<string> ()); + } else { + Assert.Equal ("string", val ["type"]?.Value<string> ()); + Assert.Equal (value, val["value"]?.Value <string> ()); + } + return; + } + Assert.True(false, $"Could not find variable '{name}'"); + } void CheckObject (JToken locals, string name, string class_name, string subtype=null) { - Console.WriteLine ($"** Locals: {locals.ToString ()}"); - Console.WriteLine ($"\tChecking {name}"); foreach (var l in locals) { if (name != l["name"]?.Value<string> ()) continue; @@ -243,8 +260,6 @@ namespace DebuggerTests } void CheckArray (JToken locals, string name, string class_name) { - Console.WriteLine ($"** Locals: {locals.ToString ()}"); - Console.WriteLine ($"\tChecking {name}"); foreach (var l in locals) { if (name != l["name"]?.Value<string> ()) continue; @@ -555,6 +570,342 @@ namespace DebuggerTests }); } + [Fact] + public async Task InspectLocalsInPreviousFramesDuringSteppingIn2 () { + var insp = new Inspector (); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready (async (cli, token) => { + var dep_cs_loc = "dotnet://Simple.Dependency.dll/dependency.cs"; + + var ctx = new DebugTestContext (cli, insp, token, scripts); + var bp1_req = JObject.FromObject(new { + lineNumber = 24, + columnNumber = 2, + url = dicFileToUrl[dep_cs_loc] + }); + + var bp1_res = await cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, token); + Assert.True (bp1_res.IsOk); + + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + + // Will stop in Complex.DoEvenMoreStuff + var wait_res = await EvaluateAndCheck ( + "window.setTimeout(function() { invoke_use_complex (); }, 1);", + dep_cs_loc, 24, 2, "DoEvenMoreStuff", ctx, + locals_fn: (locals) => { + Assert.Equal (1, locals.Count()); + CheckObject (locals, "this", "Simple.Complex"); + } + ); + + // Check UseComplex frame + await CheckLocalsOnFrame (wait_res ["callFrames"][3], debugger_test_loc, 17, 2, "UseComplex", ctx, + test_fn: (locals_m1) => { + Assert.Equal (7, locals_m1.Count()); + + CheckNumber (locals_m1, "a", 10); + CheckNumber (locals_m1, "b", 20); + CheckObject (locals_m1, "complex", "Simple.Complex"); + CheckNumber (locals_m1, "c", 30); + CheckNumber (locals_m1, "d", 50); + CheckNumber (locals_m1, "e", 60); + CheckNumber (locals_m1, "f", 0); + } + ); + + wait_res = await StepAndCheck (StepKind.Over, dep_cs_loc, 16, 2, "DoStuff", ctx, times: 2); + // Check UseComplex frame again + await CheckLocalsOnFrame (wait_res ["callFrames"][1], debugger_test_loc, 17, 2, "UseComplex", ctx, + test_fn: (locals_m1) => { + Assert.Equal (7, locals_m1.Count()); + + CheckNumber (locals_m1, "a", 10); + CheckNumber (locals_m1, "b", 20); + CheckObject (locals_m1, "complex", "Simple.Complex"); + CheckNumber (locals_m1, "c", 30); + CheckNumber (locals_m1, "d", 50); + CheckNumber (locals_m1, "e", 60); + CheckNumber (locals_m1, "f", 0); + } + ); + }); + } + + [Fact] + public async Task InspectLocalsInPreviousFramesDuringSteppingIn () { + var insp = new Inspector (); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready (async (cli, token) => { + var ctx = new DebugTestContext (cli, insp, token, scripts); + var bp1_req = JObject.FromObject(new { + lineNumber = 100, + columnNumber = 3, + url = dicFileToUrl["dotnet://debugger-test.dll/debugger-test.cs"], + }); + + var bp1_res = await cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, token); + Assert.True (bp1_res.IsOk); + + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + + // Will stop in InnerMethod + var wait_res = await EvaluateAndCheck ( + "window.setTimeout(function() { invoke_outer_method(); }, 1);", + debugger_test_loc, 100, 3, "InnerMethod", ctx, + locals_fn: (locals) => { + Assert.Equal (4, locals.Count()); + CheckNumber (locals, "i", 5); + CheckNumber (locals, "j", 24); + CheckString (locals, "foo_str", "foo"); + //CheckObject (locals, "this", "Math.NestedInMath"); + } + ); + + // Check OuterMethod frame + await CheckLocalsOnFrame (wait_res ["callFrames"][1], debugger_test_loc, 76, 2, "OuterMethod", ctx, + test_fn: (locals_m1) => { + Assert.Equal (5, locals_m1.Count()); + // FIXME: Failing test CheckNumber (locals_m1, "i", 5); + // FIXME: Failing test CheckString (locals_m1, "text", "Hello"); + CheckNumber (locals_m1, "new_i", 0); + CheckNumber (locals_m1, "k", 0); + //CheckObject (locals_m1, "nim", "Math.NestedInMath"); + } + ); + + // step back into OuterMethod + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 80, 2, "OuterMethod", ctx, times: 7, + locals_fn: (locals) => { + Assert.Equal (5, locals.Count()); + + // FIXME: Failing test CheckNumber (locals_m1, "i", 5); + CheckString (locals, "text", "Hello"); + // FIXME: Failing test CheckNumber (locals, "new_i", 24); + CheckNumber (locals, "k", 19); + //CheckObject (locals, "nim", "Math.NestedInMath"); + } + ); + + //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", ctx, times: 2); + + // step into InnerMethod2 + await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 85, 1, "InnerMethod2", ctx, + locals_fn: (locals) => { + // umm use JOBject instead of JToken? + Assert.Equal (3, locals.Count()); + + CheckString (locals, "s", "test string"); + //out var: CheckNumber (locals, "k", 0); + CheckNumber (locals, "i", 24); + } + ); + + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 89, 1, "InnerMethod2", ctx, times: 4, + locals_fn: (locals) => { + // umm use JOBject instead of JToken? + Assert.Equal (3, locals.Count()); + + CheckString (locals, "s", "test string"); + // FIXME: Failing test CheckNumber (locals, "k", 34); + CheckNumber (locals, "i", 24); + } + ); + + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", ctx, times: 2, + locals_fn: (locals) => { + Assert.Equal (5, locals.Count()); + + CheckString (locals, "text", "Hello"); + // FIXME: failing test CheckNumber (locals, "i", 5); + CheckNumber (locals, "new_i", 22); + CheckNumber (locals, "k", 34); + //CheckObject (locals, "nim", "Math.NestedInMath"); + } + ); + }); + } + + [Fact] + public async Task InspectLocalsDuringSteppingIn () { + var insp = new Inspector (); + //Collect events + var scripts = SubscribeToScripts(insp); + + await Ready(); + await insp.Ready (async (cli, token) => { + var ctx = new DebugTestContext (cli, insp, token, scripts); + var bp1_req = JObject.FromObject(new { + lineNumber = 75, + columnNumber = 2, + url = dicFileToUrl["dotnet://debugger-test.dll/debugger-test.cs"], + }); + + var bp1_res = await cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, token); + Assert.True (bp1_res.IsOk); + + await EvaluateAndCheck ("window.setTimeout(function() { invoke_outer_method(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 75, 2, "OuterMethod", ctx, + locals_fn: (locals) => { + Assert.Equal (5, locals.Count()); + + //CheckObject (locals, "nim", "Math.NestedInMath"); + CheckNumber (locals, "i", 0); + CheckNumber (locals, "k", 0); + CheckNumber (locals, "new_i", 0); + CheckString (locals, "text", null); + } + ); + + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 76, 2, "OuterMethod", ctx, + locals_fn: (locals) => { + Assert.Equal (5, locals.Count()); + + //CheckObject (locals, "nim", "Math.NestedInMath"); + // FIXME: Failing test CheckNumber (locals, "i", 5); + CheckNumber (locals, "k", 0); + CheckNumber (locals, "new_i", 0); + CheckString (locals, "text", "Hello"); + } + ); + + // Step into InnerMethod + await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 94, 2, "InnerMethod", ctx); + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 98, 3, "InnerMethod", ctx, times: 5, + locals_fn: (locals) => { + Assert.Equal (4, locals.Count()); + + CheckNumber (locals, "i", 5); + CheckNumber (locals, "j", 15); + CheckString (locals, "foo_str", "foo"); + //CheckObject (locals, "this", "Math.NestedInMath"); + } + ); + + // Step back to OuterMethod + await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 77, 2, "OuterMethod", ctx, times: 5, + locals_fn: (locals) => { + Assert.Equal (5, locals.Count()); + + //CheckObject (locals, "nim", "Math.NestedInMath"); + // FIXME: Failing test CheckNumber (locals, "i", 5); + CheckNumber (locals, "k", 0); + CheckNumber (locals, "new_i", 24); + CheckString (locals, "text", "Hello"); + } + ); + }); + } + + async Task<JObject> StepAndCheck (StepKind kind, string script_loc, int line, int column, string function_name, DebugTestContext ctx, + Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times=1) + { + for (int i = 0; i < times - 1; i ++) { + await SendCommandAndCheck (null, $"Debugger.step{kind.ToString ()}", ctx); + } + + // Check for method/line etc only at the last step + return await SendCommandAndCheck ( + null, $"Debugger.step{kind.ToString ()}", ctx, + wait_for_event_fn: async (pause_location) => { + await CheckLocalsOnFrame (pause_location ["callFrames"][0], script_loc, line, column, function_name, ctx); + if (wait_for_event_fn != null) + await wait_for_event_fn (pause_location); + }, + locals_fn: locals_fn); + } + + async Task<JObject> EvaluateAndCheck (string expression, string script_loc, int line, int column, string function_name, DebugTestContext ctx, + Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null) + => await SendCommandAndCheck ( + JObject.FromObject (new { expression = expression }), + "Runtime.evaluate", ctx, + wait_for_event_fn: async (pause_location) => { + await CheckLocalsOnFrame (pause_location ["callFrames"][0], script_loc, line, column, function_name, ctx); + if (wait_for_event_fn != null) + await wait_for_event_fn (pause_location); + }, + locals_fn: locals_fn); + + async Task<JObject> SendCommandAndCheck (JObject args, string method, DebugTestContext ctx, Func<JObject, Task> wait_for_event_fn = null, + Action<JToken> locals_fn = null, string waitForEvent = Inspector.PAUSE) + { + var res = await ctx.cli.SendCommand (method, args, ctx.token); + if (!res.IsOk) { + Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}"); + Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}"); + } + + var wait_res = await ctx.insp.WaitFor(waitForEvent); + + if (wait_for_event_fn != null) + await wait_for_event_fn (wait_res); + + if (locals_fn != null) + await CheckLocalsOnFrame (wait_res ["callFrames"][0], ctx, locals_fn); + + return wait_res; + } + + async Task CheckLocalsOnFrame (JToken frame, string script_loc, int line, int column, string function_name, DebugTestContext ctx, Action<JToken> test_fn = null) + { + CheckLocation (script_loc, line, column, ctx.scripts, frame ["location"]); + Assert.Equal (function_name, frame ["functionName"].Value<string> ()); + + await CheckLocalsOnFrame (frame, ctx, test_fn); + } + + async Task CheckLocalsOnFrame (JToken frame, DebugTestContext ctx, Action<JToken> test_fn = null) + { + var get_prop_req = JObject.FromObject (new { + objectId = frame["callFrameId"] + }); + + var frame_props = await ctx.cli.SendCommand ("Runtime.getProperties", get_prop_req, ctx.token); + if (!frame_props.IsOk) + Assert.True (false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}"); + + if (test_fn == null) + return; + + var locals = frame_props.Value ["result"]; + try { + test_fn (locals); + } catch { + Console.WriteLine ($"Failed trying to check locals: {locals.ToString ()}"); + throw; + } + } + //TODO add tests covering basic stepping behavior as step in/out/over } + + class DebugTestContext + { + public InspectorClient cli; + public Inspector insp; + public CancellationToken token; + public Dictionary<string, string> scripts; + + public DebugTestContext (InspectorClient cli, Inspector insp, CancellationToken token, Dictionary<string, string> scripts) + { + this.cli = cli; + this.insp = insp; + this.token = token; + this.scripts = scripts; + } + } + + enum StepKind + { + Into, + Over, + Out + } } diff --git a/sdks/wasm/debugger-driver.html b/sdks/wasm/debugger-driver.html index 91aa3a24a9b..42934a154d5 100644 --- a/sdks/wasm/debugger-driver.html +++ b/sdks/wasm/debugger-driver.html @@ -7,14 +7,19 @@ var App = { init: function () { this.int_add = Module.mono_bind_static_method ("[debugger-test] Math:IntAdd"); + this.use_complex = Module.mono_bind_static_method ("[debugger-test] Math:UseComplex"); this.delegates_test = Module.mono_bind_static_method ("[debugger-test] Math:DelegatesTest"); this.generic_types_test = Module.mono_bind_static_method ("[debugger-test] Math:GenericTypesTest"); + this.outer_method = Module.mono_bind_static_method ("[debugger-test] Math:OuterMethod"); console.log ("ready"); }, }; function invoke_add () { return App.int_add (10, 20); } + function invoke_use_complex () { + return App.use_complex (10, 20); + } function invoke_delegates_test () { return App.delegates_test (); } @@ -22,7 +27,13 @@ return App.generic_types_test (); } function invoke_bad_js_test () { - return App.non_existant (); + console.log ("js: In invoke_bad_js_test"); + App.non_existant (); + console.log ("js: After.. shouldn't reach here"); + } + function invoke_outer_method () { + console.log('invoke_outer_method called'); + return App.outer_method (); } </script> <script type="text/javascript" src="mono-config.js"></script> diff --git a/sdks/wasm/debugger-test.cs b/sdks/wasm/debugger-test.cs index c5c150ef783..18fba8e5b97 100644 --- a/sdks/wasm/debugger-test.cs +++ b/sdks/wasm/debugger-test.cs @@ -9,10 +9,14 @@ public class Math { //Only append content to this class as the test suite depend return e; } - public static int UseComplex () { + public static int UseComplex (int a, int b) { var complex = new Simple.Complex (10, "xx"); - var res = complex.DoStuff (); - return res; + int c = a + b; + int d = c + b; + int e = d + a; + int f = 0; + e += complex.DoStuff (); + return e; } delegate bool IsMathNull (Math m); @@ -63,4 +67,38 @@ public class Math { //Only append content to this class as the test suite depend } static bool IsMathNullDelegateTarget (Math m) => m == null; + + public static void OuterMethod () + { + Console.WriteLine ($"OuterMethod called"); + var nim = new Math.NestedInMath (); + var i = 5; + var text = "Hello"; + var new_i = nim.InnerMethod (i); + Console.WriteLine ($"i: {i}"); + Console.WriteLine ($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}"); + int k = 19; + new_i = InnerMethod2 ("test string", new_i, out k); + Console.WriteLine ($"-- InnerMethod2 returned: {new_i}, and k: {k}"); + } + + static int InnerMethod2 (string s, int i, out int k) + { + k = i + 10; + Console.WriteLine ($"s: {s}, i: {i}, k: {k}"); + return i - 2; + } + + class NestedInMath + { + public int InnerMethod (int i) + { + int j = i + 10; + string foo_str = "foo"; + Console.WriteLine ($"i: {i} and j: {j}, foo_str: {foo_str}"); + j += 9; + Console.WriteLine ($"i: {i} and j: {j}"); + return j; + } + } } |