diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets
index ee6267244d3f7b..0c80ec4ec1f790 100644
--- a/src/libraries/sendtohelix-wasm.targets
+++ b/src/libraries/sendtohelix-wasm.targets
@@ -38,8 +38,8 @@
-
-
+
+
@@ -60,8 +60,8 @@
-
-
+
+
@@ -96,6 +96,8 @@
+
+
$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'build'))
@@ -106,26 +108,6 @@
$(Scenario)-
-
-
- 929513
- https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chrome-linux.zip
- https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip
-
-
- 929513
- https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chrome-win.zip
- https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chromedriver_win32.zip
-
-
diff --git a/src/mono/wasm/BrowsersForTesting.props b/src/mono/wasm/BrowsersForTesting.props
new file mode 100644
index 00000000000000..eb0c1476f0032d
--- /dev/null
+++ b/src/mono/wasm/BrowsersForTesting.props
@@ -0,0 +1,28 @@
+
+
+
+ 929513
+ https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chrome-linux.zip
+ https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/$(ChromiumRevision)/chromedriver_linux64.zip
+ chrome-linux
+ chromedriver_linux64
+ chrome
+
+
+
+ 929513
+ https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chrome-win.zip
+ https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/$(ChromiumRevision)/chromedriver_win32.zip
+ chrome-win
+ chromedriver_win32
+ chrome.exe
+
+
diff --git a/src/mono/wasm/README.md b/src/mono/wasm/README.md
index 86e7b56912718d..2fd07e682ad72b 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 c3f184d056ae15..567b1d7837e66f 100644
--- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs
+++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs
@@ -8,6 +8,7 @@
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 @@ internal class DevToolsProxy
private ClientWebSocket browser;
private WebSocket ide;
private int next_cmd_id;
- private List pending_ops = new List();
+ private readonly ChannelWriter _channelWriter;
+ private readonly ChannelReader _channelReader;
private List queues = new List();
protected readonly ILogger logger;
@@ -33,6 +35,10 @@ public DevToolsProxy(ILoggerFactory loggerFactory, string loggerId)
{
string loggerSuffix = string.IsNullOrEmpty(loggerId) ? string.Empty : $"-{loggerId}";
logger = loggerFactory.CreateLogger($"{nameof(DevToolsProxy)}{loggerSuffix}");
+
+ var channel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true });
+ _channelWriter = channel.Writer;
+ _channelReader = channel.Reader;
}
protected virtual Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
@@ -94,7 +100,7 @@ private DevToolsQueue GetQueueForTask(Task task)
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 @@ private void Send(WebSocket to, JObject o, CancellationToken token)
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 @@ private async Task OnEvent(SessionId sessionId, string method, JObject args, Can
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 @@ private async Task OnCommand(MessageId id, string method, JObject args, Cancella
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 @@ private void OnResponse(MessageId id, Result result)
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 @@ private void ProcessBrowserMessage(string msg, CancellationToken token)
Log("protocol", $"browser: {msg}");
if (res["id"] == null)
- pending_ops.Add(OnEvent(res.ToObject(), res["method"].Value(), res["params"] as JObject, token));
+ {
+ return OnEvent(res.ToObject(), res["method"].Value(), res["params"] as JObject, token);
+ }
else
+ {
OnResponse(res.ToObject(), 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();
- pending_ops.Add(OnCommand(
+ return OnCommand(
id,
res["method"].Value(),
- res["params"] as JObject, token));
+ res["params"] as JObject, token);
}
+
+ return null;
}
internal async Task SendCommand(SessionId id, string method, JObject args, CancellationToken token)
@@ -186,7 +199,7 @@ internal async Task SendCommand(SessionId id, string method, JObject arg
return await SendCommandInternal(id, method, args, token);
}
- private Task SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
+ private async Task SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
{
int id = Interlocked.Increment(ref next_cmd_id);
@@ -204,17 +217,17 @@ private Task SendCommandInternal(SessionId sessionId, string method, JOb
//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 @@ private void SendEventInternal(SessionId sessionId, string method, JObject args,
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 @@ internal void SendResponse(MessageId id, Result result, CancellationToken token)
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 @@ public async Task Run(Uri browserUri, WebSocket ideSocket)
Log("verbose", $"DevToolsProxy: Client connected on {browserUri}");
var x = new CancellationTokenSource();
+ List 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 readerTask = _channelReader.WaitToReadAsync(x.Token).AsTask();
+ pending_ops.Add(readerTask);
try
{
@@ -278,6 +295,16 @@ public async Task Run(Uri browserUri, WebSocket ideSocket)
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 @@ public async Task Run(Uri browserUri, WebSocket ideSocket)
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 @@ public async Task Run(Uri browserUri, WebSocket ideSocket)
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 @@ public async Task Run(Uri browserUri, WebSocket ideSocket)
}
}
}
+
+ _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 213a50ae1ce7eb..dc9296b5f6b087 100644
--- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
+++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
@@ -396,7 +396,7 @@ internal static async Task CompileAndRunTheExpression(string expression
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 @@ internal static async Task CompileAndRunTheExpression(string expression
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 @@ public Result Error
}
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 @@ public ReturnAsErrorException(string message, string className)
}
}));
}
+
+ 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 565c2bcb841a3e..581f7ef5cf6ba8 100644
--- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
+++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
@@ -51,7 +51,7 @@ private bool UpdateContext(SessionId sessionId, ExecutionContext executionContex
internal Task 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 @@ internal void SendLog(SessionId sessionId, string message, CancellationToken tok
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 @@ protected override async Task AcceptEvent(SessionId sessionId, string meth
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();
@@ -805,14 +805,22 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext
if (retValue?["value"]?.Value() == 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 @@ private async Task SendCallStack(SessionId sessionId, ExecutionContext con
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 @@ internal async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method
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 @@ private async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, Exe
{
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 @@ private async Task RuntimeReady(SessionId sessionId, CancellationTok
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 @@ private async Task SetBreakpoint(SessionId sessionId, DebugStore store, Breakpoi
};
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 793d3c980b9f15..f30d42e69de143 100644
--- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
+++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
@@ -2121,8 +2121,16 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met
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 438eceea378d3e..a9e87746bd89ac 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs
@@ -552,6 +552,8 @@ await CompareObjectPropertiesFor(frame_locals, "this",
}
[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 d39c2c2c10bd96..aa61555ab223ab 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
@@ -90,14 +90,17 @@ static string GetChromePath()
{
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 @@ string FindChromePath()
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 602f06cfc95ce9..8f981735595b23 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj
@@ -5,6 +5,17 @@
true
false
true
+ $(MSBuildThisFileDirectory)..\..\BrowsersForTesting.props
+ windows
+ true
+
+
+
+
+
+ $(ArtifactsBinDir)DebuggerTestSuite\chrome\
+ $(ArtifactsBinDir)DebuggerTestSuite\
+ $(ChromeStampDir).install-chrome-$(ChromiumRevision).stamp
@@ -34,4 +45,32 @@
+
+
+
+
+ <_StampFile Include="$(ChromeStampDir).install-chrome*.stamp" />
+
+
+
+
+
+
+
+
+
+
+
+ <_ChromeBinaryPath>$([MSBuild]::NormalizePath($(ChromeDir), $(ChromiumDirName), $(ChromiumBinaryName)))
+
+
+
+
+
+
+
+
diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs
index 7c5874df227171..6eed6e177f9a9c 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs
@@ -18,6 +18,7 @@ internal class DevToolsClient : IDisposable
ClientWebSocket socket;
TaskCompletionSource _clientInitiatedClose = new TaskCompletionSource();
TaskCompletionSource _shutdownRequested = new TaskCompletionSource();
+ readonly TaskCompletionSource _failRequested = new();
TaskCompletionSource _newSendTaskAvailable = new ();
protected readonly ILogger logger;
@@ -65,6 +66,14 @@ public async Task Shutdown(CancellationToken cancellationToken)
}
}
+ public void Fail(Exception exception)
+ {
+ if (_failRequested.Task.IsCompleted)
+ logger.LogError($"Fail requested again with {exception}");
+ else
+ _failRequested.TrySetResult(exception);
+ }
+
async Task ReadOne(CancellationToken token)
{
byte[] buff = new byte[4000];
@@ -172,7 +181,8 @@ protected async Task ConnectWithMainLoops(
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 @@ protected async Task ConnectWithMainLoops(
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 c38596af1e5c69..946dcbd394b65c 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
@@ -494,6 +494,8 @@ async Task EvaluateOnCallFrameFail(string call_frame_id, params (string expressi
[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 3a74ed70e78c32..cde3ef31145a65 100644
--- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs
+++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs
@@ -29,6 +29,7 @@ class Inspector
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 @@ async Task OnMessage(string method, JObject args, CancellationToken token)
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":