diff options
author | Ankit Jain <radical@gmail.com> | 2022-02-24 04:08:23 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-24 04:08:23 +0300 |
commit | 67d8b96d6d04d8aae231425bbf2a192966c4a3f8 (patch) | |
tree | 67459cf7f57c1c4c3b26fe7aa6b3f6748a3191d6 | |
parent | ef231a14052d25f54e0da6c866ec828418310c87 (diff) |
[wasm] Misc Debugger improvements (including tests) (#65752)
* [wasm][debugger] Fail test if an assertion is detected
* [wasm][debugger] Make DevToolsProxy's `pending_ops` thread-safe
Currently, `pending_ops` can get written by different threads at the
same time, and also read in parallel. This causes tests to randomly fail
with:
```
DevToolsProxy::Run: Exception System.ArgumentException: The tasks argument included a null value. (Parameter 'tasks')
at System.Threading.Tasks.Task.WhenAny(Task[] tasks)
at Microsoft.WebAssembly.Diagnostics.DevToolsProxy.Run(Uri browserUri, WebSocket ideSocket) in /_/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs:line 269
```
Instead, we use `Channel<T>` to add the ops, and then read those in
the main loop, and add to the *local* `pending_ops` list.
* [wasm] Install chrome for debugger tests
- controlled by `$(InstallChromeForDebuggerTests)` which defaults to
`true` for non-CI docker containers
- Useful for using the correct chrome version when testing locally, or
on codespaces
- Also, add support for detecting, and defaulting to such an
installation
* [wasm][debugger] Disable tests failing with runtime assertions
`DebuggerTests.EvaluateOnCallFrameTests.EvaluateSimpleMethodCallsError`: https://github.com/dotnet/runtime/issues/65744
`DebuggerTests.ArrayTests.InvalidArrayId`: https://github.com/dotnet/runtime/issues/65742
* [wasm][debugger] Fix NRE with `"null"` condition for a breakpoint
`ConditionalBreakpoint` test fails.
The test with condition=`"null"` failed with a NRE, instead of correctly
handling it as a condition that returns null.
```
Unable evaluate conditional breakpoint: System.Exception: Internal Error: Unable to run (null ),
error: Object reference not set to an instance of an object..
---> System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.WebAssembly.Diagnostics.EvaluateExpression.CompileAndRunTheExpression(String expression, MemberReferenceResolver resolver, CancellationToken token) in /workspaces/runtime/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs:line 399
--- End of inner exception stack trace ---
at Microsoft.WebAssembly.Diagnostics.EvaluateExpression.CompileAndRunTheExpression(String expression, MemberReferenceResolver resolver, CancellationToken token) in /workspaces/runtime/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs:line 407
at Microsoft.WebAssembly.Diagnostics.MonoProxy.EvaluateCondition(SessionId sessionId, ExecutionContext context, Frame mono_frame, Breakpoint bp, CancellationToken token) in /workspaces/runtime/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs:line 801 condition:null
```
* [wasm] Improve default message for ReturnAsErrorException,
.. since we seem to be not catching them as intended in many places.
* [wasm] Better log, and surface errors in evaluation conditional
.. breakpoints.
Catch the proper exception `ReturnAsErrorException`, so we can get the
actual error message. And log that as an error for the user too.
* [wasm][debugger] Allow setting proxy port for BrowserDebugHost
.. with `--proxy-port=<port>`.
Fixes https://github.com/dotnet/runtime/issues/65209 .
* [wasm][debugger] Handle errors in getting value for locals
Essentially, catch, and skip.
* message cleanup
* remove trailing space
13 files changed, 207 insertions, 64 deletions
diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets index ee6267244d3..0c80ec4ec1f 100644 --- a/src/libraries/sendtohelix-wasm.targets +++ b/src/libraries/sendtohelix-wasm.targets @@ -38,8 +38,8 @@ <HelixPreCommand Include="export XHARNESS_DISABLE_COLORED_OUTPUT=true" /> <HelixPreCommand Include="export XHARNESS_LOG_WITH_TIMESTAMPS=true" /> - <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="export PATH=$HELIX_CORRELATION_PAYLOAD/chromedriver_linux64:$PATH" /> - <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="export PATH=$HELIX_CORRELATION_PAYLOAD/chrome-linux:$PATH" /> + <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="export PATH=$HELIX_CORRELATION_PAYLOAD/$(ChromeDriverDirName):$PATH" /> + <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="export PATH=$HELIX_CORRELATION_PAYLOAD/$(ChromiumDirName):$PATH" /> <!-- Fix symbolic links that are broken already on build machine and also in the correlation payload --> <HelixPreCommand Condition="'$(NeedsEMSDKNode)' == 'true'" Include="export HELIX_NODEJS_VERSION=%24(ls $HELIX_CORRELATION_PAYLOAD/build/emsdk/node)" /> @@ -60,8 +60,8 @@ <HelixPreCommand Include="set XHARNESS_DISABLE_COLORED_OUTPUT=true" /> <HelixPreCommand Include="set XHARNESS_LOG_WITH_TIMESTAMPS=true" /> - <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="set PATH=%HELIX_CORRELATION_PAYLOAD%\chromedriver_win32%3B%PATH%" /> - <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="set PATH=%HELIX_CORRELATION_PAYLOAD%\chrome-win%3B%PATH%" /> + <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="set PATH=%HELIX_CORRELATION_PAYLOAD%\$(ChromeDriverDirName)%3B%PATH%" /> + <HelixPreCommand Condition="'$(NeedsToRunOnBrowser)' == 'true'" Include="set PATH=%HELIX_CORRELATION_PAYLOAD%\$(ChromiumDirName)%3B%PATH%" /> <HelixPreCommand Condition="'$(NeedsEMSDKNode)' == 'true'" Include="for /f %%i in ('dir %HELIX_CORRELATION_PAYLOAD%\build\emsdk\node /b') do set HELIX_NODEJS_VERSION=%%i" /> <HelixPreCommand Condition="'$(NeedsEMSDKNode)' == 'true'" Include="set PATH=%HELIX_CORRELATION_PAYLOAD%\build\emsdk\node\%HELIX_NODEJS_VERSION%\bin%3B%PATH%" /> @@ -96,6 +96,8 @@ </When> </Choose> + <Import Project="$(RepoRoot)src\mono\wasm\BrowsersForTesting.props" /> + <Target Name="PrepareForBuildHelixWorkItems_Wasm"> <PropertyGroup> <WasmBuildTargetsDir>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'build'))</WasmBuildTargetsDir> @@ -106,26 +108,6 @@ <WorkItemPrefix Condition="'$(WorkItemPrefix)' == '' and '$(Scenario)' != ''">$(Scenario)-</WorkItemPrefix> </PropertyGroup> - <!-- Version number to revision number mapping from http://omahaproxy.appspot.com/ and find the closest one in - https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/ - and https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win_x64/ - - Eg. latest stable version is 96.0.4664.45 with revision 929512. - but the closest one available in the snapshosts is 929513. - Please make sure to check both platforms as sometime - the same snapshot might not be available in one of them. - --> - <PropertyGroup Condition="'$(BrowserHost)' != 'windows'"> - <ChromiumRevision>929513</ChromiumRevision> - <ChromiumUrl>https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chrome-linux.zip</ChromiumUrl> - <ChromeDriverUrl>https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip</ChromeDriverUrl> - </PropertyGroup> - <PropertyGroup Condition="'$(BrowserHost)' == 'windows'"> - <ChromiumRevision>929513</ChromiumRevision> - <ChromiumUrl>https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chrome-win.zip</ChromiumUrl> - <ChromeDriverUrl>https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chromedriver_win32.zip</ChromeDriverUrl> - </PropertyGroup> - <ItemGroup Condition="'$(NeedsToRunOnBrowser)' == 'true'"> <HelixCorrelationPayload Include="chromium" Uri="$(ChromiumUrl)" /> <HelixCorrelationPayload Include="chromedriver" Uri="$(ChromeDriverUrl)" /> diff --git a/src/mono/wasm/BrowsersForTesting.props b/src/mono/wasm/BrowsersForTesting.props new file mode 100644 index 00000000000..eb0c1476f00 --- /dev/null +++ b/src/mono/wasm/BrowsersForTesting.props @@ -0,0 +1,28 @@ +<Project> + <!-- Version number to revision number mapping from http://omahaproxy.appspot.com/ and find the closest one in + https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/ + and https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win_x64/ + + Eg. latest stable version is 96.0.4664.45 with revision 929512. + but the closest one available in the snapshosts is 929513. + Please make sure to check both platforms as sometime + the same snapshot might not be available in one of them. + --> + <PropertyGroup Condition="'$(BrowserHost)' != 'windows'"> + <ChromiumRevision>929513</ChromiumRevision> + <ChromiumUrl>https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chrome-linux.zip</ChromiumUrl> + <ChromeDriverUrl>https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip</ChromeDriverUrl> + <ChromiumDirName>chrome-linux</ChromiumDirName> + <ChromeDriverDirName>chromedriver_linux64</ChromeDriverDirName> + <ChromiumBinaryName>chrome</ChromiumBinaryName> + </PropertyGroup> + + <PropertyGroup Condition="'$(BrowserHost)' == 'windows'"> + <ChromiumRevision>929513</ChromiumRevision> + <ChromiumUrl>https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chrome-win.zip</ChromiumUrl> + <ChromeDriverUrl>https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chromedriver_win32.zip</ChromeDriverUrl> + <ChromiumDirName>chrome-win</ChromiumDirName> + <ChromeDriverDirName>chromedriver_win32</ChromeDriverDirName> + <ChromiumBinaryName>chrome.exe</ChromiumBinaryName> + </PropertyGroup> +</Project> diff --git a/src/mono/wasm/README.md b/src/mono/wasm/README.md index 86e7b569127..2fd07e682ad 100644 --- a/src/mono/wasm/README.md +++ b/src/mono/wasm/README.md @@ -143,6 +143,8 @@ To run a test with `FooBar` in the name: Additional arguments for `dotnet test` can be passed via `MSBUILD_ARGS` or `TEST_ARGS`. For example `MSBUILD_ARGS="/p:WasmDebugLevel=5"`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time. +- Chrome can be installed for testing by setting `InstallChromeForDebuggerTests=true` when building the tests. + ## Run samples The samples in `src/mono/sample/wasm` can be build and run like this: diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs index c3f184d056a..567b1d7837e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.WebSockets; using System.Text; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -24,7 +25,8 @@ namespace Microsoft.WebAssembly.Diagnostics private ClientWebSocket browser; private WebSocket ide; private int next_cmd_id; - private List<Task> pending_ops = new List<Task>(); + private readonly ChannelWriter<Task> _channelWriter; + private readonly ChannelReader<Task> _channelReader; private List<DevToolsQueue> queues = new List<DevToolsQueue>(); protected readonly ILogger logger; @@ -33,6 +35,10 @@ namespace Microsoft.WebAssembly.Diagnostics { string loggerSuffix = string.IsNullOrEmpty(loggerId) ? string.Empty : $"-{loggerId}"; logger = loggerFactory.CreateLogger($"{nameof(DevToolsProxy)}{loggerSuffix}"); + + var channel = Channel.CreateUnbounded<Task>(new UnboundedChannelOptions { SingleReader = true }); + _channelWriter = channel.Writer; + _channelReader = channel.Reader; } protected virtual Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) @@ -94,7 +100,7 @@ namespace Microsoft.WebAssembly.Diagnostics return queues.FirstOrDefault(q => q.CurrentSend == task); } - private void Send(WebSocket to, JObject o, CancellationToken token) + private async Task Send(WebSocket to, JObject o, CancellationToken token) { string sender = browser == to ? "Send-browser" : "Send-ide"; @@ -106,7 +112,7 @@ namespace Microsoft.WebAssembly.Diagnostics Task task = queue.Send(bytes, token); if (task != null) - pending_ops.Add(task); + await _channelWriter.WriteAsync(task, token); } private async Task OnEvent(SessionId sessionId, string method, JObject args, CancellationToken token) @@ -116,7 +122,7 @@ namespace Microsoft.WebAssembly.Diagnostics if (!await AcceptEvent(sessionId, method, args, token)) { //logger.LogDebug ("proxy browser: {0}::{1}",method, args); - SendEventInternal(sessionId, method, args, token); + await SendEventInternal(sessionId, method, args, token); } } catch (Exception e) @@ -132,7 +138,7 @@ namespace Microsoft.WebAssembly.Diagnostics if (!await AcceptCommand(id, method, args, token)) { Result res = await SendCommandInternal(id, method, args, token); - SendResponseInternal(id, res, token); + await SendResponseInternal(id, res, token); } } catch (Exception e) @@ -153,7 +159,7 @@ namespace Microsoft.WebAssembly.Diagnostics logger.LogError("Cannot respond to command: {id} with result: {result} - command is not pending", id, result); } - private void ProcessBrowserMessage(string msg, CancellationToken token) + private Task ProcessBrowserMessage(string msg, CancellationToken token) { var res = JObject.Parse(msg); @@ -161,23 +167,30 @@ namespace Microsoft.WebAssembly.Diagnostics Log("protocol", $"browser: {msg}"); if (res["id"] == null) - pending_ops.Add(OnEvent(res.ToObject<SessionId>(), res["method"].Value<string>(), res["params"] as JObject, token)); + { + return OnEvent(res.ToObject<SessionId>(), res["method"].Value<string>(), res["params"] as JObject, token); + } else + { OnResponse(res.ToObject<MessageId>(), Result.FromJson(res)); + return null; + } } - private void ProcessIdeMessage(string msg, CancellationToken token) + private Task ProcessIdeMessage(string msg, CancellationToken token) { Log("protocol", $"ide: {msg}"); if (!string.IsNullOrEmpty(msg)) { var res = JObject.Parse(msg); var id = res.ToObject<MessageId>(); - pending_ops.Add(OnCommand( + return OnCommand( id, res["method"].Value<string>(), - res["params"] as JObject, token)); + res["params"] as JObject, token); } + + return null; } internal async Task<Result> SendCommand(SessionId id, string method, JObject args, CancellationToken token) @@ -186,7 +199,7 @@ namespace Microsoft.WebAssembly.Diagnostics return await SendCommandInternal(id, method, args, token); } - private Task<Result> SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token) + private async Task<Result> SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token) { int id = Interlocked.Increment(ref next_cmd_id); @@ -204,17 +217,17 @@ namespace Microsoft.WebAssembly.Diagnostics //Log ("verbose", $"add cmd id {sessionId}-{id}"); pending_cmds[msgId] = tcs; - Send(this.browser, o, token); - return tcs.Task; + await Send(browser, o, token); + return await tcs.Task; } - public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + public Task SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { //Log ("verbose", $"sending event {method}: {args}"); - SendEventInternal(sessionId, method, args, token); + return SendEventInternal(sessionId, method, args, token); } - private void SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token) + private Task SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token) { var o = JObject.FromObject(new { @@ -224,7 +237,7 @@ namespace Microsoft.WebAssembly.Diagnostics if (sessionId.sessionId != null) o["sessionId"] = sessionId.sessionId; - Send(this.ide, o, token); + return Send(ide, o, token); } internal void SendResponse(MessageId id, Result result, CancellationToken token) @@ -232,13 +245,13 @@ namespace Microsoft.WebAssembly.Diagnostics SendResponseInternal(id, result, token); } - private void SendResponseInternal(MessageId id, Result result, CancellationToken token) + private Task SendResponseInternal(MessageId id, Result result, CancellationToken token) { JObject o = result.ToJObject(id); if (!result.IsOk) logger.LogError($"sending error response for id: {id} -> {result}"); - Send(this.ide, o, token); + return Send(this.ide, o, token); } // , HttpContext context) @@ -258,10 +271,14 @@ namespace Microsoft.WebAssembly.Diagnostics Log("verbose", $"DevToolsProxy: Client connected on {browserUri}"); var x = new CancellationTokenSource(); + List<Task> pending_ops = new(); + pending_ops.Add(ReadOne(browser, x.Token)); pending_ops.Add(ReadOne(ide, x.Token)); pending_ops.Add(side_exception.Task); pending_ops.Add(client_initiated_close.Task); + Task<bool> readerTask = _channelReader.WaitToReadAsync(x.Token).AsTask(); + pending_ops.Add(readerTask); try { @@ -278,6 +295,16 @@ namespace Microsoft.WebAssembly.Diagnostics break; } + if (readerTask.IsCompleted) + { + while (_channelReader.TryRead(out Task newTask)) + { + pending_ops.Add(newTask); + } + + pending_ops[4] = _channelReader.WaitToReadAsync(x.Token).AsTask(); + } + //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task)); if (completedTask == pending_ops[0]) { @@ -285,7 +312,9 @@ namespace Microsoft.WebAssembly.Diagnostics if (msg != null) { pending_ops[0] = ReadOne(browser, x.Token); //queue next read - ProcessBrowserMessage(msg, x.Token); + Task newTask = ProcessBrowserMessage(msg, x.Token); + if (newTask != null) + pending_ops.Add(newTask); } } else if (completedTask == pending_ops[1]) @@ -294,7 +323,9 @@ namespace Microsoft.WebAssembly.Diagnostics if (msg != null) { pending_ops[1] = ReadOne(ide, x.Token); //queue next read - ProcessIdeMessage(msg, x.Token); + Task newTask = ProcessIdeMessage(msg, x.Token); + if (newTask != null) + pending_ops.Add(newTask); } } else if (completedTask == pending_ops[2]) @@ -314,10 +345,13 @@ namespace Microsoft.WebAssembly.Diagnostics } } } + + _channelWriter.Complete(); } catch (Exception e) { Log("error", $"DevToolsProxy::Run: Exception {e}"); + _channelWriter.Complete(e); //throw; } finally diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 213a50ae1ce..dc9296b5f6b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -396,7 +396,7 @@ namespace Microsoft.WebAssembly.Diagnostics string.Join("\n", findVarNMethodCall.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); var state = await newScript.RunAsync(cancellationToken: token); - return JObject.FromObject(ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType())); + return JObject.FromObject(ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue?.GetType())); } catch (CompilationErrorException cee) { @@ -419,7 +419,7 @@ namespace Microsoft.WebAssembly.Diagnostics private static object ConvertCSharpToJSType(object v, Type type) { if (v == null) - return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() }; + return new { type = "object", subtype = "null", className = type?.ToString(), description = type?.ToString() }; if (v is string s) return new { type = "string", value = s, description = s }; if (NumericTypes.Contains(v.GetType())) @@ -443,10 +443,11 @@ namespace Microsoft.WebAssembly.Diagnostics } set { } } - public ReturnAsErrorException(JObject error) + public ReturnAsErrorException(JObject error) : base(error.ToString()) => Error = Result.Err(error); public ReturnAsErrorException(string message, string className) + : base($"[{className}] {message}") { var result = new { @@ -466,5 +467,7 @@ namespace Microsoft.WebAssembly.Diagnostics } })); } + + public override string ToString() => $"Error object: {Error}. {base.ToString()}"; } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 565c2bcb841..581f7ef5cf6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -51,7 +51,7 @@ namespace Microsoft.WebAssembly.Diagnostics internal Task<Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token); - internal void SendLog(SessionId sessionId, string message, CancellationToken token) + internal void SendLog(SessionId sessionId, string message, CancellationToken token, string type = "warning") { if (!contexts.TryGetValue(sessionId, out ExecutionContext context)) return; @@ -68,7 +68,7 @@ namespace Microsoft.WebAssembly.Diagnostics SendEvent(id, "Log.entryAdded", o, token);*/ var o = JObject.FromObject(new { - type = "warning", + type, args = new JArray(JObject.FromObject(new { type = "string", @@ -139,7 +139,7 @@ namespace Microsoft.WebAssembly.Diagnostics case "Runtime.executionContextCreated": { - SendEvent(sessionId, method, args, token); + await SendEvent(sessionId, method, args, token); JToken ctx = args?["context"]; var aux_data = ctx?["auxData"] as JObject; int id = ctx["id"].Value<int>(); @@ -805,14 +805,22 @@ namespace Microsoft.WebAssembly.Diagnostics if (retValue?["value"]?.Value<bool>() == true) return true; } - else if (retValue?["value"]?.Type != JTokenType.Null) + else if (retValue?["value"] != null && // null object, missing value + retValue?["value"]?.Type != JTokenType.Null) + { return true; + } + } + catch (ReturnAsErrorException raee) + { + logger.LogDebug($"Unable to evaluate breakpoint condition '{condition}': {raee}"); + SendLog(sessionId, $"Unable to evaluate breakpoint condition '{condition}': {raee.Message}", token, type: "error"); + bp.ConditionAlreadyEvaluatedWithError = true; } catch (Exception e) { - Log("info", $"Unable evaluate conditional breakpoint: {e} condition:{condition}"); + Log("info", $"Unable to evaluate breakpoint condition '{condition}': {e}"); bp.ConditionAlreadyEvaluatedWithError = true; - return false; } return false; } @@ -986,7 +994,7 @@ namespace Microsoft.WebAssembly.Diagnostics await SendCommand(sessionId, "Debugger.resume", new JObject(), token); return true; } - SendEvent(sessionId, "Debugger.paused", o, token); + await SendEvent(sessionId, "Debugger.paused", o, token); return true; @@ -1103,7 +1111,7 @@ namespace Microsoft.WebAssembly.Diagnostics foreach (SourceFile source in asm.Sources) { var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); - SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); + await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); } return asm.GetMethodByToken(method_token); } @@ -1333,7 +1341,7 @@ namespace Microsoft.WebAssembly.Diagnostics { JObject scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); Log("debug", $"sending {source.Url} {context.Id} {sessionId.sessionId}"); - SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); + await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); foreach (var req in context.BreakpointRequests.Values) { @@ -1412,7 +1420,7 @@ namespace Microsoft.WebAssembly.Diagnostics DebugStore store = await LoadStore(sessionId, token); context.ready.SetResult(store); - SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); + await SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); context.SdbAgent.ResetStore(store); return store; } @@ -1502,7 +1510,7 @@ namespace Microsoft.WebAssembly.Diagnostics }; if (sendResolvedEvent) - SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token); + await SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token); } req.Locations.AddRange(breakpoints); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 793d3c980b9..f30d42e69de 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -2121,8 +2121,16 @@ namespace Microsoft.WebAssembly.Diagnostics using var localsDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetValues, commandParamsWriter, token); foreach (var var in varIds) { - var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, false, -1, false, token); - locals.Add(var_json); + try + { + var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, false, -1, false, token); + locals.Add(var_json); + } + catch (Exception ex) + { + logger.LogDebug($"Failed to create value for local var {var}: {ex}"); + continue; + } } if (!method.Info.IsStatic()) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index 438eceea378..a9e87746bd8 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -552,6 +552,8 @@ namespace DebuggerTests } [Fact] + [Trait("Category", "windows-failing")] // https://github.com/dotnet/runtime/issues/65742 + [Trait("Category", "linux-failing")] // https://github.com/dotnet/runtime/issues/65742 public async Task InvalidArrayId() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index d39c2c2c10b..aa61555ab22 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -90,14 +90,17 @@ namespace DebuggerTests { chrome_path = FindChromePath(); if (!string.IsNullOrEmpty(chrome_path)) + { + chrome_path = Path.GetFullPath(chrome_path); Console.WriteLine ($"** Using chrome from {chrome_path}"); + } else throw new Exception("Could not find an installed Chrome to use"); } return chrome_path; - string FindChromePath() + static string FindChromePath() { string chrome_path_env_var = Environment.GetEnvironmentVariable("CHROME_PATH_FOR_DEBUGGER_TESTS"); if (!string.IsNullOrEmpty(chrome_path_env_var)) @@ -108,6 +111,15 @@ namespace DebuggerTests Console.WriteLine ($"warning: Could not find CHROME_PATH_FOR_DEBUGGER_TESTS={chrome_path_env_var}"); } + // Look for a chrome installed in artifacts, for local runs + string baseDir = Path.Combine(Path.GetDirectoryName(typeof(DebuggerTestBase).Assembly.Location), "..", ".."); + string path = Path.Combine(baseDir, "chrome", "chrome-linux", "chrome"); + if (File.Exists(path)) + return path; + path = Path.Combine(baseDir, "chrome", "chrome-win", "chrome.exe"); + if (File.Exists(path)) + return path; + return PROBE_LIST.FirstOrDefault(p => File.Exists(p)); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj index 602f06cfc95..8f981735595 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -5,6 +5,17 @@ <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <RunAnalyzers>false</RunAnalyzers> <IsTestProject>true</IsTestProject> + <VersionsPropsFile>$(MSBuildThisFileDirectory)..\..\BrowsersForTesting.props</VersionsPropsFile> + <BrowserHost Condition="$([MSBuild]::IsOSPlatform('windows'))">windows</BrowserHost> + <InstallChromeForDebuggerTests Condition="'$(InstallChromeForDebuggerTests)' == '' and '$(ContinuousIntegrationBuild)' != 'true' and Exists('/.dockerenv')">true</InstallChromeForDebuggerTests> + </PropertyGroup> + + <Import Project="$(VersionsPropsFile)" /> + + <PropertyGroup> + <ChromeDir>$(ArtifactsBinDir)DebuggerTestSuite\chrome\</ChromeDir> + <ChromeStampDir>$(ArtifactsBinDir)DebuggerTestSuite\</ChromeStampDir> + <ChromeStampFile>$(ChromeStampDir).install-chrome-$(ChromiumRevision).stamp</ChromeStampFile> </PropertyGroup> <ItemGroup> @@ -34,4 +45,32 @@ <Copy SourceFiles="@(_FilesToCopy)" DestinationFiles="$(ArchiveDirForHelix)\%(TargetPath)\%(RecursiveDir)%(FileName)%(Extension)" /> </Target> + + <Target Name="DownloadAndInstallChrome" + AfterTargets="Build" + Condition="!Exists($(ChromeStampFile)) and '$(InstallChromeForDebuggerTests)' == 'true'"> + + <ItemGroup> + <_StampFile Include="$(ChromeStampDir).install-chrome*.stamp" /> + </ItemGroup> + + <Delete Files="@(_StampFile)" /> + <RemoveDir Directories="$(ChromeDir)" /> + + <DownloadFile SourceUrl="$(ChromiumUrl)" DestinationFolder="$(ChromeDir)" SkipUnchangedFiles="true"> + <Output TaskParameter="DownloadedFile" PropertyName="_DownloadedFile" /> + </DownloadFile> + <Unzip SourceFiles="$(_DownloadedFile)" DestinationFolder="$(ChromeDir)" /> + + <PropertyGroup> + <_ChromeBinaryPath>$([MSBuild]::NormalizePath($(ChromeDir), $(ChromiumDirName), $(ChromiumBinaryName)))</_ChromeBinaryPath> + </PropertyGroup> + + <Error Text="Cannot find chrome at $(_ChromeBinaryPath) in the downloaded copy" + Condition="!Exists($(_ChromeBinaryPath))" /> + + <Exec Command="chmod +x $(_ChromeBinaryPath)" Condition="!$([MSBuild]::IsOSPlatform('windows'))" /> + + <Touch Files="$(ChromeStampFile)" AlwaysCreate="true" /> + </Target> </Project> diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs index 7c5874df227..6eed6e177f9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs @@ -18,6 +18,7 @@ namespace Microsoft.WebAssembly.Diagnostics ClientWebSocket socket; TaskCompletionSource _clientInitiatedClose = new TaskCompletionSource(); TaskCompletionSource _shutdownRequested = new TaskCompletionSource(); + readonly TaskCompletionSource<Exception> _failRequested = new(); TaskCompletionSource _newSendTaskAvailable = new (); protected readonly ILogger logger; @@ -65,6 +66,14 @@ namespace Microsoft.WebAssembly.Diagnostics } } + public void Fail(Exception exception) + { + if (_failRequested.Task.IsCompleted) + logger.LogError($"Fail requested again with {exception}"); + else + _failRequested.TrySetResult(exception); + } + async Task<string> ReadOne(CancellationToken token) { byte[] buff = new byte[4000]; @@ -172,7 +181,8 @@ namespace Microsoft.WebAssembly.Diagnostics ReadOne(linkedCts.Token), _newSendTaskAvailable.Task, _clientInitiatedClose.Task, - _shutdownRequested.Task + _shutdownRequested.Task, + _failRequested.Task }; // In case we had a Send called already @@ -192,6 +202,9 @@ namespace Microsoft.WebAssembly.Diagnostics if (_clientInitiatedClose.Task.IsCompleted) return (RunLoopStopReason.ClientInitiatedClose, new TaskCanceledException("Proxy or the browser closed the connection")); + if (_failRequested.Task.IsCompleted) + return (RunLoopStopReason.Exception, _failRequested.Task.Result); + if (_newSendTaskAvailable.Task.IsCompleted) { // Just needed to wake up. the new task has already diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index c38596af1e5..946dcbd394b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -494,6 +494,8 @@ namespace DebuggerTests [Fact] + [Trait("Category", "windows-failing")] // https://github.com/dotnet/runtime/issues/65744 + [Trait("Category", "linux-failing")] // https://github.com/dotnet/runtime/issues/65744 public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.EvaluateMethodTestsClass.TestEvaluate", "run", 9, "run", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs index 3a74ed70e78..cde3ef31145 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -29,6 +29,7 @@ namespace DebuggerTests public const string READY = "ready"; public CancellationToken Token { get; } public InspectorClient Client { get; } + public bool DetectAndFailOnAssertions { get; set; } = true; private CancellationTokenSource _cancellationTokenSource; @@ -184,8 +185,17 @@ namespace DebuggerTests NotifyOf(READY, args); break; case "Runtime.consoleAPICalled": - _logger.LogInformation(FormatConsoleAPICalled(args)); + { + string line = FormatConsoleAPICalled(args); + _logger.LogInformation(line); + if (DetectAndFailOnAssertions && line.Contains("console.error: * Assertion at")) + { + args["__forMethod"] = method; + Client.Fail(new ArgumentException($"Assertion detected in the messages: {line}{Environment.NewLine}{args}")); + return; + } break; + } case "Inspector.detached": case "Inspector.targetCrashed": case "Inspector.targetReloadedAfterCrash": |