diff --git a/eng/Configurations.props b/eng/Configurations.props index c4bd905ddc6278..9da7fe86922370 100644 --- a/eng/Configurations.props +++ b/eng/Configurations.props @@ -73,6 +73,7 @@ <_runtimeOS>$(RuntimeOS) + <__DistroRid>osx-x64 <_parseDistroRid>$(__DistroRid) <_parseDistroRid Condition="'$(_parseDistroRid)' == ''">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier) <_distroRidIndex>$(_parseDistroRid.LastIndexOfAny("-")) diff --git a/eng/testing/WasmRunnerTemplate.sh b/eng/testing/WasmRunnerTemplate.sh index e2e3996cab58c0..fbe0d22e91975e 100644 --- a/eng/testing/WasmRunnerTemplate.sh +++ b/eng/testing/WasmRunnerTemplate.sh @@ -18,6 +18,14 @@ if [ -z "$XHARNESS_COMMAND" ]; then XHARNESS_COMMAND="test" fi +DEBUG_PROXY_PROJ=$HOME/dev/runtime/src/mono/wasm/debugger/BrowserDebugHost/BrowserDebugHost.csproj +DEBUG_PROXY_PORT=9300 +BROWSER_DEBUG_PORT=9222 +XHARNESS_ARGS="$XHARNESS_ARGS --debug-port=$BROWSER_DEBUG_PORT" + +dotnet run -p $DEBUG_PROXY_PROJ $BROWSER_DEBUG_PORT $DEBUG_PROXY_PORT & +echo $? + # RunCommands defined in tests.mobile.targets [[RunCommands]] diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index ef94fd017269ac..f84674c91edaf8 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -10,7 +10,7 @@ - $HARNESS_RUNNER wasm $XHARNESS_COMMAND --app=. --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js --output-directory=$XHARNESS_OUT -- $(RunTestsJSArguments) --run WasmTestRunner.dll $(AssemblyName).dll + $HARNESS_RUNNER wasm $XHARNESS_COMMAND --app=. --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js --output-directory=$XHARNESS_OUT $XHARNESS_ARGS -- $(RunTestsJSArguments) --run WasmTestRunner.dll $(AssemblyName).dll diff --git a/mono-config.js b/mono-config.js new file mode 100644 index 00000000000000..b83ae367c24223 --- /dev/null +++ b/mono-config.js @@ -0,0 +1,717 @@ +config = { + "assembly_root": "managed", + "debug_level": 1, + "diagnostic_tracing": true, + "assets": [ + { + "behavior": "assembly", + "name": "Microsoft.Diagnostics.Runtime.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.DotNet.RemoteExecutor.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.DotNet.XHarness.TestRunners.Common.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.DotNet.XHarness.TestRunners.Xunit.dll" + }, + { + "behavior": "assembly", + "name": "xunit.abstractions.dll" + }, + { + "behavior": "assembly", + "name": "xunit.assert.dll" + }, + { + "behavior": "assembly", + "name": "xunit.core.dll" + }, + { + "behavior": "assembly", + "name": "xunit.execution.dotnet.dll" + }, + { + "behavior": "assembly", + "name": "xunit.runner.utility.netcoreapp10.dll" + }, + + { + "behavior": "assembly", + "name": "Microsoft.DotNet.XUnitExtensions.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.TestPlatform.CoreUtilities.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.TestPlatform.PlatformAbstractions.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.VisualStudio.TestPlatform.ObjectModel.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.Win32.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "Microsoft.Win32.Registry.dll" + }, + { + "behavior": "assembly", + "name": "Mono.Options.dll" + }, + { + "behavior": "assembly", + "name": "mscorlib.dll" + }, + { + "behavior": "assembly", + "name": "netstandard.dll" + }, + { + "behavior": "assembly", + "name": "NuGet.Frameworks.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.Concurrent.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.Concurrent.Tests.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.Concurrent.Tests.pdb" + }, + { + "behavior": "assembly", + "name": "System.Collections.Immutable.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.NonGeneric.dll" + }, + { + "behavior": "assembly", + "name": "System.Collections.Specialized.dll" + }, + { + "behavior": "assembly", + "name": "System.ComponentModel.dll" + }, + { + "behavior": "assembly", + "name": "System.ComponentModel.EventBasedAsync.dll" + }, + { + "behavior": "assembly", + "name": "System.ComponentModel.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.ComponentModel.TypeConverter.dll" + }, + { + "behavior": "assembly", + "name": "System.Console.dll" + }, + { + "behavior": "assembly", + "name": "System.Data.Common.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.Contracts.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.Debug.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.DiagnosticSource.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.FileVersionInfo.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.Process.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.StackTrace.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.TextWriterTraceListener.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.TraceSource.dll" + }, + { + "behavior": "assembly", + "name": "System.Diagnostics.Tracing.dll" + }, + { + "behavior": "assembly", + "name": "System.Drawing.dll" + }, + { + "behavior": "assembly", + "name": "System.Drawing.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.Formats.Asn1.dll" + }, + { + "behavior": "assembly", + "name": "System.Globalization.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.Compression.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.Compression.Brotli.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.Compression.ZipFile.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.FileSystem.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.FileSystem.AccessControl.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.FileSystem.DriveInfo.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.FileSystem.Watcher.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.IsolatedStorage.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.MemoryMappedFiles.dll" + }, + { + "behavior": "assembly", + "name": "System.IO.Pipes.dll" + }, + { + "behavior": "assembly", + "name": "System.Linq.dll" + }, + { + "behavior": "assembly", + "name": "System.Linq.Expressions.dll" + }, + { + "behavior": "assembly", + "name": "System.Linq.Parallel.dll" + }, + { + "behavior": "assembly", + "name": "System.Linq.Queryable.dll" + }, + { + "behavior": "assembly", + "name": "System.Memory.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Http.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.HttpListener.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Mail.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.NameResolution.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.NetworkInformation.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Ping.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Quic.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Requests.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Security.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.ServicePoint.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.Sockets.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.WebClient.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.WebHeaderCollection.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.WebProxy.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.WebSockets.dll" + }, + { + "behavior": "assembly", + "name": "System.Net.WebSockets.Client.dll" + }, + { + "behavior": "assembly", + "name": "System.Numerics.Vectors.dll" + }, + { + "behavior": "assembly", + "name": "System.ObjectModel.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.CoreLib.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.DataContractSerialization.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.Runtime.InteropServices.JavaScript.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.Uri.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.Xml.dll" + }, + { + "behavior": "assembly", + "name": "System.Private.Xml.Linq.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.DispatchProxy.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Emit.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Emit.ILGeneration.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Emit.Lightweight.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Extensions.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Metadata.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.Reflection.TypeExtensions.dll" + }, + { + "behavior": "assembly", + "name": "System.Resources.ResourceManager.dll" + }, + { + "behavior": "assembly", + "name": "System.Resources.Writer.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.CompilerServices.Unsafe.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.CompilerServices.VisualC.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Extensions.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.InteropServices.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.InteropServices.RuntimeInformation.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Loader.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Numerics.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Serialization.Formatters.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Serialization.Json.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Serialization.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.Runtime.Serialization.Xml.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.AccessControl.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Claims.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Cryptography.Algorithms.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Cryptography.Csp.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Cryptography.Encoding.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Cryptography.Primitives.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Cryptography.X509Certificates.dll" + }, + { + "behavior": "assembly", + "name": "System.Security.Principal.Windows.dll" + }, + { + "behavior": "assembly", + "name": "System.Text.Encoding.dll" + }, + { + "behavior": "assembly", + "name": "System.Text.Encoding.Extensions.dll" + }, + { + "behavior": "assembly", + "name": "System.Text.RegularExpressions.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.Channels.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.Overlapped.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.Tasks.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.Tasks.Parallel.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.Thread.dll" + }, + { + "behavior": "assembly", + "name": "System.Threading.ThreadPool.dll" + }, + { + "behavior": "assembly", + "name": "System.Transactions.Local.dll" + }, + { + "behavior": "assembly", + "name": "System.Web.HttpUtility.dll" + }, + { + "behavior": "assembly", + "name": "System.Xml.ReaderWriter.dll" + }, + { + "behavior": "assembly", + "name": "System.Xml.XDocument.dll" + }, + { + "behavior": "assembly", + "name": "System.Xml.XmlSerializer.dll" + }, + { + "behavior": "assembly", + "name": "System.Xml.XPath.dll" + }, + { + "behavior": "assembly", + "name": "System.Xml.XPath.XDocument.dll" + }, + { + "behavior": "assembly", + "name": "TestUtilities.dll" + }, + { + "behavior": "assembly", + "name": "WasmTestRunner.dll" + }, + { + "behavior": "assembly", + "name": "WasmTestRunner.pdb" + }, + { + "culture": "cs", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "de", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "es", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "fr", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "it", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "ja", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "ko", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "pl", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "pt-BR", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "ru", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "tr", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "zh-Hans", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "culture": "zh-Hant", + "behavior": "resource", + "name": "Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "System.Collections.Concurrent.Tests.deps.json", + "behavior": "vfs", + "name": "supportFiles/0_System.Collections.Concurrent.Tests.deps.json" + }, + { + "virtual_path": "System.Collections.Concurrent.Tests.runtimeconfig.json", + "behavior": "vfs", + "name": "supportFiles/1_System.Collections.Concurrent.Tests.runtimeconfig.json" + }, + { + "virtual_path": "TestUtilities.dll", + "behavior": "vfs", + "name": "supportFiles/2_TestUtilities.dll" + }, + { + "virtual_path": "TestUtilities.pdb", + "behavior": "vfs", + "name": "supportFiles/3_TestUtilities.pdb" + }, + { + "virtual_path": "cs/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/4_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "de/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/5_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "es/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/6_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "fr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/7_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "it/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/8_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "ja/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/9_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "ko/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/10_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "pl/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/11_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "pt-BR/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/12_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "ru/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/13_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "tr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/14_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "zh-Hans/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/15_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "virtual_path": "zh-Hant/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "behavior": "vfs", + "name": "supportFiles/16_Microsoft.VisualStudio.TraceDataCollector.resources.dll" + }, + { + "load_remote": false, + "behavior": "icu", + "name": "icudt.dat" + }, + { + "virtual_path": "/usr/share/zoneinfo/", + "behavior": "vfs", + "name": "dotnet.timezones.blat" + } + ], + "remote_sources": [] +}; diff --git a/runtime.js b/runtime.js new file mode 100644 index 00000000000000..06df15f6817286 --- /dev/null +++ b/runtime.js @@ -0,0 +1,409 @@ +// -*- mode: js; js-indent-level: 4; -*- +// +// Run runtime tests under a JS shell or a browser +// + +//glue code to deal with the differences between chrome, ch, d8, jsc and sm. +var is_browser = typeof window != "undefined"; +var consoleWebSocket; +var print; + +if (is_browser) { + // We expect to be run by tests/runtime/run.js which passes in the arguments using http parameters + window.real_print = console.log; + print = function(_msg) { window.real_print(_msg); }; + console.log = print; + console.debug = print; + console.error = print; + console.trace = print; + console.warn = print; + console.info = print; + + //const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + + //consoleWebSocket = new WebSocket(consoleUrl); + //consoleWebSocket.onopen = function(event) { + //consoleWebSocket.send("browser: Console websocket connected."); + + //window.real_print = function(msg) { + //consoleWebSocket.send(msg); + //}; + //}; + //consoleWebSocket.onerror = function(event) { + //console.log(`websocket error: ${event}`); + //}; + + var url = new URL (decodeURI (window.location)); + arguments = []; + for (var v of url.searchParams) { + if (v [0] == "arg") { + console.log ("URL ARG: " + v [0] + "=" + v [1]); + arguments.push (v [1]); + } + } +} + +// JavaScript core does not have a console defined +if (typeof console === "undefined") { + var Console = function () { + this.log = function(msg){ print(msg) }; + this.clear = function() { }; + }; + console = new Console(); +} + +if (typeof console !== "undefined") { + if (!console.debug) + console.debug = console.log; + if (!console.trace) + console.trace = console.log; + if (!console.warn) + console.warn = console.log; + if (!console.error) + console.error = console.log; +} + +if (typeof crypto === 'undefined') { + // **NOTE** this is a simple insecure polyfill for testing purposes only + // /dev/random doesn't work on js shells, so define our own + // See library_fs.js:createDefaultDevices () + var crypto = { + getRandomValues: function (buffer) { + for (var i = 0; i < buffer.length; i++) + buffer [i] = (Math.random () * 256) | 0; + } + } +} + +if (typeof performance == 'undefined') { + // performance.now() is used by emscripten and doesn't work in JSC + var performance = { + now: function () { + return Date.now (); + } + } +} + +try { + if (typeof arguments == "undefined") + arguments = WScript.Arguments; + load = WScript.LoadScriptFile; + read = WScript.LoadBinaryFile; +} catch (e) { +} + +try { + if (typeof arguments == "undefined") { + if (typeof scriptArgs !== "undefined") + arguments = scriptArgs; + } +} catch (e) { +} + +if (arguments === undefined) + arguments = []; + +//end of all the nice shell glue code. + +// set up a global variable to be accessed in App.init +var testArguments = arguments; + +function test_exit (exit_code) { + if (is_browser) { + // Notify the selenium script + Module.exit_code = exit_code; + print ("WASM EXIT " + exit_code); + var tests_done_elem = document.createElement ("label"); + tests_done_elem.id = "tests_done"; + tests_done_elem.innerHTML = exit_code.toString (); + document.body.appendChild (tests_done_elem); + } else { + Module.wasm_exit (exit_code); + } +} + +function fail_exec (reason) { + print (reason); + test_exit (1); +} + +function inspect_object (o) { + var r = ""; + for(var p in o) { + var t = typeof o[p]; + r += "'" + p + "' => '" + t + "', "; + } + return r; +} + +// Preprocess arguments +var args = testArguments; +print("Arguments: " + testArguments); +profilers = []; +setenv = {}; +runtime_args = []; +enable_gc = true; +enable_zoneinfo = false; +working_dir='/'; +while (args !== undefined && args.length > 0) { + if (args [0].startsWith ("--profile=")) { + var arg = args [0].substring ("--profile=".length); + + profilers.push (arg); + + args = args.slice (1); + } else if (args [0].startsWith ("--setenv=")) { + var arg = args [0].substring ("--setenv=".length); + var parts = arg.split ('='); + if (parts.length != 2) + fail_exec ("Error: malformed argument: '" + args [0]); + setenv [parts [0]] = parts [1]; + args = args.slice (1); + } else if (args [0].startsWith ("--runtime-arg=")) { + var arg = args [0].substring ("--runtime-arg=".length); + runtime_args.push (arg); + args = args.slice (1); + } else if (args [0] == "--disable-on-demand-gc") { + enable_gc = false; + args = args.slice (1); + } else if (args [0].startsWith ("--working-dir=")) { + var arg = args [0].substring ("--working-dir=".length); + working_dir = arg; + args = args.slice (1); + } else { + break; + } +} +testArguments = args; + +// cheap way to let the testing infrastructure know we're running in a browser context (or not) +setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); + +function writeContentToFile(content, path) +{ + var stream = FS.open(path, 'w+'); + FS.write(stream, content, 0, content.length, 0); + FS.close(stream); +} + +function loadScript (url) +{ + if (is_browser) { + var script = document.createElement ("script"); + script.src = url; + document.head.appendChild (script); + } else { + load (url); + } +} + +loadScript ("mono-config.js"); + +var Module = { + mainScriptUrlOrBlob: "dotnet.js", + + print: print, + printErr: function(x) { print ("WASM-ERR: " + x) }, + + onAbort: function(x) { + print ("ABORT: " + x); + var err = new Error(); + print ("Stacktrace: \n"); + print (err.stack); + test_exit (1); + }, + + onRuntimeInitialized: function () { + // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. + for (var variable in setenv) { + MONO.mono_wasm_setenv (variable, setenv [variable]); + } + + if (!enable_gc) { + Module.ccall ('mono_wasm_enable_on_demand_gc', 'void', ['number'], [0]); + } + + MONO.mono_wasm_setenv ("MONO_LOG_LEVEL", "debug"); + MONO.mono_wasm_setenv ("MONO_LOG_MASK", "all"); + + config.loaded_cb = function () { + let wds = FS.stat (working_dir); + if (wds === undefined || !FS.isDir (wds.mode)) { + fail_exec (`Could not find working directory ${working_dir}`); + return; + } + + FS.chdir (working_dir); + //App.init (); + }; + config.fetch_file_cb = function (asset) { + // console.log("fetch_file_cb('" + asset + "')"); + // for testing purposes add BCL assets to VFS until we special case File.Open + // to identify when an assembly from the BCL is being open and resolve it correctly. + /* + var content = new Uint8Array (read (asset, 'binary')); + var path = asset.substr(config.deploy_prefix.length); + writeContentToFile(content, path); + */ + + if (typeof window != 'undefined') { + return fetch (asset, { credentials: 'same-origin' }); + } else { + // The default mono_load_runtime_and_bcl defaults to using + // fetch to load the assets. It also provides a way to set a + // fetch promise callback. + // Here we wrap the file read in a promise and fake a fetch response + // structure. + return new Promise ((resolve, reject) => { + var bytes = null, error = null; + try { + bytes = read (asset, 'binary'); + } catch (exc) { + error = exc; + } + var response = { ok: (bytes && !error), url: asset, + arrayBuffer: function () { + return new Promise ((resolve2, reject2) => { + if (error) + reject2 (error); + else + resolve2 (new Uint8Array (bytes)); + } + )} + } + resolve (response); + }) + } + }; + + MONO.mono_load_runtime_and_bcl_args (config); + }, +}; + +loadScript ("dotnet.js"); + +const IGNORE_PARAM_COUNT = -1; + +var App = { + init: function () { + + var assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']) + var runtime_invoke = Module.cwrap ('mono_wasm_invoke_method', 'number', ['number', 'number', 'number', 'number']); + var string_from_js = Module.cwrap ('mono_wasm_string_from_js', 'number', ['string']); + var assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']); + var string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'string', ['number']); + var string_array_new = Module.cwrap ('mono_wasm_string_array_new', 'number', ['number']); + var obj_array_set = Module.cwrap ('mono_wasm_obj_array_set', 'void', ['number', 'number', 'number']); + var wasm_set_main_args = Module.cwrap ('mono_wasm_set_main_args', 'void', ['number', 'number']); + var wasm_strdup = Module.cwrap ('mono_wasm_strdup', 'number', ['string']); + var unbox_int = Module.cwrap ('mono_unbox_int', 'number', ['number']); + + Module.wasm_exit = Module.cwrap ('mono_wasm_exit', 'void', ['number']); + + Module.print("Initializing....."); + + for (var i = 0; i < profilers.length; ++i) { + var init = Module.cwrap ('mono_wasm_load_profiler_' + profilers [i], 'void', ['string']) + + init (""); + } + + if (args.length == 0) { + fail_exec ("Missing required --run argument"); + return; + } + + if (args[0] == "--regression") { + var exec_regression = Module.cwrap ('mono_wasm_exec_regression', 'number', ['number', 'string']) + + var res = 0; + try { + res = exec_regression (10, args[1]); + Module.print ("REGRESSION RESULT: " + res); + } catch (e) { + Module.print ("ABORT: " + e); + print (e.stack); + res = 1; + } + + if (res) + fail_exec ("REGRESSION TEST FAILED"); + + return; + } + + if (runtime_args.length > 0) + MONO.mono_wasm_set_runtime_options (runtime_args); + + if (args[0] == "--run") { + // Run an exe + if (args.length == 1) { + fail_exec ("Error: Missing main executable argument."); + return; + } + + main_assembly_name = args[1]; + var app_args = string_array_new (args.length - 2); + for (var i = 2; i < args.length; ++i) { + obj_array_set (app_args, i - 2, string_from_js (args [i])); + } + + var main_argc = args.length - 2 + 1; + var main_argv = Module._malloc (main_argc * 4); + aindex = 0; + Module.setValue (main_argv + (aindex * 4), wasm_strdup (args [1]), "i32") + aindex += 1; + for (var i = 2; i < args.length; ++i) { + Module.setValue (main_argv + (aindex * 4), wasm_strdup (args [i]), "i32"); + aindex += 1; + } + wasm_set_main_args (main_argc, main_argv); + + function isThenable (js_obj) { + // When using an external Promise library the Promise.resolve may not be sufficient + // to identify the object as a Promise. + return Promise.resolve (js_obj) === js_obj || + ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") + } + + try { + // Automatic signature isn't working correctly + let exit_code = Module.mono_call_assembly_entry_point (main_assembly_name, [app_args], "m"); + + if (isThenable (exit_code)) + { + exit_code.then ( + (result) => { + test_exit (result); + }, + (reason) => { + console.error (reason); + test_exit (1); + }); + } else { + test_exit (exit_code); + return; + } + } catch (ex) { + print ("JS exception: " + ex); + print (ex.stack); + test_exit (1); + return; + } + } else { + fail_exec ("Unhandled argument: " + args [0]); + } + }, + call_test_method: function (method_name, args) { + if (arguments.length > 2) + throw new Error("Invalid number of arguments for call_test_method"); + + var fqn = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:" + method_name; + try { + return BINDING.call_static_method(fqn, args || []); + } catch (exc) { + console.error("exception thrown in", fqn); + throw exc; + } + } +}; diff --git a/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs index 46a942a0f9cde1..afabbcf789aa10 100644 --- a/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs +++ b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs @@ -11,14 +11,29 @@ public class SimpleWasmTestRunner : WasmApplicationEntryPoint { public static async Task Main(string[] args) { - var testAssembly = args[0]; + return await RunMain(args ?? new string[]{}); + } + + public static async Task RunMain(string[] args) + { + if (args == null) + { + args = new string[0]; + + } + Console.WriteLine ($"RunMain: args: {args?.Length}"); + if (args != null) + foreach (var arg in args) + Console.WriteLine ($" arg: {arg}"); + + var testAssembly = args?[0]; var excludedTraits = new List(); var includedTraits = new List(); var includedNamespaces = new List(); var includedClasses = new List(); var includedMethods = new List(); - for (int i = 1; i < args.Length; i++) + for (int i = 1; i < args?.Length; i++) { var option = args[i]; switch (option) @@ -50,7 +65,7 @@ public static async Task Main(string[] args) var runner = new SimpleWasmTestRunner() { - TestAssembly = testAssembly, + TestAssembly = testAssembly ?? String.Empty, ExcludedTraits = excludedTraits, IncludedTraits = includedTraits, IncludedNamespaces = includedNamespaces, diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 9a7cb150088104..00c6fef46191fc 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -146,7 +146,7 @@ build: EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs.pretest -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true build-all: - EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true + EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true /bl runtime: EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono.runtime+mono.wasmruntime+libs.native+libs.pretest -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Program.cs b/src/mono/wasm/debugger/BrowserDebugHost/Program.cs index 6c4a79dfeb238e..d2b5c3c91bbaf9 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Program.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Program.cs @@ -24,6 +24,16 @@ public class Program { public static void Main(string[] args) { + Uri devToolsUri = new ($"http://localhost:9222"); + if (args.Length >= 1) + devToolsUri = new Uri($"http://localhost:{args[0]}"); + + string proxyUrl = "http://127.0.0.1:0"; + if (args.Length >= 2) + proxyUrl = $"http://127.0.0.1:{args[1]}"; + + Console.WriteLine ($"Chrome devtools: {devToolsUri}"); + IWebHost host = new WebHostBuilder() .UseSetting("UseIISIntegration", false.ToString()) .UseKestrel() @@ -33,7 +43,14 @@ public static void Main(string[] args) { config.AddCommandLine(args); }) - .UseUrls("http://127.0.0.1:0") + .ConfigureServices(services => + { + services.Configure(options => + { + options.DevToolsUrl = devToolsUri; + }); + }) + .UseUrls(proxyUrl) .Build(); host.Run(); diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs index 0a28e88096272d..4119cbf750bd76 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs @@ -154,7 +154,7 @@ async Task ConnectProxy(HttpContext context) try { using ILoggerFactory loggerFactory = LoggerFactory.Create( - builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); + builder => builder.AddConsole().AddFilter(null, LogLevel.Debug)); context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList); var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList()); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 14c6dbefb1f7ed..6da6086e5c86b2 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -378,6 +378,19 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinition type) public override string ToString() => "TypeInfo('" + FullName + "')"; } + internal class CustomAssemblyResolver : DefaultAssemblyResolver + { + private ILogger _logger; + public CustomAssemblyResolver(ILogger logger) + => _logger = logger; + + public void Register(AssemblyDefinition asm_def) + { + _logger.LogDebug($"CustomAssemblyResolver:Register: {asm_def}"); + RegisterAssembly(asm_def); + } + } + internal class AssemblyInfo { private static int next_id; @@ -439,6 +452,8 @@ public AssemblyInfo(IAssemblyResolver resolver, string url, byte[] assembly, byt this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp); } + ((CustomAssemblyResolver)resolver).Register(this.image.Assembly); + Populate(); } @@ -739,7 +754,19 @@ public DebugStore(ILogger logger, HttpClient client) { this.client = client; this.logger = logger; - this.resolver = new DefaultAssemblyResolver(); + this.resolver = new CustomAssemblyResolver(logger); + ((DefaultAssemblyResolver)resolver).ResolveFailure += (_, name) => + { + logger.LogDebug($"AssemblyResolve failed: {name}"); + + foreach (var dir in ((DefaultAssemblyResolver)resolver).GetSearchDirectories()) + { + logger.LogDebug($"\tsdir: {dir}"); + foreach (var f in Directory.GetFiles(dir)) + Console.WriteLine ($"\t\tf: {f}"); + } + return null; + }; } public DebugStore(ILogger logger) : this(logger, new HttpClient()) @@ -792,6 +819,7 @@ public async IAsyncEnumerable Load(SessionId sessionId, string[] loa asm_files.Add(file_name); } + List steps = new List(); foreach (string url in asm_files) { @@ -819,11 +847,12 @@ public async IAsyncEnumerable Load(SessionId sessionId, string[] loa try { byte[][] bytes = await step.Data.ConfigureAwait(false); + logger.LogDebug($"Loading {step.Url}, pdb: {bytes[1]?.Length > 0}"); assembly = new AssemblyInfo(this.resolver, step.Url, bytes[0], bytes[1]); } catch (Exception e) { - logger.LogDebug($"Failed to load {step.Url} ({e.Message})"); + logger.LogDebug($"Failed to load {step.Url} ({e.ToString()})"); } if (assembly == null) continue; @@ -833,6 +862,7 @@ public async IAsyncEnumerable Load(SessionId sessionId, string[] loa logger.LogDebug($"Skipping loading {assembly.Name} into the debug store, as it already exists"); continue; } + logger.LogDebug($"Loaded {step.Url}, {assembly.AssemblyNameUnqualified} {assembly.Name}"); assemblies.Add(assembly); foreach (SourceFile source in assembly.Sources) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs index 197f4dd6cea5db..3c07c17465fc97 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs @@ -17,9 +17,9 @@ public class DebuggerProxy { private readonly MonoProxy proxy; - public DebuggerProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList) + public DebuggerProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList, string id = "default") { - proxy = new MonoProxy(loggerFactory, urlSymbolServerList); + proxy = new MonoProxy(loggerFactory, urlSymbolServerList, id); } public Task Run(Uri browserUri, WebSocket ideSocket) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs index 204e9b7845b8be..002219970f8a2a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs @@ -16,54 +16,10 @@ namespace Microsoft.WebAssembly.Diagnostics { - internal class DevToolsQueue - { - private Task current_send; - private List pending; - - public WebSocket Ws { get; private set; } - public Task CurrentSend { get { return current_send; } } - public DevToolsQueue(WebSocket sock) - { - this.Ws = sock; - pending = new List(); - } - - public Task Send(byte[] bytes, CancellationToken token) - { - pending.Add(bytes); - if (pending.Count == 1) - { - if (current_send != null) - throw new Exception("current_send MUST BE NULL IF THERE'S no pending send"); - //logger.LogTrace ("sending {0} bytes", bytes.Length); - current_send = Ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); - return current_send; - } - return null; - } - - public Task Pump(CancellationToken token) - { - current_send = null; - pending.RemoveAt(0); - - if (pending.Count > 0) - { - if (current_send != null) - throw new Exception("current_send MUST BE NULL IF THERE'S no pending send"); - - current_send = Ws.SendAsync(new ArraySegment(pending[0]), WebSocketMessageType.Text, true, token); - return current_send; - } - return null; - } - } - internal class DevToolsProxy { private TaskCompletionSource side_exception = new TaskCompletionSource(); - private TaskCompletionSource client_initiated_close = new TaskCompletionSource(); + private TaskCompletionSource client_initiated_close = new TaskCompletionSource(); private Dictionary> pending_cmds = new Dictionary>(); private ClientWebSocket browser; private WebSocket ide; @@ -72,10 +28,12 @@ internal class DevToolsProxy private List queues = new List(); protected readonly ILogger logger; + protected readonly string _id; - public DevToolsProxy(ILoggerFactory loggerFactory) + public DevToolsProxy(string id, ILoggerFactory loggerFactory) { - logger = loggerFactory.CreateLogger(); + _id = id; + logger = loggerFactory.CreateLogger($"{typeof(DevToolsProxy)}-{_id}"); } protected virtual Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) @@ -98,14 +56,14 @@ private async Task ReadOne(WebSocket socket, CancellationToken token) if (socket.State != WebSocketState.Open) { Log("error", $"DevToolsProxy: Socket is no longer open."); - client_initiated_close.TrySetResult(true); + client_initiated_close.TrySetResult(); return null; } WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment(buff), token); if (result.MessageType == WebSocketMessageType.Close) { - client_initiated_close.TrySetResult(true); + client_initiated_close.TrySetResult(); return null; } @@ -302,6 +260,16 @@ public async Task Run(Uri browserUri, WebSocket ideSocket) while (!x.IsCancellationRequested) { Task task = await Task.WhenAny(pending_ops.ToArray()); + + if (client_initiated_close.Task.IsCompleted) + { + await client_initiated_close.Task.ConfigureAwait(false); + Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}"); + x.Cancel(); + + break; + } + //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task)); if (task == pending_ops[0]) { @@ -326,12 +294,6 @@ public async Task Run(Uri browserUri, WebSocket ideSocket) bool res = ((Task)task).Result; throw new Exception("side task must always complete with an exception, what's going on???"); } - else if (task == pending_ops[3]) - { - bool res = ((Task)task).Result; - Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}"); - x.Cancel(); - } else { //must be a background task @@ -339,8 +301,7 @@ public async Task Run(Uri browserUri, WebSocket ideSocket) DevToolsQueue queue = GetQueueForTask(task); if (queue != null) { - Task tsk = queue.Pump(x.Token); - if (tsk != null) + if (queue.TryPumpIfCurrentCompleted(x.Token, out Task tsk)) pending_ops.Add(tsk); } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsQueue.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsQueue.cs new file mode 100644 index 00000000000000..7baa7ab847f83c --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsQueue.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class DevToolsQueue + { + private Task? current_send; + private ConcurrentQueue pending; + + public WebSocket Ws { get; private set; } + public Task? CurrentSend { get { return current_send; } } + public DevToolsQueue(WebSocket sock) + { + this.Ws = sock; + pending = new ConcurrentQueue(); + } + + public Task? Send(byte[] bytes, CancellationToken token) + { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + + pending.Enqueue(bytes); + TryPumpIfCurrentCompleted(token, out Task? sendTask); + return sendTask; + } + + public bool TryPumpIfCurrentCompleted(CancellationToken token, [NotNullWhen(true)] out Task? sendTask) + { + sendTask = null; + + if (current_send?.IsCompleted == false) + return false; + + current_send = null; + if (pending.TryDequeue(out byte[]? bytes)) + { + current_send = Ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); + sendTask = current_send; + } + + return sendTask != null; + } + } +} diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 180fb8cc7439c3..6927ec83da6a75 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -26,7 +26,7 @@ internal class MonoProxy : DevToolsProxy private HashSet sessions = new HashSet(); private Dictionary contexts = new Dictionary(); - public MonoProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList) : base(loggerFactory) + public MonoProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList, string id = "unknown") : base(id, loggerFactory) { this.urlSymbolServerList = urlSymbolServerList ?? new List(); } @@ -50,6 +50,7 @@ private bool UpdateContext(SessionId sessionId, ExecutionContext executionContex protected override async Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { + //logger.LogDebug($"AcceptEvent: {method}"); switch (method) { case "Runtime.consoleAPICalled": @@ -159,7 +160,7 @@ protected override async Task AcceptEvent(SessionId sessionId, string meth case var _ when url == "": case var _ when url.StartsWith("wasm://", StringComparison.Ordinal): { - Log("verbose", $"ignoring wasm: Debugger.scriptParsed {url}"); + Log("verbose", $"ignoring wasm: Debugger.scriptParsed url: {url}"); return true; } } @@ -195,6 +196,7 @@ private async Task IsRuntimeAlreadyReadyAlready(SessionId sessionId, Cance protected override async Task AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) { + //logger.LogDebug($"AcceptCommand {method}"); // Inspector doesn't use the Target domain or sessions // so we try to init immediately if (id == SessionId.Null) @@ -390,7 +392,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J } case "DotnetDebugger.getMethodLocation": { - Console.WriteLine("set-breakpoint-by-method: " + id + " " + args); + logger.LogDebug($"set-breakpoint-by-method: {id} {args}"); DebugStore store = await RuntimeReady(id, token); string aname = args["assemblyName"]?.Value(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index 6e5e4a948dd41c..2a24d5648d1e46 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -202,77 +202,68 @@ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, st string local_var_name_prefix, object[] array, object[] array_elem_props, bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - Assert.Equal(4, locals.Count()); - CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0); - CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0); - CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); - CheckBool(locals, "call_other", test_prev_frame); + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Equal(4, locals.Count()); + CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0); + CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0); + CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); + CheckBool(locals, "call_other", test_prev_frame); - var local_arr_name = $"{local_var_name_prefix}_arr"; + var local_arr_name = $"{local_var_name_prefix}_arr"; - JToken prefix_arr; - if (use_cfo) - { // Use `Runtime.callFunctionOn` to get the properties - var frame = pause_location["callFrames"][frame_idx]; - var name = local_arr_name; - var fl = await GetProperties(frame["callFrameId"].Value()); - var l_obj = GetAndAssertObjectWithName(locals, name); - var l_objectId = l_obj["value"]["objectId"]?.Value(); + JToken prefix_arr; + if (use_cfo) + { // Use `Runtime.callFunctionOn` to get the properties + var frame = pause_location["callFrames"][frame_idx]; + var name = local_arr_name; + var fl = await GetProperties(frame["callFrameId"].Value()); + var l_obj = GetAndAssertObjectWithName(locals, name); + var l_objectId = l_obj["value"]["objectId"]?.Value(); - Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}"); + Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}"); - prefix_arr = await GetObjectWithCFO(l_objectId); - } - else - { - prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name); - } + prefix_arr = await GetObjectWithCFO(l_objectId); + } + else + { + prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name); + } - await CheckProps(prefix_arr, array, local_arr_name); + await CheckProps(prefix_arr, array, local_arr_name); - if (array_elem_props?.Length > 0) + if (array_elem_props?.Length > 0) + { + for (int i = 0; i < array_elem_props.Length; i++) { - for (int i = 0; i < array_elem_props.Length; i++) + var i_str = i.ToString(); + var label = $"{local_var_name_prefix}_arr[{i}]"; + if (array_elem_props[i] == null) { - var i_str = i.ToString(); - var label = $"{local_var_name_prefix}_arr[{i}]"; - if (array_elem_props[i] == null) - { - var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value() == i_str); - Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); + var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value() == i_str); + Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); - await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label); - } - else - { - await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label); - } + await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label); + } + else + { + await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label); } } + } - var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); - await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty"); - }); + var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); + await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty"); async Task GetObjectWithCFO(string objectId, JObject fn_args = null) { @@ -287,7 +278,7 @@ async Task GetObjectWithCFO(string objectId, JObject fn_args = null) cfo_args["arguments"] = fn_args; // callFunctionOn - var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + var result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); return await GetProperties(result.Value["result"]["objectId"]?.Value(), fn_args); } @@ -298,79 +289,71 @@ async Task GetObjectWithCFO(string objectId, JObject fn_args = null) [InlineData(true)] public async Task InspectObjectArrayMembers(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 227; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers"; string method_name = "PlaceholderMethod"; int frame_idx = 1; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - Assert.Single(locals); - CheckObject(locals, "c", "DebuggerTests.Container"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Single(locals); + CheckObject(locals, "c", "DebuggerTests.Container"); - var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c"); - await CheckProps(c_props, new - { - id = TString("c#id"), - ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3), - ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3), - PointsProperty = TArray("DebuggerTests.Point[]", 2), - PointsField = TArray("DebuggerTests.Point[]", 2) - }, - "c" - ); + var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c"); + await CheckProps(c_props, new + { + id = TString("c#id"), + ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3), + ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3), + PointsProperty = TArray("DebuggerTests.Point[]", 2), + PointsField = TArray("DebuggerTests.Point[]", 2) + }, + "c" + ); - await CompareObjectPropertiesFor(c_props, "ClassArrayProperty", - new[] - { + await CompareObjectPropertiesFor(c_props, "ClassArrayProperty", + new[] + { TSimpleClass(5, -2, "ClassArrayProperty#Id#0", "Green"), TSimpleClass(30, 1293, "ClassArrayProperty#Id#1", "Green"), TObject("DebuggerTests.SimpleClass", is_null : true) - }, - label: "InspectLocalsWithStructsStaticAsync"); + }, + label: "InspectLocalsWithStructsStaticAsync"); - await CompareObjectPropertiesFor(c_props, "ClassArrayField", - new[] - { + await CompareObjectPropertiesFor(c_props, "ClassArrayField", + new[] + { TObject("DebuggerTests.SimpleClass", is_null : true), TSimpleClass(5, -2, "ClassArrayField#Id#1", "Blue"), TSimpleClass(30, 1293, "ClassArrayField#Id#2", "Green") - }, - label: "c#ClassArrayField"); + }, + label: "c#ClassArrayField"); - await CompareObjectPropertiesFor(c_props, "PointsProperty", - new[] - { + await CompareObjectPropertiesFor(c_props, "PointsProperty", + new[] + { TPoint(5, -2, "PointsProperty#Id#0", "Green"), TPoint(123, 0, "PointsProperty#Id#1", "Blue"), - }, - label: "c#PointsProperty"); + }, + label: "c#PointsProperty"); - await CompareObjectPropertiesFor(c_props, "PointsField", - new[] - { + await CompareObjectPropertiesFor(c_props, "PointsField", + new[] + { TPoint(5, -2, "PointsField#Id#0", "Green"), TPoint(123, 0, "PointsField#Id#1", "Blue"), - }, - label: "c#PointsField"); - }); + }, + label: "c#PointsField"); } [Theory] @@ -378,53 +361,46 @@ await CompareObjectPropertiesFor(c_props, "PointsField", [InlineData(true)] public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 157; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', false" // *false* here keeps us only in the static method - + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" // *false* here keeps us only in the static method + + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new - { - call_other = TBool(false), - gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), - gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), - gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), - gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), - // BUG: this shouldn't be null! - points = TObject("DebuggerTests.Point[]", is_null: true) - }, "ValueTypeLocalsAsync#locals"); - - var local_var_name_prefix = "gvclass"; - await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new - { - Id = TString(null), - Color = TEnum("DebuggerTests.RGB", "Red"), - Value = TPoint(0, 0, null, "Red") - }); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), + gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), + gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), + gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), + // BUG: this shouldn't be null! + points = TObject("DebuggerTests.Point[]", is_null: true) + }, "ValueTypeLocalsAsync#locals"); + + var local_var_name_prefix = "gvclass"; + await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new + { + Id = TString(null), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(0, 0, null, "Red") + }); - await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", - new[] - { + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", + new[] + { new { Id = TString("gvclass_arr#1#Id"), @@ -437,11 +413,10 @@ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", Color = TEnum("DebuggerTests.RGB", "Blue"), Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green") } - } - ); - await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty", - new object[0]); - }); + } + ); + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty", + new object[0]); } // TODO: Check previous frame too @@ -450,58 +425,51 @@ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_emp [InlineData(true)] public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo) { - var insp = new Inspector(); //Collect events - var scripts = SubscribeToScripts(insp); int line = 170; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', true" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + t1 = TObject("DebuggerTests.SimpleGenericStruct"), + @this = TObject("DebuggerTests.ArrayTestsClass"), + point_arr = TArray("DebuggerTests.Point[]", 2), + point = TValueType("DebuggerTests.Point") + }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); + + await CompareObjectPropertiesFor(frame_locals, "t1", + new { - t1 = TObject("DebuggerTests.SimpleGenericStruct"), - @this = TObject("DebuggerTests.ArrayTestsClass"), - point_arr = TArray("DebuggerTests.Point[]", 2), - point = TValueType("DebuggerTests.Point") - }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); - - await CompareObjectPropertiesFor(frame_locals, "t1", - new - { - Id = TString("gvclass_arr#1#Id"), - Color = TEnum("DebuggerTests.RGB", "Red"), - Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") - }); + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") + }); - await CompareObjectPropertiesFor(frame_locals, "point_arr", - new[] - { + await CompareObjectPropertiesFor(frame_locals, "point_arr", + new[] + { TPoint(5, -2, "point_arr#Id#0", "Red"), TPoint(123, 0, "point_arr#Id#1", "Blue"), - } - ); + } + ); - await CompareObjectPropertiesFor(frame_locals, "point", - TPoint(45, 51, "point#Id", "Green")); - }); + await CompareObjectPropertiesFor(frame_locals, "point", + TPoint(45, 51, "point#Id", "Green")); } [Theory] @@ -509,39 +477,31 @@ await CompareObjectPropertiesFor(frame_locals, "point", [InlineData(true)] public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 244; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); - //await SetBreakpoint (debugger_test_loc, 143, 3); + await SetBreakpoint(debugger_test_loc, line, col); + //await SetBreakpoint (debugger_test_loc, 143, 3); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', false" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new - { - call_other = TBool(false), - local_i = TNumber(5), - sc = TSimpleClass(10, 45, "sc#Id", "Blue") - }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); - }); + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + local_i = TNumber(5), + sc = TSimpleClass(10, 45, "sc#Id", "Blue") + }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); } [Theory] @@ -549,56 +509,48 @@ await insp.Ready(async (cli, token) => [InlineData(true)] public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 251; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', true" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "locals#0"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + await CompareObjectPropertiesFor(frame_locals, "local_gs", + new { - sc_arg = TObject("DebuggerTests.SimpleClass"), - @this = TValueType("DebuggerTests.Point"), - local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + Id = TString("local_gs#Id"), + Color = TEnum("DebuggerTests.RGB", "Green"), + Value = TNumber(4) }, - "locals#0"); + label: "local_gs#0"); - await CompareObjectPropertiesFor(frame_locals, "local_gs", - new - { - Id = TString("local_gs#Id"), - Color = TEnum("DebuggerTests.RGB", "Green"), - Value = TNumber(4) - }, - label: "local_gs#0"); - - await CompareObjectPropertiesFor(frame_locals, "sc_arg", - TSimpleClass(10, 45, "sc_arg#Id", "Blue"), - label: "sc_arg#0"); - - await CompareObjectPropertiesFor(frame_locals, "this", - TPoint(90, -4, "point#Id", "Green"), - label: "this#0"); - }); + await CompareObjectPropertiesFor(frame_locals, "sc_arg", + TSimpleClass(10, 45, "sc_arg#Id", "Blue"), + label: "sc_arg#0"); + + await CompareObjectPropertiesFor(frame_locals, "this", + TPoint(90, -4, "point#Id", "Green"), + label: "this#0"); } [Fact] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs new file mode 100644 index 00000000000000..e7424c85c98828 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; +using Xunit; + +#nullable enable + +namespace DebuggerTests +{ + public class BadHarnessInitTests : DebuggerTestBase + { + public override async Task InitializeAsync() => await Task.CompletedTask; + + [Fact] + public async Task InvalidInitCommands() + { + var bad_cmd_name = "non-existant.command"; + + Func)>> fn = (session, token) => + new List<(string, Task)> + { + ("Profiler.enable", session.SendCommand("Profiler.enable", null, token)), + (bad_cmd_name, session.SendCommand(bad_cmd_name, null, token)) + }; + + await Ready(); + + var ae = await Assert.ThrowsAsync(async () => await insp.OpenSessionAsync("/debugger-driver.html", fn)); + Assert.Contains(bad_cmd_name, ae.Message); + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BrowserCdpConnection.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserCdpConnection.cs new file mode 100644 index 00000000000000..cb4a69a3a328cc --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserCdpConnection.cs @@ -0,0 +1,236 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BrowserCdpConnection : IAsyncDisposable, IDisposable + { + public ConcurrentDictionary Targets = new (); + public ConcurrentDictionary Sessions = new (); + + public InspectorClient InspectorClient { get; set; } + public ILogger Logger { get; private set; } + + private CancellationTokenSource _cts; + + private const int ConcurrentChromeConnections = 6; + private static SemaphoreSlim s_semaphore = new (1); + private static SemaphoreSlim s_connectionSemaphore = new (ConcurrentChromeConnections); + private static MethodInfo? threadpoolPump; + private static MethodInfo? timerPump; + + private BrowserCdpConnection(InspectorClient client, ILogger logger, CancellationTokenSource cts) + { + Logger = logger; + _cts = cts; + InspectorClient = client; + InspectorClient.RunLoopStopped += (_, _) => Dispose(); + } + + public static async Task Open(int browser_id, string testId, ILogger logger, CancellationTokenSource cts) + { + await s_connectionSemaphore.WaitAsync(); + // try + // { + var client = new InspectorClient(testId, logger); + var connection = new BrowserCdpConnection(client, logger, cts); + + // FIXME: use a per-test logger here + await client.Connect( + new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/connect-to-devtools/{browser_id}?testId={testId}"), + connection.MessageFallbackHandler, + cts.Token); + + return connection; + // } + // finally + // { + // s_connectionSemaphore.Release(); + // } + } + + public void RemoveSession(SessionId sessionId) + { + Sessions.Remove(sessionId.sessionId, out _); + InspectorClient.RemoveMessageHandlerForSession(sessionId); + } + + private async Task MessageFallbackHandler(JObject msg, CancellationToken token) + { + Logger.LogDebug($"Fallback: {msg}"); + // string? method = msg["method"]?.Value(); + // JObject? args = msg["params"]?.Value(); + // if (method == null) + // return; + + // switch (method) + // { + // case "Target.detachedFromTarget": + // { + // // this has sessionId + // // string? sessionId = args?["sessionId"]?.Value(); + // // // FIXME: remove the target, and the session message handler + // // // Console.WriteLine ($"\tsessionId: {sessionId}"); + // // if (sessionId != null && Sessions.TryGetValue(sessionId, out var session)) + // // { + // // // await session.DisposeAsync(); + // // } + // break; + // } + + // targetDestroyed, .. + // } + + await Task.CompletedTask; + } + + public async Task SendCommandNoSession(string method, JObject? args, CancellationToken token) + => await InspectorClient.SendCommand(method, args, token); + + public async Task SendCommand(SessionId sessionId, string method, JObject? args, CancellationToken token) + => await InspectorClient.SendCommand(sessionId, method, args, token); + + public async Task OpenSession( + string url, Func onMessage, + ILogger logger, string id, CancellationTokenSource cts, BrowserTarget? target=null) + { + Result res; + if (target == null) + { + res = await SendCommandNoSession("Target.createBrowserContext", JObject.FromObject(new + { + disposeOnDetach = true + }), cts.Token); + if (!res.IsOk) + throw new Exception($"Target.createBrowserContext failed with {res}"); + + string browserContextId = res.Value["browserContextId"]?.Value() ?? throw new Exception($"Missing browserContextId in {res}"); + + res = await SendCommandNoSession("Target.createTarget", JObject.FromObject(new + { + url, + browserContextId + }), cts.Token); + if (!res.IsOk) + throw new Exception($"Target.createTarget failed with {res}"); + + var targetId = res.Value["targetId"]?.Value(); + if (string.IsNullOrEmpty(targetId)) + throw new Exception($"Target.createTarget missing a targetId: {res}"); + + res = await SendCommandNoSession("Target.activateTarget", res.Value, cts.Token); + target = new BrowserTarget(browserContextId, targetId, url, this); + Targets[target.Id] = target; + } + + res = await SendCommandNoSession("Target.attachToTarget", JObject.FromObject(new + { + targetId = target.Id, + flatten = true + }), cts.Token); + // Console.WriteLine($"----> attachToTarget: {res}"); + if (res.IsErr) + throw new Exception($"Target.attachToTarget failed with {res}"); + + // TODO: um.. do this in response to attachedToTarget + if (string.IsNullOrEmpty(res.Value["sessionId"]?.Value())) + throw new Exception($"Target.attachToTarget didn't return any sessionId. {res}"); + + var sessionId = new SessionId(res.Value["sessionId"]?.Value()); + InspectorClient.AddMessageHandlerForSession(sessionId, onMessage); + var session = new BrowserSession(sessionId, target); + Sessions[sessionId.sessionId] = session; + + return session; + } + + public void Dispose() => CloseAsync().Wait(); + public async Task CloseAsync() => await DisposeAsync(); + + // bool _disposed = false; + bool _disposing = false; + bool _disposed = false; + public async ValueTask DisposeAsync() + { + // if (_disposed) + // throw new ObjectDisposedException(nameof(BrowserTarget)); + + if (_disposing || _disposed) + return; + + Logger.LogDebug($"--------------------- CdpConnection.DisposeAsync, _disposing: {_disposing}, InspectorClient.IsRunning: {InspectorClient.IsRunning} ---------------"); + _disposing = true; + // Console.WriteLine (Environment.StackTrace); + // close this + // this might not actually work.. eg. if devtoolsclient is no longer running!! + if (InspectorClient.IsRunning) + { + foreach (var session in Sessions.Values) + await session.DisposeAsync(); + + Sessions.Clear(); + + foreach (var target in Targets.Values) + await target.DisposeAsync(); + + Targets.Clear(); + //FIXME: + // Result res = await InspectorClient.SendCommand("Target.closeTarget", JObject.FromObject(new + // { + // targetId = Id + // }), new CancellationToken(false)); + // _logger.LogDebug($"-- BrowserTarget.DisposeAsync: closeTarget sent, result: {res}"); + + // FIXME: um if closeTarget failed, then also we need to stop the client, and close the socket! + // if (!res.IsOk) + // throw new Exception("Failed to close target {TargetId}"); + } + + var res = await InspectorClient.SendCommand("Target.getTargets", null, new CancellationToken()); + Logger.LogDebug("getTargets returned {res}"); + res = await InspectorClient.SendCommand("Target.getBrowserContexts", null, new CancellationToken()); + Logger.LogDebug("getBrowserContextx returned {res}"); + + // await InspectorClient.DisposeAsync(); + InspectorClient.Dispose(); + + await s_semaphore.WaitAsync(); + try + { + if (threadpoolPump == null || timerPump == null) + { + threadpoolPump = typeof(ThreadPool).GetMethod("PumpThreadPool", BindingFlags.NonPublic | BindingFlags.Static); + timerPump = Type.GetType("System.Threading.TimerQueue")?.GetMethod("PumpTimerQueue", BindingFlags.NonPublic | BindingFlags.Static); + } + + Logger.LogDebug($"CdpConnection.DisposeAsync: TP PendingWorkItemCount: {ThreadPool.PendingWorkItemCount}\n"); + threadpoolPump?.Invoke(this, null); + timerPump?.Invoke(this, null); + Logger.LogDebug($"CdpConnection.DisposeAsync AFTER: TP PendingWorkItemCount: {ThreadPool.PendingWorkItemCount}\n"); + } finally { + s_semaphore.Release(); + } + + s_connectionSemaphore.Release(); + + Logger.LogDebug($"-- BrowserTarget.DisposeAsync: done"); + _disposing = false; + _disposed = true; + // s_semaphore.Release(); + + await Task.CompletedTask; + } + } + +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BrowserInstance.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserInstance.cs new file mode 100644 index 00000000000000..932d11cc9de737 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserInstance.cs @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BrowserInstance : IAsyncDisposable + { + static int s_nextId = 0; + + private Uri _remoteConnectionUri; + public int Id { get; private set; } + + private Process _process; + private ILogger _poolLogger; + + // private ConcurrentDictionary targets = new ConcurrentDictionary(); + public bool HasExited => _process.HasExited; + + public CancellationToken CancellationToken => _cancellationTokenSource.Token; + private CancellationTokenSource _cancellationTokenSource; + + private BrowserInstance(Uri remoteConnectionUri, Process process, ILogger logger) + { + _remoteConnectionUri = remoteConnectionUri; + _process = process; + _poolLogger = logger; + _cancellationTokenSource = new CancellationTokenSource(); + + Id = Interlocked.Increment(ref s_nextId); + + _poolLogger.LogDebug($"Adding [{Id}] = {remoteConnectionUri}"); + TestHarnessProxy.LauncherData.IdToDevToolsUrl[Id.ToString()] = remoteConnectionUri; + } + + private async Task OpenConnection(string testId, ILogger testLogger) + { + return await BrowserCdpConnection.Open(Id, testId, testLogger, _cancellationTokenSource); + } + + public static async Task StartAsync(ILogger logger, CancellationToken token, int port=0) + { + (var _process, var uri) = await LaunchChrome(new [] + { + "--headless", + "--no-first-run", + "--disable-gpu", + "--lang=en-US", + "--incognito", + $"--remote-debugging-port={port}", + "--user-data-dir=/tmp/asd", + "--enable" + + }, logger); + + return new BrowserInstance(uri, _process, logger); + } + + public async Task OpenSession(string relativeUrl, + Func onMessage, + Action<(RunLoopStopReason reason, Exception? ex)> onRunLoopStopped, + string testId, + ILogger logger, + CancellationTokenSource cts, + BrowserCdpConnection? connection=null, + BrowserTarget? target=null) + { + + if (connection == null) + { + connection = await OpenConnection(testId, logger); + connection.InspectorClient!.RunLoopStopped += (_, args) => onRunLoopStopped(args); + } + + var session = await connection.OpenSession(//this, + $"{TestHarnessProxy.Endpoint.Scheme}://{TestHarnessProxy.Endpoint.Authority}{relativeUrl}", + onMessage, + logger, + testId, + cts, + target); + + return session; + } + + static async Task<(Process, Uri)> LaunchChrome(string[] args, ILogger logger) + { + var psi = new ProcessStartInfo + { + Arguments = string.Join(' ', args), + UseShellExecute = false, + FileName = FindChromePath(), + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + var tcs = new TaskCompletionSource(); + + logger.LogDebug($"Launching browser with '{psi.FileName}' args: '{psi.Arguments}'"); + var process = Process.Start(psi); + try + { + process!.ErrorDataReceived += (sender, e) => + { + string? str = e.Data; + logger.LogTrace($"browser-stderr: {str}"); + + if (tcs.Task.IsCompleted) + return; + + var match = parseConnection.Match(str!); + if (match.Success) + { + tcs.TrySetResult(match.Groups[1].Captures[0].Value); + } + }; + + process.OutputDataReceived += (sender, e) => + { + logger.LogTrace($"browser-stdout: {e.Data}"); + }; + + process.Exited += (_, e) => logger.LogDebug($"browser exited with {process.ExitCode}"); + + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + + if (await Task.WhenAny(tcs.Task, Task.Delay(5000)) != tcs.Task) + { + logger.LogError("Didnt get the con string after 5s."); + + process.CancelErrorRead(); + process.CancelOutputRead(); + process.Kill(); + process.WaitForExit(); + process.Close(); + + + throw new Exception("Didn't get the remote connection string after 5s"); + } + + var line = await tcs.Task; + logger.LogDebug($"Chrome devtools listening on {line}"); + + return (process, new Uri(line)); + } + catch (Exception e) + { + logger.LogError("got exception {0}", e); + throw; + } + } + + // public void Dispose() + // { + // } + + private bool _disposing = false; + private bool _disposed = false; + public async ValueTask DisposeAsync() + { + if (_disposed || _disposing) + return; + + _disposing = true; + //FIXME: um..this Id type, and the type in the dict need to match! + TestHarnessProxy.LauncherData.IdToDevToolsUrl.Remove(Id.ToString()); + await Task.CompletedTask; + // var tasks = new List(); + // foreach (var tgt in targets.Values) + // await tgt.DisposeAsync(); + // tasks.Add(tgt.DisposeAsync()); + + // await Task.WhenAll(tasks); + _disposing = false; + _disposed = true; + } + + static string[] PROBE_LIST = { + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + }; + + static string? chrome_path; + + static string FindChromePath() + { + if (chrome_path != null) + return chrome_path; + + foreach (var s in PROBE_LIST) + { + if (File.Exists(s)) + { + chrome_path = s; + // Console.WriteLine($"Using chrome path: ${s}"); + return s; + } + } + throw new Exception("Could not find an installed Chrome to use"); + } + + static Regex parseConnection = new Regex(@"listening on (ws?s://[^\s]*)"); + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BrowserPool.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserPool.cs new file mode 100644 index 00000000000000..2e99c7845967b9 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserPool.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BrowserPool + { + static BrowserInstance? s_browserInstance; + static SemaphoreSlim s_myLock = new SemaphoreSlim(1); + static Task? s_proxyTask; + + public static async Task GetInstanceAsync(ILogger logger, string debuggerTestPath, CancellationToken token) + { + if (s_browserInstance != null && !s_browserInstance.HasExited) + return s_browserInstance; + + //FIXME: um use some better way to do this.. we want to init just once + await s_myLock.WaitAsync(); + try { + if (s_browserInstance?.HasExited == true) + { + logger.LogError($"Chrome has crashed, cleaning up"); + await s_browserInstance.DisposeAsync(); + s_browserInstance = null; + } + + if (s_proxyTask == null) + s_proxyTask = TestHarnessProxy.Start(debuggerTestPath, logger, token); + + if (s_browserInstance == null) + { + Task browserTask = BrowserInstance.StartAsync(logger, token); + + await Task.WhenAll(s_proxyTask, browserTask); + if (s_proxyTask.IsCompletedSuccessfully && browserTask.IsCompletedSuccessfully) + { + s_browserInstance = await browserTask; + } + else + { + // -- umm.. throw!! + logger.LogError($"----- EEERRRRRRRRRRR.. proxy or browser launch failed"); + throw new Exception("proxy or brwoser failed"); + } + } + } finally { + s_myLock.Release(); + } + + return s_browserInstance; + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BrowserSession.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserSession.cs new file mode 100644 index 00000000000000..8e8f766ef73509 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserSession.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BrowserSession : IAsyncDisposable + { + public SessionId Id { get; private set; } + public BrowserTarget Target { get; private set; } + public BrowserCdpConnection Connection => Target.Connection; + public DateTime StartTime { get; } = DateTime.Now; + + private bool _disposed = false; + //fIXME: disposing + + public BrowserSession(SessionId id, BrowserTarget target) + { + Id = id; + Target = target; + } + + public async Task SendCommand(string method, JObject? args, CancellationToken token) + => await Connection.SendCommand(Id, method, args, token); + + public async Task ShutdownConnection(CancellationToken token) + => await Connection.DisposeAsync(); + + public async ValueTask DisposeAsync() + { + if (_disposed) + return; + + // FIXME: remove this from Connection + + await Connection.SendCommandNoSession("Target.detachFromTarget", JObject.FromObject(new { sessionId = Id.sessionId }), new CancellationToken()); + Connection.RemoveSession(Id); + + //FIXME: check error + _disposed = true; + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BrowserTarget.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserTarget.cs new file mode 100644 index 00000000000000..74e1a35cc55b1d --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BrowserTarget.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; + +#nullable enable + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class BrowserTarget : IAsyncDisposable + { + public string BrowserContextId { get; private set; } + public string Id { get; private set; } + public string Url { get; private set; } + public BrowserCdpConnection Connection { get; private set; } + private bool _disposed = false; + + public BrowserTarget(string browserContextId, string id, string url, BrowserCdpConnection connection) + { + BrowserContextId = browserContextId; + Id = id; + Url = url; + Connection = connection; + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + return; + + // FIXME: remove this from Connection + // var res = await Connection.SendCommand( + // "Target.closeTarget", + // JObject.FromObject(new { targetId = Id }), + // new CancellationToken()); + //FIXME: and disposeBrowserContext? + + // if (res.IsErr) + // Connection.Logger.LogError($"Target.closeTarget failed with {res}"); + _disposed = true; + await Task.CompletedTask; + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs index 09094cea2212ff..01a342c36fcef7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs @@ -30,12 +30,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array { var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else @@ -43,12 +43,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array // Check for a __proto__ object // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); await CheckProps(obj_own.Value["result"], new { @@ -90,12 +90,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col, var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); // isOwn = false, accessorPropertiesOnly = true - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else @@ -104,12 +104,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col, // Ignoring the __proto__ property // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; var num_elems_recd = len == 0 ? 0 : num_elems_fetch; @@ -142,24 +142,24 @@ await RunCallFunctionOn(eval_fn, var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); // getProperties (isOwn = false, accessorPropertiesOnly = true) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); // getProperties (isOwn = true, accessorPropertiesOnly = false) - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); await CheckProps(obj_own.Value["result"], new { @@ -188,12 +188,12 @@ await RunCallFunctionOn(eval_fn, var is_js = bp_loc.EndsWith(".js"); // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); @@ -202,12 +202,12 @@ await RunCallFunctionOn(eval_fn, // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count"); @@ -239,23 +239,23 @@ public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn( var ret_len = 5; // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; await CheckProps(obj_own_val, new @@ -299,22 +299,22 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti { // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; var dt = new DateTime(2020, 1, 2, 3, 4, 5); @@ -345,23 +345,23 @@ public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn( { // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; await CheckProps(obj_own_val, new @@ -442,74 +442,65 @@ public async Task RunOnArrayReturnArrayByValue(string eval_fn, string bp_loc, in [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] public async Task RunOnArrayReturnPrimitive(string eval_fn, string bp_loc, int line, int col, bool return_by_val) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetBreakpoint(bp_loc, line, col); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); - - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, scripts, pause_location ["callFrames"][0]["location"]); - // Check the object at the bp - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, "big"); - var obj_id = obj["value"]["objectId"].Value(); + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var obj_id = obj["value"]["objectId"].Value(); - var cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 5; }", - objectId = obj_id - }); + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 5; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], TNumber(5), "cfo-res"); + // callFunctionOn + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], TNumber(5), "cfo-res"); - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 'test value'; }", - objectId = obj_id - }); + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 'test value'; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res"); + // callFunctionOn + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res"); - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return null; }", - objectId = obj_id - }); + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return null; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res"); - }); + // callFunctionOn + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res"); } public static TheoryData SilentErrorsTestData(bool? silent) => new TheoryData @@ -523,45 +514,36 @@ await insp.Ready(async (cli, token) => [MemberData(nameof(SilentErrorsTestData), true)] public async Task CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int line, int col, bool? silent) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + await SetBreakpoint(bp_loc, line, col); - // callFunctionOn - var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, "big"); - var big_obj_id = obj["value"]["objectId"].Value(); - var error_msg = "#This is an error message#"; + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var big_obj_id = obj["value"]["objectId"].Value(); + var error_msg = "#This is an error message#"; - // Check the object at the bp - var cfo_args = JObject.FromObject(new - { - functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", - objectId = big_obj_id - }); + // Check the object at the bp + var cfo_args = JObject.FromObject(new + { + functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", + objectId = big_obj_id + }); - if (silent.HasValue) - cfo_args["silent"] = silent; + if (silent.HasValue) + cfo_args["silent"] = silent; - // callFunctionOn, Silent does not change the result, except that the error - // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - Assert.False(result.IsOk, "result.IsOk"); - Assert.True(result.IsErr, "result.IsErr"); + // callFunctionOn, Silent does not change the result, except that the error + // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + Assert.False(result.IsOk, "result.IsOk"); + Assert.True(result.IsErr, "result.IsErr"); - var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value()?.Contains(error_msg); - Assert.True((hasErrorMessage ?? false), "Exception message not found"); - }); + var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value()?.Contains(error_msg); + Assert.True((hasErrorMessage ?? false), "Exception message not found"); } public static TheoryData, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData, string, bool> @@ -771,7 +753,7 @@ await RunCallFunctionOn( async Task GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_fields) { - var res = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + var res = await SendCommand("Runtime.getProperties", get_prop_req, token); if (!res.IsOk) Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString()}, with Result: {res}"); @@ -808,7 +790,7 @@ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int objectId = ptd_id + "_invalid" }); - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + var res = await SendCommand("Runtime.callFunctionOn", cfo_args, token); Assert.True(res.IsErr); }); @@ -816,35 +798,26 @@ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int [MemberData(nameof(NegativeTestsData), false)] public async Task RunOnInvalidThirdSegmentOfObjectId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - await SetBreakpoint(bp_loc, line, col); + UseCallFunctionOnBeforeGetProperties = use_cfo; + await SetBreakpoint(bp_loc, line, col); - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); - var ptd_id = ptd["value"]["objectId"].Value(); - - var cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 0; }", - objectId = ptd_id + "_invalid" - }); + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - Assert.True(res.IsErr); + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 0; }", + objectId = ptd_id + "_invalid" }); + + var res = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + Assert.True(res.IsErr); } [Theory] @@ -852,33 +825,24 @@ await insp.Ready(async (cli, token) => [MemberData(nameof(NegativeTestsData), true)] public async Task InvalidPropertyGetters(string eval_fn, string bp_loc, int line, int col, bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + await SetBreakpoint(bp_loc, line, col); + UseCallFunctionOnBeforeGetProperties = use_cfo; - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr })); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr })); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); - var ptd_id = ptd["value"]["objectId"].Value(); + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); - var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 }; - foreach (var invalid_arg in invalid_args) - { - var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg); - AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}"); - } - }); + var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 }; + foreach (var invalid_arg in invalid_args) + { + var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg); + AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}"); + } } [Theory] @@ -922,81 +886,72 @@ public async Task ReturnNullFromCFO(string eval_fn, string bp_loc, int line, int async Task RunCallFunctionOn(string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1, Func test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); + + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, scripts, pause_location ["callFrames"][0]["location"]); + + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, local_name); + var obj_id = obj["value"]["objectId"].Value(); - await Ready(); - await insp.Ready(async (cli, token) => + var cfo_args = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + functionDeclaration = fn_decl, + objectId = obj_id + }); - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + if (fn_args != null) + cfo_args["arguments"] = fn_args; - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + if (returnByValue) + cfo_args["returnByValue"] = returnByValue; - // Check the object at the bp - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, local_name); - var obj_id = obj["value"]["objectId"].Value(); + // callFunctionOn + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckCFOResult(result); - var cfo_args = JObject.FromObject(new + // If it wasn't `returnByValue`, then try to run a new function + // on that *returned* object + // This second function, just returns the object as-is, so the same + // test_fn is re-usable. + if (!returnByValue && roundtrip) + { + cfo_args = JObject.FromObject(new { - functionDeclaration = fn_decl, - objectId = obj_id + functionDeclaration = "function () { return this; }", + objectId = result.Value["result"]["objectId"]?.Value() }); if (fn_args != null) cfo_args["arguments"] = fn_args; - if (returnByValue) - cfo_args["returnByValue"] = returnByValue; + result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); await CheckCFOResult(result); + } - // If it wasn't `returnByValue`, then try to run a new function - // on that *returned* object - // This second function, just returns the object as-is, so the same - // test_fn is re-usable. - if (!returnByValue && roundtrip) - { - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return this; }", - objectId = result.Value["result"]["objectId"]?.Value() - }); - - if (fn_args != null) - cfo_args["arguments"] = fn_args; - - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - - await CheckCFOResult(result); - } - - if (test_fn != null) - await test_fn(result); + if (test_fn != null) + await test_fn(result); - return; + return; - async Task CheckCFOResult(Result result) - { - if (returnByValue) - return; + async Task CheckCFOResult(Result result) + { + if (returnByValue) + return; - if (res_array_len < 0) - await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res"); - else - await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res"); - } - }); + if (res_array_len < 0) + await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res"); + else + await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res"); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs index 40a156c4f31823..2d3707fec1d8d8 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs @@ -18,39 +18,30 @@ public class DateTimeList : DebuggerTestBase [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] public async Task CheckDateTimeLocale(string locale, string fdtp, string ldp, string ltp, string sdp, string stp) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; + await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); - await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + + $"'{locale}'); }}, 1);", + debugger_test_loc, 25, 12, "LocaleTest", + locals_fn: async (locals) => + { + DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; + CultureInfo.CurrentCulture = new CultureInfo(locale, false); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + - $"'{locale}'); }}, 1);", - debugger_test_loc, 25, 12, "LocaleTest", - locals_fn: async (locals) => + await CheckProps(locals, new { - DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; - CultureInfo.CurrentCulture = new CultureInfo(locale, false); - - await CheckProps(locals, new - { - fdtp = TString(fdtp), - ldp = TString(ldp), - ltp = TString(ltp), - sdp = TString(sdp), - stp = TString(stp), - dt = TDateTime(new DateTime(2020, 1, 2, 3, 4, 5)) - }, "locals", num_fields: 8); - } - ); - - }); + fdtp = TString(fdtp), + ldp = TString(ldp), + ltp = TString(ltp), + sdp = TString(sdp), + stp = TString(stp), + dt = TDateTime(new DateTime(2020, 1, 2, 3, 4, 5)) + }, "locals", num_fields: 8); + } + ); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs similarity index 77% rename from src/mono/wasm/debugger/DebuggerTestSuite/Support.cs rename to src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 4a0e50b40c7245..e0672d6ab79ce9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -19,106 +19,23 @@ namespace DebuggerTests { - class Inspector + public class DebuggerTestBase : IAsyncLifetime { - // InspectorClient client; - Dictionary> notifications = new Dictionary>(); - Dictionary> eventListeners = new Dictionary>(); + // internal InspectorClient cli; + internal Inspector insp; - public const string PAUSE = "pause"; - public const string READY = "ready"; - - public Task WaitFor(string what) - { - if (notifications.ContainsKey(what)) - throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup"); - var n = new TaskCompletionSource(); - notifications[what] = n; - return n.Task; - } - - void NotifyOf(string what, JObject args) - { - if (!notifications.ContainsKey(what)) - throw new Exception($"Invalid internal state, notifying of {what}, but nobody waiting"); - notifications[what].SetResult(args); - notifications.Remove(what); - } - - public void On(string evtName, Func cb) - { - eventListeners[evtName] = cb; - } - - void FailAllWaitersWithException(JObject exception) - { - foreach (var tcs in notifications.Values) - tcs.SetException(new ArgumentException(exception.ToString())); - } - - async Task OnMessage(string method, JObject args, CancellationToken token) - { - //System.Console.WriteLine("OnMessage " + method + args); - switch (method) - { - case "Debugger.paused": - NotifyOf(PAUSE, args); - break; - case "Mono.runtimeReady": - NotifyOf(READY, args); - break; - case "Runtime.consoleAPICalled": - Console.WriteLine("CWL: {0}", args?["args"]?[0]?["value"]); - break; - } - if (eventListeners.ContainsKey(method)) - await eventListeners[method](args, token); - else if (String.Compare(method, "Runtime.exceptionThrown") == 0) - FailAllWaitersWithException(args); - } - - public async Task Ready(Func cb = null, TimeSpan? span = null) - { - using (var cts = new CancellationTokenSource()) - { - cts.CancelAfter(span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default - var uri = new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect"); - using var loggerFactory = LoggerFactory.Create( - builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - using (var client = new InspectorClient(loggerFactory.CreateLogger())) - { - await client.Connect(uri, OnMessage, async token => - { - Task[] init_cmds = { - client.SendCommand("Profiler.enable", null, token), - client.SendCommand("Runtime.enable", null, token), - client.SendCommand("Debugger.enable", null, token), - client.SendCommand("Runtime.runIfWaitingForDebugger", null, token), - WaitFor(READY), - }; - // await Task.WhenAll (init_cmds); - Console.WriteLine("waiting for the runtime to be ready"); - await init_cmds[4]; - Console.WriteLine("runtime ready, TEST TIME"); - if (cb != null) - { - Console.WriteLine("await cb(client, token)"); - await cb(client, token); - } + protected CancellationTokenSource _cancellationTokenSource = new (); + protected CancellationToken token; + protected Dictionary scripts; + protected Task startTask; - }, cts.Token); - await client.Close(cts.Token); - } - } - } - } + public bool UseCallFunctionOnBeforeGetProperties; - public class DebuggerTestBase - { - protected Task startTask; + private const int DefaultTestTimeoutMs = 1 * 60 * 1000; static string s_debuggerTestAppPath; - protected static string DebuggerTestAppPath + static int s_nextId; + public static string DebuggerTestAppPath { get { @@ -139,40 +56,53 @@ static protected string FindTestPath() throw new Exception($"Could not figure out debugger-test app path ({test_app_path}) based on the test suite location ({asm_dir})"); } - static string[] PROBE_LIST = { - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", - "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", - "/usr/bin/chromium", - "/usr/bin/chromium-browser", - }; - static string chrome_path; + public string Id { get; init; } - static string FindChromePath() + public DebuggerTestBase()//string driver = "debugger-driver.html") { - if (chrome_path != null) - return chrome_path; + Id = Interlocked.Increment(ref s_nextId).ToString(); + insp = new Inspector(Id, _cancellationTokenSource); + // cli = insp.Client; + scripts = SubscribeToScripts(insp); + + token = _cancellationTokenSource.Token; + + // startTask = TestHarnessProxy.Start(DebuggerTestAppPath, + } - foreach (var s in PROBE_LIST) + public virtual async Task InitializeAsync() + { + Func)>> fn = (session, token) => { - if (File.Exists(s)) + Func)> getInitCmdFn = (cmd) => (cmd, session.SendCommand(cmd, null, token)); + var init_cmds = new List<(string, Task)> { - chrome_path = s; - Console.WriteLine($"Using chrome path: ${s}"); - return s; - } - } - throw new Exception("Could not find an installed Chrome to use"); + getInitCmdFn("Profiler.enable"), + getInitCmdFn("Runtime.enable"), + getInitCmdFn("Debugger.enable"), + getInitCmdFn("Runtime.runIfWaitingForDebugger") + }; + + return init_cmds; + }; + + TimeSpan? span = null; //TODO + + _cancellationTokenSource.CancelAfter(span?.Milliseconds ?? DefaultTestTimeoutMs); + + // await Ready(); + await insp.OpenSessionAsync("/debugger-driver.html", fn); + // cli = insp.Client; } - public DebuggerTestBase(string driver = "debugger-driver.html") + public virtual async Task DisposeAsync() { - startTask = TestHarnessProxy.Start(FindChromePath(), DebuggerTestAppPath, driver); + Console.WriteLine ($"-- [{Id}] TestBase.DisposeAsync"); + await insp.DisposeAsync(true); } - public Task Ready() => startTask; + public Task Ready() => Task.CompletedTask; - internal DebugTestContext ctx; internal Dictionary dicScriptsIdToUrl; internal Dictionary dicFileToUrl; internal Dictionary SubscribeToScripts(Inspector insp) @@ -203,88 +133,70 @@ internal Dictionary SubscribeToScripts(Inspector insp) internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression, Action test_fn = null, Func wait_for_event_fn = null, bool use_cfo = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var bp = await SetBreakpoint(url_key, line, column); - var bp = await SetBreakpoint(url_key, line, column); - - await EvaluateAndCheck( - eval_expression, url_key, line, column, - function_name, - wait_for_event_fn: async (pause_location) => - { + await EvaluateAndCheck( + eval_expression, url_key, line, column, + function_name, + wait_for_event_fn: async (pause_location) => + { //make sure we're on the right bp Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location!["callFrames"]?[0]; - var scope = top_frame["scopeChain"][0]; - if (wait_for_event_fn != null) - await wait_for_event_fn(pause_location); - else - await Task.CompletedTask; - }, - locals_fn: (locals) => - { - if (test_fn != null) - test_fn(locals); - } - ); - }); + var scope = top_frame!["scopeChain"]?[0]; + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); + else + await Task.CompletedTask; + }, + locals_fn: (locals) => + { + if (test_fn != null) + test_fn(locals); + } + ); } // sets breakpoint by method name and line offset internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression, Action locals_fn = null, Func wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; - var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col); + var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col); - var args = JObject.FromObject(new { expression = eval_expression }); - var res = await ctx.cli.SendCommand("Runtime.evaluate", 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 args = JObject.FromObject(new { expression = eval_expression }); + var res = await SendCommand("Runtime.evaluate", args, 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 pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - if (bp_function_name != null) - Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value()); + if (bp_function_name != null) + Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location!["callFrames"]?[0]; - var scope = top_frame["scopeChain"][0]; + var scope = top_frame?["scopeChain"]?[0]; - if (wait_for_event_fn != null) - await wait_for_event_fn(pause_location); + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); - if (locals_fn != null) - { - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - locals_fn(locals); - } - }); + if (locals_fn != null) + { + var locals = await GetProperties(pause_location?["callFrames"]?[0]?["callFrameId"]?.Value()); + locals_fn(locals); + } } internal void CheckLocation(string script_loc, int line, int column, Dictionary scripts, JToken location) @@ -348,13 +260,13 @@ internal async Task CheckDateTime(JToken value, DateTime expected, string label await CheckDateTimeValue(value, expected, label); } - internal async Task CheckDateTime(JToken locals, string name, DateTime expected, string label="") + internal async Task CheckDateTime(JToken locals, string name, DateTime expected, string label = "") { var obj = GetAndAssertObjectWithName(locals, name, label); await CheckDateTimeValue(obj["value"], expected, label); } - internal async Task CheckDateTimeValue(JToken value, DateTime expected, string label="") + internal async Task CheckDateTimeValue(JToken value, DateTime expected, string label = "") { await CheckDateTimeMembers(value, expected, label); @@ -363,7 +275,7 @@ internal async Task CheckDateTimeValue(JToken value, DateTime expected, string l // FIXME: check some float properties too - async Task CheckDateTimeMembers(JToken v, DateTime exp_dt, string label="") + async Task CheckDateTimeMembers(JToken v, DateTime exp_dt, string label = "") { AssertEqual("System.DateTime", v["className"]?.Value(), $"{label}#className"); AssertEqual(exp_dt.ToString(), v["description"]?.Value(), $"{label}#description"); @@ -412,7 +324,7 @@ internal void CheckArray(JToken locals, string name, string class_name, int leng GetAndAssertObjectWithName(locals, name)["value"], TArray(class_name, length), name).Wait(); - internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label="") + internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label = "") { var l = obj.FirstOrDefault(jt => jt["name"]?.Value() == name); if (l == null) @@ -420,9 +332,9 @@ internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label return l; } - internal async Task SendCommand(string method, JObject args) + internal async Task SendCommand(string method, JObject args, CancellationToken? token = null) { - var res = await ctx.cli.SendCommand(method, args, ctx.token); + var res = await insp.Session?.SendCommand(method, args, token ?? _cancellationTokenSource.Token); if (!res.IsOk) { Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); @@ -433,7 +345,7 @@ internal async Task SendCommand(string method, JObject args) internal async Task Evaluate(string expression) { - return await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expression })); + return await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expression }), _cancellationTokenSource.Token); } internal void AssertLocation(JObject args, string methodName) @@ -448,7 +360,7 @@ internal async Task RunUntil(string methodName) await SetBreakpointInMethod("debugger-test", "DebuggerTest", methodName); // This will run all the tests until it hits the bp await Evaluate("window.setTimeout(function() { invoke_run_all (); }, 1);"); - var wait_res = await ctx.insp.WaitFor(Inspector.PAUSE); + var wait_res = await insp.WaitFor(Inspector.PAUSE); AssertLocation(wait_res, "locals_inner"); return wait_res; } @@ -464,7 +376,7 @@ internal async Task InvokeGetter(JToken obj, object arguments, string fn if (returnByValue != null) req["returnByValue"] = returnByValue.Value; - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", req, ctx.token); + var res = await SendCommand("Runtime.callFunctionOn", req, token); Assert.True(expect_ok == res.IsOk, $"InvokeGetter failed for {req} with {res}"); return res; @@ -496,23 +408,23 @@ internal async Task EvaluateAndCheck(string expression, string script_l internal async Task SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name, Func wait_for_event_fn = null, Action locals_fn = null, string waitForEvent = Inspector.PAUSE) { - var res = await ctx.cli.SendCommand(method, args, ctx.token); + var res = await SendCommand(method, args, 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); + var wait_res = await insp.WaitFor(waitForEvent); JToken top_frame = wait_res["callFrames"]?[0]; if (function_name != null) { AssertEqual(function_name, wait_res["callFrames"]?[0]?["functionName"]?.Value(), top_frame?.ToString()); } - Console.WriteLine (top_frame); + Console.WriteLine(top_frame); if (script_loc != null && line >= 0) - CheckLocation(script_loc, line, column, ctx.scripts, top_frame["location"]); + CheckLocation(script_loc, line, column, scripts, top_frame["location"]); if (wait_for_event_fn != null) await wait_for_event_fn(wait_res); @@ -752,14 +664,14 @@ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) } catch { - Console.WriteLine ($"Expected: {exp_val}. Actual: {actual_val}"); + Console.WriteLine($"Expected: {exp_val}. Actual: {actual_val}"); throw; } } internal async Task GetLocalsForFrame(JToken frame, string script_loc, int line, int column, string function_name) { - CheckLocation(script_loc, line, column, ctx.scripts, frame["location"]); + CheckLocation(script_loc, line, column, scripts, frame["location"]); Assert.Equal(function_name, frame["functionName"].Value()); return await GetProperties(frame["callFrameId"].Value()); @@ -801,7 +713,7 @@ internal async Task GetObjectOnLocals(JToken locals, string name) /* @fn_args is for use with `Runtime.callFunctionOn` only */ internal async Task GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { - if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) + if (UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) { var fn_decl = "function () { return this; }"; var cfo_args = JObject.FromObject(new @@ -812,7 +724,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool if (fn_args != null) cfo_args["arguments"] = fn_args; - var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + var result = await SendCommand("Runtime.callFunctionOn", cfo_args, token); AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}"); if (!result.IsOk) return null; @@ -832,7 +744,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool get_prop_req["accessorPropertiesOnly"] = accessors_only.Value; } - var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + var frame_props = await SendCommand("Runtime.getProperties", get_prop_req, token); AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); if (!frame_props.IsOk) return null; @@ -863,7 +775,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool expression = expression }); - var res = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token); + var res = await SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, token); AssertEqual(expect_ok, res.IsOk, $"Debugger.evaluateOnCallFrame ('{expression}', scope: {id}) returned {res.IsOk} instead of {expect_ok}, with Result: {res}"); if (res.IsOk) return (res.Value["result"], res); @@ -878,7 +790,7 @@ internal async Task RemoveBreakpoint(string id, bool expect_ok = true) breakpointId = id }); - var res = await ctx.cli.SendCommand("Debugger.removeBreakpoint", remove_bp, ctx.token); + var res = await SendCommand("Debugger.removeBreakpoint", remove_bp, token); Assert.True(expect_ok ? res.IsOk : res.IsErr); return res; @@ -890,7 +802,7 @@ internal async Task SetBreakpoint(string url_key, int line, int column, JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], }) : JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, }); - var bp1_res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + var bp1_res = await SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr); return bp1_res; @@ -898,7 +810,7 @@ internal async Task SetBreakpoint(string url_key, int line, int column, internal async Task SetPauseOnException(string state) { - var exc_res = await ctx.cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), ctx.token); + var exc_res = await SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), token); return exc_res; } @@ -907,7 +819,7 @@ internal async Task SetBreakpointInMethod(string assembly, string type, var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset }); // Protocol extension - var res = await ctx.cli.SendCommand("DotnetDebugger.getMethodLocation", req, ctx.token); + var res = await SendCommand("DotnetDebugger.getMethodLocation", req, token); Assert.True(res.IsOk); var m_url = res.Value["result"]["url"].Value(); @@ -920,7 +832,7 @@ internal async Task SetBreakpointInMethod(string assembly, string type, url = m_url }); - res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + res = await SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); Assert.True(res.IsOk); return res; @@ -1010,24 +922,6 @@ internal static JObject TDateTime(DateTime dt) => JObject.FromObject(new }); } - class DebugTestContext - { - public InspectorClient cli; - public Inspector insp; - public CancellationToken token; - public Dictionary scripts; - - public bool UseCallFunctionOnBeforeGetProperties; - - public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary scripts) - { - this.cli = cli; - this.insp = insp; - this.token = token; - this.scripts = scripts; - } - } - class DotnetObjectId { public string Scheme { get; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj index 4a057ed11853eb..a46fd059b01fe9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs index b8fbac85a14de3..13e024c9de1aaf 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DevToolsClient.cs @@ -14,16 +14,22 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class DevToolsClient : IDisposable { + DevToolsQueue _queue; ClientWebSocket socket; - List pending_ops = new List(); - TaskCompletionSource side_exit = new TaskCompletionSource(); - List pending_writes = new List(); - Task current_write; + TaskCompletionSource _clientInitiatedClose = new (); + TaskCompletionSource _shutdownRequested = new (); + TaskCompletionSource _newSendTaskAvailable = new (); protected readonly ILogger logger; + protected readonly string _id; - public DevToolsClient(ILogger logger) + // private bool IsCloseRequested => _clientInitiatedClose.Task.IsCompletedSuccessfully; + public bool IsRunning { get; private set; } + public event EventHandler<(RunLoopStopReason reason, Exception ex)> RunLoopStopped; + + public DevToolsClient(string id, ILogger logger) { this.logger = logger; + _id = id; } ~DevToolsClient() @@ -36,127 +42,228 @@ public void Dispose() Dispose(true); } - public async Task Close(CancellationToken cancellationToken) - { - if (socket.State == WebSocketState.Open) - await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); - } - protected virtual void Dispose(bool disposing) { if (disposing) socket.Dispose(); } - Task Pump(Task task, CancellationToken token) + //FIXME: DisposeAsync + + public async Task Shutdown(CancellationToken cancellationToken) { - if (task != current_write) - return null; - current_write = null; + if (_shutdownRequested.Task.IsCompleted) + { + logger.LogDebug($"Shutdown was already requested once. socket: {socket.State}. Ignoring"); + return; + } - pending_writes.RemoveAt(0); + try + { + _shutdownRequested.SetResult(); - if (pending_writes.Count > 0) + if (!cancellationToken.IsCancellationRequested && socket.State == WebSocketState.Open) + await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); + } + catch (Exception ex) when (ex is IOException || ex is WebSocketException || ex is OperationCanceledException) { - current_write = socket.SendAsync(new ArraySegment(pending_writes[0]), WebSocketMessageType.Text, true, token); - return current_write; + logger.LogDebug($"DevToolsClient.Shutdown: Close failed, but ignoring: {ex}"); } - return null; } + private void LogState() + { + var sb = new StringBuilder(); + sb.Append($"------------ DevtoolsClient.RunLoop [{_id}] state dump -----------\n"); + LogState(sb); + + // sb.Append($"Running time: {DateTime.Now - _startTime}"); + + // ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads); + // sb.Append($"TP available threads: worker: {workerThreads}, compleition: {completionPortThreads}\n"); + // sb.Append($"TP PendingWorkItemCount: {ThreadPool.PendingWorkItemCount}\n"); + + sb.Append($"------------ DevtoolsClient.RunLoop -----------\n"); + logger.LogDebug(sb.ToString()); + } + + protected virtual void LogState(StringBuilder sb) => + sb.Append($"pending_ops: , IsRunning: {IsRunning}\n"); + async Task ReadOne(CancellationToken token) { byte[] buff = new byte[4000]; var mem = new MemoryStream(); while (true) { - var result = await this.socket.ReceiveAsync(new ArraySegment(buff), token); - if (result.MessageType == WebSocketMessageType.Close) + if (socket.State != WebSocketState.Open) { + logger.LogDebug($"Socket is no longer open"); + _clientInitiatedClose.TrySetResult(); return null; } - if (result.EndOfMessage) + WebSocketReceiveResult result; + try { - mem.Write(buff, 0, result.Count); - return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); + result = await socket.ReceiveAsync(new ArraySegment(buff), token).ConfigureAwait(false); } - else + catch (Exception ex) { - mem.Write(buff, 0, result.Count); + if (token.IsCancellationRequested || _shutdownRequested.Task.IsCompletedSuccessfully) + return null; + + logger.LogDebug($"DevToolsClient.ReadOne threw {ex.Message}, token: {token.IsCancellationRequested}, _shutdown: {_shutdownRequested.Task.Status}, clientInitiated: {_clientInitiatedClose.Task.Status}"); + throw; } - } - } - protected void Send(byte[] bytes, CancellationToken token) - { - pending_writes.Add(bytes); - if (pending_writes.Count == 1) - { - if (current_write != null) - throw new Exception("Internal state is bad. current_write must be null if there are no pending writes"); + if (result.MessageType == WebSocketMessageType.Close) + { + _clientInitiatedClose.TrySetResult(); + return null; + } - current_write = socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); - pending_ops.Add(current_write); + mem.Write(buff, 0, result.Count); + if (result.EndOfMessage) + { + return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); + } } } - async Task MarkCompleteAfterward(Func send, CancellationToken token) + protected void Send(byte[] bytes, CancellationToken token) { - try - { - await send(token); - side_exit.SetResult(true); - } - catch (Exception e) - { - side_exit.SetException(e); - } + Task sendTask = _queue.Send(bytes, token); + if (sendTask != null) + _newSendTaskAvailable.TrySetResult(); } - protected async Task ConnectWithMainLoops( + protected async Task ConnectWithMainLoops( Uri uri, Func receive, - Func send, CancellationToken token) { - logger.LogDebug("connecting to {0}", uri); this.socket = new ClientWebSocket(); this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; await this.socket.ConnectAsync(uri, token); - pending_ops.Add(ReadOne(token)); - pending_ops.Add(side_exit.Task); - pending_ops.Add(MarkCompleteAfterward(send, token)); + _queue = new DevToolsQueue(socket); + + var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(token); - while (!token.IsCancellationRequested) + IsRunning = true; + _ = Task.Run(async () => { - var task = await Task.WhenAny(pending_ops); - if (task == pending_ops[0]) - { //pending_ops[0] is for message reading - var msg = ((Task)task).Result; - pending_ops[0] = ReadOne(token); - Task tsk = receive(msg, token); - if (tsk != null) - pending_ops.Add(tsk); + try + { + RunLoopStopReason reason; + Exception exception; + + try + { + (reason, exception) = await RunLoop(receive, linkedCts); + } + catch (Exception ex) + { + logger.LogDebug($"RunLoop threw an exception. (parentToken: {token.IsCancellationRequested}, linked: {linkedCts.IsCancellationRequested}): {ex} "); + RunLoopStopped?.Invoke(this, (RunLoopStopReason.Exception, ex)); + return; + } + + try + { + logger.LogDebug($"RunLoop stopped, reason: {reason}. (parentToken: {token.IsCancellationRequested}, linked: {linkedCts.IsCancellationRequested}): {exception?.Message}"); + RunLoopStopped?.Invoke(this, (reason, exception)); + } + catch (Exception ex) + { + logger.LogError(ex, $"Invoking RunLoopStopped event failed for (reason: {reason}, exception: {exception})"); + } + } + finally + { + IsRunning = false; + logger.LogDebug($"Loop ended with socket: {socket.State}"); + linkedCts.Cancel(); + } + }); + } + + private async Task<(RunLoopStopReason, Exception)> RunLoop( + Func receive, + CancellationTokenSource linkedCts) + { + var pending_ops = new List + { + ReadOne(linkedCts.Token), + _newSendTaskAvailable.Task, + _clientInitiatedClose.Task, + _shutdownRequested.Task + }; + + // In case we had a Send called already + if (_queue.TryPumpIfCurrentCompleted(linkedCts.Token, out Task sendTask)) + pending_ops.Add(sendTask); + + while (!linkedCts.IsCancellationRequested) + { + var task = await Task.WhenAny(pending_ops).ConfigureAwait(false); + + if (task.IsCanceled && linkedCts.IsCancellationRequested) + return (RunLoopStopReason.Cancelled, null); + + if (_shutdownRequested.Task.IsCompleted) + return (RunLoopStopReason.Shutdown, null); + + if (_clientInitiatedClose.Task.IsCompleted) + return (RunLoopStopReason.ClientInitiatedClose, new TaskCanceledException("Proxy or the browser closed the connection")); + + if (_newSendTaskAvailable.Task.IsCompleted) + { + // Just needed to wake up. the new task has already + // been added to pending_ops + _newSendTaskAvailable = new (); + pending_ops[1] = _newSendTaskAvailable.Task; + + _queue.TryPumpIfCurrentCompleted(linkedCts.Token, out _); + if (_queue.CurrentSend != null) + pending_ops.Add(_queue.CurrentSend); } - else if (task == pending_ops[1]) + + if (task == pending_ops[0]) { - var res = ((Task)task).Result; - //it might not throw if exiting successfull - return res; + var msg = await (Task)pending_ops[0]; + pending_ops[0] = ReadOne(linkedCts.Token); + + if (msg != null) + { + Task tsk = receive(msg, linkedCts.Token); + if (tsk != null) + pending_ops.Add(tsk); + } } else - { //must be a background task + { + //must be a background task pending_ops.Remove(task); - var tsk = Pump(task, token); - if (tsk != null) - pending_ops.Add(tsk); + if (task == _queue.CurrentSend && _queue.TryPumpIfCurrentCompleted(linkedCts.Token, out sendTask)) + pending_ops.Add(sendTask); } } - return false; + if (linkedCts.IsCancellationRequested) + return (RunLoopStopReason.Cancelled, null); + + return (RunLoopStopReason.Exception, new InvalidOperationException($"This shouldn't ever get thrown. Unsure why the loop stopped")); } } + + internal enum RunLoopStopReason + { + Shutdown, + Cancelled, + Exception, + ClientInitiatedClose + } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 2218b9a7945114..7f8e9591565e16 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -49,26 +49,26 @@ public async Task EvaluateTypeInstanceMembers(string prefix, int bias, string ty var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); var DTProp = dateTime.AddMinutes(10); - foreach (var pad in new[] { String.Empty, " " }) - { - var padded_prefix = pad + prefix; - await EvaluateOnCallFrameAndCheck(id, - ($"{padded_prefix}a", TNumber(4)), - - // fields - ($"{padded_prefix}dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())), - ($"{padded_prefix}dateTime", TDateTime(dateTime)), - ($"{padded_prefix}dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), - - // properties - ($"{padded_prefix}DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), - ($"{padded_prefix}DTProp", TDateTime(DTProp)), - ($"{padded_prefix}DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())), - - ($"{padded_prefix}IntProp", TNumber(9)), - ($"{padded_prefix}NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true)) - ); - } + foreach (var pad in new[] { String.Empty, " " }) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + ($"{padded_prefix}a", TNumber(4)), + + // fields + ($"{padded_prefix}dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())), + ($"{padded_prefix}dateTime", TDateTime(dateTime)), + ($"{padded_prefix}dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), + + // properties + ($"{padded_prefix}DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), + ($"{padded_prefix}DTProp", TDateTime(DTProp)), + ($"{padded_prefix}DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())), + + ($"{padded_prefix}IntProp", TNumber(9)), + ($"{padded_prefix}NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true)) + ); + } }); [Theory] @@ -145,13 +145,13 @@ public async Task EvaluateLocalsAsync() => await CheckInspectLocalsAtBreakpointS { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - // sc_arg - { + // sc_arg + { var (sc_arg, _) = await EvaluateOnCallFrame(id, "sc_arg"); await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), nameof(sc_arg)); - // Check that we did get the correct object - var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); + // Check that we did get the correct object + var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); await CheckProps(sc_arg_props, new { X = TNumber(10), @@ -167,8 +167,8 @@ await EvaluateOnCallFrameAndCheck(id, ("sc_arg.Id + (sc_arg.X==10 ? \"_is_ten\" : \"_not_ten\")", TString($"sc#Id_is_ten"))); } - // local_gs - { + // local_gs + { var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs"); await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), nameof(local_gs)); @@ -221,28 +221,28 @@ public async Task InheritedAndPrivateMembersInAClass(string prefix) { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - foreach(var pad in new [] { String.Empty, " "}) - { - var padded_prefix = pad + prefix; - await EvaluateOnCallFrameAndCheck(id, - // overridden - ($"{padded_prefix}FirstName + \"_foo\"", TString("DerivedClass#FirstName_foo")), - ($"{padded_prefix}DateTimeForOverride.Date.Year", TNumber(2190)), - ($"{padded_prefix}DateTimeForOverride.Date.Year - 10", TNumber(2180)), - ($"\"foo_\" + {padded_prefix}StringPropertyForOverrideWithAutoProperty", TString("foo_DerivedClass#StringPropertyForOverrideWithAutoProperty")), - - // private - ($"{padded_prefix}_stringField + \"_foo\"", TString("DerivedClass#_stringField_foo")), - ($"{padded_prefix}_stringField", TString("DerivedClass#_stringField")), - ($"{padded_prefix}_dateTime.Second + 4", TNumber(7)), - ($"{padded_prefix}_DTProp.Second + 4", TNumber(13)), - - // inherited public - ($"\"foo_\" + {padded_prefix}Base_AutoStringProperty", TString("foo_base#Base_AutoStringProperty")), - // inherited private - ($"{padded_prefix}_base_dateTime.Date.Year - 10", TNumber(2124)) - ); - } + foreach (var pad in new[] { String.Empty, " " }) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + // overridden + ($"{padded_prefix}FirstName + \"_foo\"", TString("DerivedClass#FirstName_foo")), + ($"{padded_prefix}DateTimeForOverride.Date.Year", TNumber(2190)), + ($"{padded_prefix}DateTimeForOverride.Date.Year - 10", TNumber(2180)), + ($"\"foo_\" + {padded_prefix}StringPropertyForOverrideWithAutoProperty", TString("foo_DerivedClass#StringPropertyForOverrideWithAutoProperty")), + + // private + ($"{padded_prefix}_stringField + \"_foo\"", TString("DerivedClass#_stringField_foo")), + ($"{padded_prefix}_stringField", TString("DerivedClass#_stringField")), + ($"{padded_prefix}_dateTime.Second + 4", TNumber(7)), + ($"{padded_prefix}_DTProp.Second + 4", TNumber(13)), + + // inherited public + ($"\"foo_\" + {padded_prefix}Base_AutoStringProperty", TString("foo_base#Base_AutoStringProperty")), + // inherited private + ($"{padded_prefix}_base_dateTime.Date.Year - 10", TNumber(2124)) + ); + } }); [Fact] @@ -322,8 +322,8 @@ public async Task EvaluateOnPreviousFrames(string type_name, bool is_valuetype) var dt_local = new DateTime(2020, 3, 4, 5, 6, 7); var dt_this = new DateTime(2010, 9, 8, 7, 6, 5); - // At EvaluateShadow - { + // At EvaluateShadow + { var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id0, ("dateTime", TDateTime(dt_local)), @@ -339,38 +339,38 @@ await EvaluateOnCallFrameFail(id1, ("dateTime", "ReferenceError"), ("this.dateTime", "ReferenceError")); - // obj available only on the -1 frame - await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7))); + // obj available only on the -1 frame + await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7))); } await SetBreakpointInMethod("debugger-test.dll", type_name, "SomeMethod", 1); pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "SomeMethod"); - // At SomeMethod + // At SomeMethod - // TODO: change types also.. so, that `this` is different! + // TODO: change types also.. so, that `this` is different! - // Check frame0 - { + // Check frame0 + { var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); - // 'me' and 'dateTime' are reversed in this method - await EvaluateOnCallFrameAndCheck(id0, - ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)), - ("this.dateTime", TDateTime(dt_this)), - ("me", TDateTime(dt_local)), + // 'me' and 'dateTime' are reversed in this method + await EvaluateOnCallFrameAndCheck(id0, + ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)), + ("this.dateTime", TDateTime(dt_this)), + ("me", TDateTime(dt_local)), - // local variable shadows field, but isn't "live" yet - ("DTProp", TString(null)), + // local variable shadows field, but isn't "live" yet + ("DTProp", TString(null)), - // access field via `this.` - ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); + // access field via `this.` + ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); await EvaluateOnCallFrameFail(id0, ("obj", "ReferenceError")); } - // check frame1 - { + // check frame1 + { var id1 = pause_location["callFrames"][1]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id1, @@ -388,15 +388,15 @@ await EvaluateOnCallFrameAndCheck(id1, await EvaluateOnCallFrameFail(id1, ("obj", "ReferenceError")); } - // check frame2 - { + // check frame2 + { var id2 = pause_location["callFrames"][2]["callFrameId"].Value(); - // Only obj should be available - await EvaluateOnCallFrameFail(id2, - ("dateTime", "ReferenceError"), - ("this.dateTime", "ReferenceError"), - ("me", "ReferenceError")); + // Only obj should be available + await EvaluateOnCallFrameFail(id2, + ("dateTime", "ReferenceError"), + ("this.dateTime", "ReferenceError"), + ("me", "ReferenceError")); await EvaluateOnCallFrameAndCheck(id2, ("obj", is_valuetype ? TValueType(type_name) : TObject(type_name))); } @@ -405,32 +405,23 @@ await EvaluateOnCallFrameFail(id2, [Fact] public async Task JSEvaluate() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - var bp_loc = "/other.js"; var line = 76; var col = 1; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + await SetBreakpoint(bp_loc, line, col); - var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)"; + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - await EvaluateOnCallFrameFail(id, - ("me.foo", null), - ("obj.foo.bar", null)); + await EvaluateOnCallFrameFail(id, + ("me.foo", null), + ("obj.foo.bar", null)); - await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true); - }); + await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true); } [Fact] @@ -441,23 +432,23 @@ public async Task NegativeTestsInInstanceMethod() => await CheckInspectLocalsAtB { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - // Use '.' on a primitive member - await EvaluateOnCallFrameFail(id, - //BUG: TODO: - //("a)", "CompilationError"), + // Use '.' on a primitive member + await EvaluateOnCallFrameFail(id, + //BUG: TODO: + //("a)", "CompilationError"), - ("this.a.", "ReferenceError"), - ("a.", "ReferenceError"), + ("this.a.", "ReferenceError"), + ("a.", "ReferenceError"), - ("this..a", "CompilationError"), - (".a.", "ReferenceError"), + ("this..a", "CompilationError"), + (".a.", "ReferenceError"), - ("me.foo", "ReferenceError"), + ("me.foo", "ReferenceError"), - ("this.a + non_existant", "ReferenceError"), + ("this.a + non_existant", "ReferenceError"), - ("this.NullIfAIsNotZero.foo", "ReferenceError"), - ("NullIfAIsNotZero.foo", "ReferenceError")); + ("this.NullIfAIsNotZero.foo", "ReferenceError"), + ("NullIfAIsNotZero.foo", "ReferenceError")); }); [Fact] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs index 6f339c28ea90fd..16346ac508b229 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs @@ -16,102 +16,83 @@ public class ExceptionTests : DebuggerTestBase [Fact] public async Task ExceptionTestAll() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs"; - - await SetPauseOnException("all"); + await SetPauseOnException("all"); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null); - //stop in the managed caught exception - pause_location = await WaitForManagedException(pause_location); + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null); + //stop in the managed caught exception + pause_location = await WaitForManagedException(pause_location); - AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause0"); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause0"); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "DebuggerTests.CustomException", - uncaught = false - }), "exception0.data"); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = false + }), "exception0.data"); - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "not implemented caught"); + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented caught"); - pause_location = await WaitForManagedException(null); - AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause1"); + pause_location = await WaitForManagedException(null); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause1"); - //stop in the uncaught exception - CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]); + //stop in the uncaught exception + CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "DebuggerTests.CustomException", - uncaught = true - }), "exception1.data"); - - exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "not implemented uncaught"); - }); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented uncaught"); } [Fact] public async Task JSExceptionTestAll() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + await SetPauseOnException("all"); - await SetPauseOnException("all"); + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null); - var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; - var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null); - - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "TypeError", - uncaught = false - }), "exception0.data"); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "TypeError", + uncaught = false + }), "exception0.data"); - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "exception caught"); + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception caught"); - pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test"); + pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test"); - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "RangeError", - uncaught = true - }), "exception1.data"); - - exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "exception uncaught"); - }); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "RangeError", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception uncaught"); } // FIXME? BUG? We seem to get the stack trace for Runtime.exceptionThrown at `call_method`, @@ -119,88 +100,68 @@ await CheckValue(pause_location["data"], JObject.FromObject(new [Fact] public async Task ExceptionTestNone() { - var insp = new Inspector(); //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + await SetPauseOnException("none"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - await SetPauseOnException("none"); + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + var eo = JObject.Parse(ae.Message); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value (), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - try - { - await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); - } - catch (ArgumentException ae) + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new { - var eo = JObject.Parse(ae.Message); - - // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value (), "lineNumber"); - AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - - await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "Error" // BUG?: "DebuggerTests.CustomException" - }), "exception"); + type = "object", + subtype = "error", + className = "Error" // BUG?: "DebuggerTests.CustomException" + }), "exception"); - return; - } + return; + } - Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); - }); + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); } [Fact] public async Task JSExceptionTestNone() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetPauseOnException("none"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; - await SetPauseOnException("none"); + int line = 44; + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + var eo = JObject.Parse(ae.Message); - var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value(), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - int line = 44; - try - { - await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); - } - catch (ArgumentException ae) + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new { - Console.WriteLine($"{ae}"); - var eo = JObject.Parse(ae.Message); - - AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value(), "lineNumber"); - AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - - await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "RangeError" - }), "exception"); + type = "object", + subtype = "error", + className = "RangeError" + }), "exception"); - return; - } + return; + } - Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); - }); + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); } [Theory] @@ -211,31 +172,22 @@ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name, string exception_type, string exception_message) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetPauseOnException("uncaught"); + await SetPauseOnException("uncaught"); - var eval_expr = $"window.setTimeout({eval_fn}, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name); + var eval_expr = $"window.setTimeout({eval_fn}, 1);"; + var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name); - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = exception_type, - uncaught = true - }), "exception.data"); - - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", exception_message); - }); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = exception_type, + uncaught = true + }), "exception.data"); + + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", exception_message); } async Task WaitForManagedException(JObject pause_location) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 2f35e8cfdc0e09..ed0184e74c21b5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -188,7 +188,7 @@ public async Task InspectTypeInheritedMembers(string type_name, bool? own_proper }); public static IEnumerable MembersForLocalNestedStructData(bool is_async) - => StructGetPropertiesTestData(false).Select (datum => datum [1..]); + => StructGetPropertiesTestData(false).Select(datum => datum[1..]); [Theory] [MemberData(nameof(MembersForLocalNestedStructData), parameters: false)] @@ -217,7 +217,7 @@ public async Task MembersForLocalNestedStruct(bool? own_properties, bool? access AssertEqual(expected_names.Length, cs_props.Count(), $"expected number of properties"); }); - public static TheoryData JSGetPropertiesTestData(bool test_js)=> new TheoryData + public static TheoryData JSGetPropertiesTestData(bool test_js) => new TheoryData { // default, no args set { @@ -282,49 +282,40 @@ public async Task MembersForLocalNestedStruct(bool? own_properties, bool? access // [MemberData(nameof(JSGetPropertiesTestData), parameters: false)] public async Task GetPropertiesTestJSAndManaged(bool test_js, bool? own_properties, bool? accessors_only, string[] expected_names) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - - await Ready(); - await insp.Ready(async (cli, token) => + string eval_expr; + if (test_js) { - ctx = new DebugTestContext(cli, insp, token, scripts); - string eval_expr; - if (test_js) - { - await SetBreakpoint("/other.js", 93, 1); - eval_expr = "window.setTimeout(function() { get_properties_test (); }, 1)"; - } - else - { - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.GetPropertiesTests.DerivedClassForJSTest", "run", 2); - eval_expr = "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClassForJSTest:run'); }, 1)"; - } + await SetBreakpoint("/other.js", 93, 1); + eval_expr = "window.setTimeout(function() { get_properties_test (); }, 1)"; + } + else + { + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.GetPropertiesTests.DerivedClassForJSTest", "run", 2); + eval_expr = "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClassForJSTest:run'); }, 1)"; + } - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var result = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var id = pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(); + var id = pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(); - var frame_locals = await GetProperties(id); - var obj = GetAndAssertObjectWithName(frame_locals, "obj"); - var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value(), - own_properties: own_properties, accessors_only: accessors_only); + var frame_locals = await GetProperties(id); + var obj = GetAndAssertObjectWithName(frame_locals, "obj"); + var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value(), + own_properties: own_properties, accessors_only: accessors_only); - IEnumerable filtered_props; - if (test_js) - { - filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value() == true); - } - else - { - // we don't set `enumerable` right now - filtered_props = obj_props.Children().Where(jt=> true); - } + IEnumerable filtered_props; + if (test_js) + { + filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value() == true); + } + else + { + // we don't set `enumerable` right now + filtered_props = obj_props.Children().Where(jt => true); + } - var expected_props = new Dictionary () + var expected_props = new Dictionary() { // own {"owner_name", (TString("foo"), true)}, @@ -336,13 +327,12 @@ await insp.Ready(async (cli, token) => {"available", (TGetter("available"), false)}, }; - await CheckExpectedProperties( - expected_names, - name => filtered_props.Where(jt => jt["name"]?.Value () == name).SingleOrDefault(), - expected_props); + await CheckExpectedProperties( + expected_names, + name => filtered_props.Where(jt => jt["name"]?.Value() == name).SingleOrDefault(), + expected_props); - AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties"); - }); + AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties"); } private async Task CheckExpectedProperties(string[] expected_names, Func get_actual_prop, Dictionary all_props) @@ -356,7 +346,7 @@ private async Task CheckExpectedProperties(string[] expected_names, Func () == true, $"{exp_name}#isOwn"); + AssertEqual(is_own, actual_prop["isOwn"]?.Value() == true, $"{exp_name}#isOwn"); if (exp_prop["__custom_type"]?.Value() == "getter") { @@ -373,14 +363,14 @@ private async Task CheckExpectedProperties(string[] expected_names, Func actual) + private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEnumerable actual) { var exp = new HashSet(expected_names); foreach (var obj in actual) { - if (!exp.Contains(obj["name"]?.Value ())) - Console.WriteLine ($"Unexpected: {obj}"); + if (!exp.Contains(obj["name"]?.Value())) + Console.WriteLine($"Unexpected: {obj}"); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs new file mode 100644 index 00000000000000..1e289dd6458c3f --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; +using Xunit; + +#nullable enable + +namespace DebuggerTests +{ + public class HarnessTests : DebuggerTestBase + { + [Fact] + public async Task TimedOutWaitingForInvalidBreakpoint() + { + await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 100, 0); + var tce = await Assert.ThrowsAsync( + async () => await EvaluateAndCheck("window.setTimeout(function() { invoke_add(); }, 1);", null, -1, -1, null)); + Assert.Contains("timed out", tce.Message); + } + + [Fact] + public async Task ExceptionThrown() + { + var ae = await Assert.ThrowsAsync( + async () => await EvaluateAndCheck("window.setTimeout(function() { non_existant_fn(); }, 1);", null, -1, -1, null)); + Assert.Contains("non_existant_fn is not defined", ae.Message); + } + + // [Fact] + // public async Task BrowserCrash() => await Assert.ThrowsAsync(async () => + // await SendCommandAndCheck(null, "Browser.crash", null, -1, -1, null)); + + // [Fact] + // public async Task BrowserClose() => await Assert.ThrowsAsync(async () => + // await SendCommandAndCheck(null, "Browser.close", null, -1, -1, null)); + + [Fact] + public async Task InspectorWaitForAfterMessageAlreadyReceived() + { + Result res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + Assert.True(res.IsOk, $"setBreakpoint failed with {res}"); + + res = await SendCommand( + "Runtime.evaluate", + JObject.FromObject(new { expression = "window.setTimeout(function() { invoke_add(); }, 0);" }), + token); + Assert.True(res.IsOk, $"evaluating the function failed with {res}"); + + // delay, so that we can get the Debugger.pause event + await Task.Delay(1000); + + await insp.WaitFor(Inspector.PAUSE); + } + + [Fact] + public async Task InspectorWaitForMessageThatNeverArrives() + { + var tce = await Assert.ThrowsAsync(async () => await insp.WaitFor("Message.that.never.arrives")); + Assert.Contains("timed out", tce.Message); + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs new file mode 100644 index 00000000000000..aa0a2862602b3b --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -0,0 +1,277 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; + +#nullable enable + +namespace DebuggerTests +{ + class Inspector + { + Dictionary> notifications = new (); + Dictionary> eventListeners = new (); + + public const string PAUSE = "pause"; + public const string READY = "ready"; + public CancellationToken Token => _cancellationTokenSource.Token; + // public InspectorClient? Client { get; private set; } + + public BrowserSession? Session { get; private set; } + + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly string _id; + + protected ILoggerFactory _loggerFactory; + protected ILogger _logger; + + public Inspector(string id, CancellationTokenSource cts) + { + _id = id; + _cancellationTokenSource = cts; + + _loggerFactory = LoggerFactory.Create(builder => + { + builder.AddSimpleConsole(c => + { + c.ColorBehavior = LoggerColorBehavior.Enabled; + c.TimestampFormat = "[HH:mm:ss.fff] "; + c.SingleLine = true; + }).AddFilter(null, LogLevel.Trace); + }); + + // Client = new InspectorClient(_id, _loggerFactory.CreateLogger($"{typeof(InspectorClient)}-{_id}")); + _logger = _loggerFactory.CreateLogger($"{typeof(Inspector)}-{_id}"); + } + + public Task WaitFor(string what) + { + if (notifications.TryGetValue(what, out TaskCompletionSource? tcs)) + { + if (tcs.Task.IsCompleted) + { + notifications.Remove(what); + return tcs.Task; + } + + throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup"); + } + else + { + var n = new TaskCompletionSource(); + notifications[what] = n; + return n.Task; + } + } + + void NotifyOf(string what, JObject args) + { + if (notifications.TryGetValue(what, out TaskCompletionSource? tcs)) + { + if (tcs.Task.IsCompleted) + throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read."); + + notifications[what].SetResult(args); + notifications.Remove(what); + } + else + { + var n = new TaskCompletionSource(); + notifications[what] = n; + n.SetResult(args); + } + } + + public void On(string evtName, Func cb) + { + eventListeners[evtName] = cb; + } + + void FailAllWaiters(Exception? exception = null) + { + // Because we can create already completed tasks, + // when we get a NotifyOf before the corresponding + // WaitFor, it might already be completed. So, use + // *Try* methods + + if (exception != null) + { + foreach (var tcs in notifications.Values) + tcs.TrySetException(exception); + } + else + { + foreach (var tcs in notifications.Values) + tcs.TrySetCanceled(); + } + } + + async Task OnMessage(string method, JObject args, CancellationToken token) + { + switch (method) + { + case "Debugger.paused": + NotifyOf(PAUSE, args); + break; + case "Mono.runtimeReady": + NotifyOf(READY, args); + break; + case "Runtime.consoleAPICalled": + _logger.LogInformation("CWL: {0}", args["args"]); + break; + } + if (eventListeners.TryGetValue(method, out var listener)) + { + await listener(args, token).ConfigureAwait(false); + } + else if (String.Compare(method, "Runtime.exceptionThrown") == 0) + { + _logger.LogDebug($"Failing all waiters because: {method}: {args}"); + FailAllWaiters(new ArgumentException(args.ToString())); + } + } + + public async Task OpenSessionAsync(string relativeUrl, Func)>> getInitCmds) + { + var start = DateTime.Now; + try + { + // var uri = new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect?testId={_id}"); + + BrowserInstance browser = await BrowserPool.GetInstanceAsync(_logger, DebuggerTestBase.DebuggerTestAppPath, Token); + Session = await browser.OpenSession( + relativeUrl, + OnMessage, + RunLoopStoppedHandler, + _id, + _logger, + _cancellationTokenSource); + + // Client = Session.Connection.InspectorClient; + + // await Client.Connect(uri, OnMessage, _cancellationTokenSource.Token); + // Client.RunLoopStopped += (_, args) => + + var init_cmds = getInitCmds(Session, _cancellationTokenSource.Token); + + Task readyTask = Task.Run(async () => Result.FromJson(await WaitFor(READY))); + init_cmds.Add((READY, readyTask)); + + _logger.LogInformation("waiting for the runtime to be ready"); + while (!_cancellationTokenSource.IsCancellationRequested && init_cmds.Count > 0) + { + var cmd_tasks = init_cmds.Select(ct => ct.Item2); + Task t = await Task.WhenAny(cmd_tasks); + + int cmdIdx = init_cmds.FindIndex(ct => ct.Item2 == t); + string cmd_name = init_cmds[cmdIdx].Item1; + + if (t.IsCanceled) + { + throw new TaskCanceledException( + $"Command {cmd_name} timed out during init for the test." + + $"Remaining commands: {RemainingCommandsToString(cmd_name, init_cmds)}." + + $"Total time: {(DateTime.Now - start).TotalSeconds}"); + } + + if (t.IsFaulted) + { + _logger.LogError($"Command {cmd_name} failed with {t.Exception}. Remaining commands: {RemainingCommandsToString(cmd_name, init_cmds)}."); + throw t.Exception!; + } + + Result res = t.Result; + if (res.IsErr) + throw new ArgumentException($"Command {cmd_name} failed with: {res.Error}. Remaining commands: {RemainingCommandsToString(cmd_name, init_cmds)}"); + + init_cmds.RemoveAt(cmdIdx); + } + + _logger.LogInformation("runtime ready, TEST TIME"); + } + catch (Exception ex) + { + _logger.LogDebug(ex.ToString()); + throw; + } + + void RunLoopStoppedHandler((RunLoopStopReason reason, Exception? ex) args) + { + _logger.LogDebug($"Inspector: RunLoop stopped: {args.reason}, {args.ex}"); + switch (args.reason) + { + case RunLoopStopReason.Exception: + FailAllWaiters(args.ex); + break; + + case RunLoopStopReason.Cancelled when Token.IsCancellationRequested: + FailAllWaiters(new TaskCanceledException($"Test timed out (elapsed time: {(DateTime.Now - start).TotalSeconds}")); + break; + + default: + FailAllWaiters(); + break; + }; + }; + + static string RemainingCommandsToString(string cmd_name, IList<(string, Task)> cmds) + { + var sb = new StringBuilder(); + for (int i = 0; i < cmds.Count; i++) + { + var (name, task) = cmds[i]; + + if (cmd_name == name) + continue; + + sb.Append($"[{name}: {task.Status}], "); + } + + if (sb.Length > 2) + sb.Length -= 2; + + return sb.ToString(); + } + } + + public async Task DisposeAsync(bool shutdownConnection = true) + { + // if (Client == null) + // throw new InvalidOperationException($"InspectorClient is null. Duplicate Shutdown?"); + + try + { + _logger?.LogDebug($"- test done,. let's close the client"); + if (Session != null) + { + // FIXME: ium and add a timer on this token? + if (shutdownConnection) + await Session!.ShutdownConnection(new CancellationToken()); + await Session!.DisposeAsync(); + } + // await Client.Shutdown(_cancellationTokenSource.Token).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger?.LogError(ex.ToString()); + throw; + } + finally + { + _cancellationTokenSource.Cancel(); + // Client.Dispose(); + _loggerFactory?.Dispose(); + _cancellationTokenSource.Dispose(); + } + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs index e461300457da77..14110f6e961212 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs @@ -13,43 +13,92 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class InspectorClient : DevToolsClient { - Dictionary> pending_cmds = new Dictionary>(); - Func onEvent; + Dictionary> pending_cmds = new(); int next_cmd_id; + Func _catchAllMessageHandler; + Dictionary> onMessageHandlers = new(); - public InspectorClient(ILogger logger) : base(logger) { } + public InspectorClient(string id, ILogger logger) : base (id, logger) {} Task HandleMessage(string msg, CancellationToken token) { - var res = JObject.Parse(msg); + try + { + var res = JObject.Parse(msg); + string method = res["method"]?.Value(); + + if (res["id"] == null) { + string sidStr = res["sessionId"]?.Value(); + // } + if (sidStr == null) + return _catchAllMessageHandler(res, token); + + var sessionId = new SessionId(sidStr); + if (onMessageHandlers.TryGetValue(sessionId, out var onMessage)) { + // Console.WriteLine($"\tcalling onMessage"); + return onMessage(res["method"].Value(), res["params"] as JObject, token); + } else { + return _catchAllMessageHandler(res, token); + } + } - if (res["id"] == null) - return onEvent(res["method"].Value(), res["params"] as JObject, token); + var id = res.ToObject(); + if (!pending_cmds.Remove(id, out var item)) + logger.LogError ($"Unable to find command {id}"); - var id = res.ToObject(); - if (!pending_cmds.Remove(id, out var item)) - logger.LogError($"Unable to find command {id}"); + item.SetResult(Result.FromJson(res)); + return null; + } catch (Exception ex) { + Console.WriteLine($"------- {ex} -------"); + logger.LogError(ex.ToString()); + // throw; - item.SetResult(Result.FromJson(res)); - return null; + return null; + } } public async Task Connect( Uri uri, - Func onEvent, - Func send, + Func catchAllMessageHandler, CancellationToken token) { + _catchAllMessageHandler = catchAllMessageHandler; + RunLoopStopped += (_, args) => + { + logger.LogDebug($"InspectorClient: Let's fail all the pending cmds (nr: {pending_cmds.Count})!"); + if (args.reason == RunLoopStopReason.Cancelled) + { + foreach (var cmd in pending_cmds.Values) + cmd.SetCanceled(); + } + else + { + //FIXME: um args.ex should be non-null + foreach (var cmd in pending_cmds.Values) + cmd.SetException(args.ex); + } + + }; + await ConnectWithMainLoops(uri, HandleMessage, token); + } - this.onEvent = onEvent; - await ConnectWithMainLoops(uri, HandleMessage, send, token); + public void AddMessageHandlerForSession(SessionId sessionId, Func onEvent) + { + // logger.LogDebug($">>> Adding handler for {sessionId}"); + onMessageHandlers.Add(sessionId, onEvent); } + public void RemoveMessageHandlerForSession(SessionId sessionId) + => onMessageHandlers.Remove(sessionId); + public Task SendCommand(string method, JObject args, CancellationToken token) => SendCommand(new SessionId(null), method, args, token); public Task SendCommand(SessionId sessionId, string method, JObject args, CancellationToken token) { + if (!IsRunning) + throw new InvalidOperationException($"DevToolsClient.RunLoop is not running cmd: {method}"); + int id = ++next_cmd_id; if (args == null) args = new JObject(); @@ -60,15 +109,27 @@ public Task SendCommand(SessionId sessionId, string method, JObject args method = method, @params = args }); + if (sessionId != SessionId.Null) + o["sessionId"] = sessionId.sessionId; var tcs = new TaskCompletionSource(); pending_cmds[new MessageId(sessionId.sessionId, id)] = tcs; var str = o.ToString(); + // logger.LogDebug($"SendCommand: id: {id} method: {method} params: {args}"); + // Console.WriteLine($"SendCommand: id: {id} method: {method} params: {args}"); var bytes = Encoding.UTF8.GetBytes(str); Send(bytes, token); return tcs.Task; } + + protected override void LogState(StringBuilder sb) + { + base.LogState(sb); + sb.Append($"Commands waiting for response: {pending_cmds.Count}\n"); + foreach (MessageId cmd_id in pending_cmds.Keys) + sb.Append($"\t{cmd_id}\n"); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index 892fe613f591c2..e91eef96b4adad 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.IO; using System.Linq; @@ -13,146 +16,111 @@ public class MonoJsTests : DebuggerTestBase [Fact] public async Task FixupNameValueObjectsWithMissingParts() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - await Ready(); - await insp.Ready(async (cli, token) => + var names = new JObject[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - - var names = new JObject[] - { JObject.FromObject(new { name = "Abc" }), JObject.FromObject(new { name = "Def" }), JObject.FromObject(new { name = "Xyz" }) - }; + }; - var values = new JObject[] - { + var values = new JObject[] + { JObject.FromObject(new { value = TObject("testclass") }), JObject.FromObject(new { value = TString("test string") }), - }; + }; - var getters = new JObject[] - { + var getters = new JObject[] + { GetterRes("xyz"), GetterRes("unattached") - }; + }; - var list = new[] { names[0], names[1], values[0], names[2], getters[0], getters[1] }; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression = $"MONO._fixup_name_value_objects({JsonConvert.SerializeObject(list)})", returnByValue = true }), ctx.token); - Assert.True(res.IsOk); + var list = new[] { names[0], names[1], values[0], names[2], getters[0], getters[1] }; + var res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression = $"MONO._fixup_name_value_objects({JsonConvert.SerializeObject(list)})", returnByValue = true }), token); + Assert.True(res.IsOk); - await CheckProps(res.Value["result"]["value"], new - { - Abc = TSymbol(""), - Def = TObject("testclass"), - Xyz = TGetter("xyz") - }, "#1", num_fields: 4); + await CheckProps(res.Value["result"]["value"], new + { + Abc = TSymbol(""), + Def = TObject("testclass"), + Xyz = TGetter("xyz") + }, "#1", num_fields: 4); - JObject.DeepEquals(getters[1], res.Value["result"]["value"].Values().ToArray()[3]); + JObject.DeepEquals(getters[1], res.Value["result"]["value"].Values().ToArray()[3]); - JObject GetterRes(string name) => JObject.FromObject(new + static JObject GetterRes(string name) => JObject.FromObject(new + { + get = new { - get = new - { - className = "Function", - description = $"get {name} () {{}}", - type = "function" - } - }); + className = "Function", + description = $"get {name} () {{}}", + type = "function" + } }); } [Fact] public async Task GetParamsAndLocalsWithInvalidIndices() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", - null, -1, -1, "IntAdd"); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", + null, -1, -1, "IntAdd"); - var scope_id = pause_location["callFrames"][0]["callFrameId"].Value(); - var scope = int.Parse(scope_id.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[2]); + var scope_id = pause_location["callFrames"][0]["callFrameId"].Value(); + var scope = int.Parse(scope_id.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[2]); - var var_ids = new[] - { + var var_ids = new[] + { new { index = 0, name = "one" }, new { index = -12, name = "bad0" }, new { index = 1231, name = "bad1" } }; - var expression = $"MONO.mono_wasm_get_variables({scope}, {JsonConvert.SerializeObject(var_ids)})"; + var expression = $"MONO.mono_wasm_get_variables({scope}, {JsonConvert.SerializeObject(var_ids)})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.True(res.IsOk); + var res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.True(res.IsOk); - await CheckProps(res.Value["result"]?["value"], new - { - one = TNumber(3), - bad0 = TSymbol(""), - bad1 = TSymbol("") - }, "results"); - }); + await CheckProps(res.Value["result"]?["value"], new + { + one = TNumber(3), + bad0 = TSymbol(""), + bad1 = TSymbol("") + }, "results"); } [Fact] public async Task InvalidScopeId() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", + null, -1, -1, "IntAdd"); - await Ready(); - await insp.Ready(async (cli, token) => + var var_ids = new[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", - null, -1, -1, "IntAdd"); - - var var_ids = new[] - { new { index = 0, name = "one" }, }; - var scope_id = "-12"; - var expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.False(res.IsOk); + var scope_id = "-12"; + var expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + var res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.False(res.IsOk); - scope_id = "30000"; - expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; - res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.False(res.IsOk); - }); + scope_id = "30000"; + expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.False(res.IsOk); } [Fact] public async Task BadRaiseDebugEventsTest() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var bad_expressions = new[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bad_expressions = new[] - { "MONO.mono_wasm_raise_debug_event('')", "MONO.mono_wasm_raise_debug_event(undefined)", "MONO.mono_wasm_raise_debug_event({})", @@ -161,17 +129,16 @@ await insp.Ready(async (cli, token) => "MONO.mono_wasm_raise_debug_event({eventName:'foo'}, 12)" }; - foreach (var expression in bad_expressions) - { - var res = await ctx.cli.SendCommand($"Runtime.evaluate", - JObject.FromObject(new - { - expression, - returnByValue = true - }), ctx.token); - Assert.False(res.IsOk, $"Expected to fail for {expression}"); - } - }); + foreach (var expression in bad_expressions) + { + var res = await SendCommand($"Runtime.evaluate", + JObject.FromObject(new + { + expression, + returnByValue = true + }), token); + Assert.False(res.IsOk, $"Expected to fail for {expression}"); + } } [Theory] @@ -180,38 +147,30 @@ await insp.Ready(async (cli, token) => [InlineData(null)] public async Task RaiseDebugEventTraceTest(bool? trace) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var tcs = new TaskCompletionSource(); + insp.On("Runtime.consoleAPICalled", async (args, token) => { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var tcs = new TaskCompletionSource(); - insp.On("Runtime.consoleAPICalled", async (args, token) => { - if (args?["type"]?.Value() == "debug" && - args?["args"]?.Type == JTokenType.Array && - args?["args"]?[0]?["value"]?.Value()?.StartsWith("mono_wasm_debug_event_raised:") == true) - { - tcs.SetResult(true); - } + if (args?["type"]?.Value() == "debug" && + args?["args"]?.Type == JTokenType.Array && + args?["args"]?[0]?["value"]?.Value()?.StartsWith("mono_wasm_debug_event_raised:") == true) + { + tcs.SetResult(true); + } - await Task.CompletedTask; - }); + await Task.CompletedTask; + }); - var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty; - var expression = $"MONO.mono_wasm_raise_debug_event({{ eventName:'qwe' }}, {{ {trace_str} }})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty; + var expression = $"MONO.mono_wasm_raise_debug_event({{ eventName:'qwe' }}, {{ {trace_str} }})"; + var res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); - if (trace == true) - Assert.True(tcs.Task == t, "Timed out waiting for the event to be logged"); - else - Assert.False(tcs.Task == t, "Event should not have been logged"); - }); + if (trace == true) + Assert.True(tcs.Task == t, "Timed out waiting for the event to be logged"); + else + Assert.False(tcs.Task == t, "Event should not have been logged"); } [Theory] @@ -250,8 +209,6 @@ public async Task DuplicateAssemblyLoadedEventWithEmbeddedPdbNotLoadedFromBundle async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_path, string source_file, int expected_count) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); int event_count = 0; var tcs = new TaskCompletionSource(); @@ -262,7 +219,7 @@ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_ var url = args["url"]?.Value(); if (url?.EndsWith(source_file) == true) { - event_count ++; + event_count++; if (event_count > expected_count) tcs.SetResult(false); } @@ -275,40 +232,34 @@ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_ await Task.CompletedTask; }); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - byte[] bytes = File.ReadAllBytes(asm_path); - string asm_base64 = Convert.ToBase64String(bytes); + byte[] bytes = File.ReadAllBytes(asm_path); + string asm_base64 = Convert.ToBase64String(bytes); - string pdb_base64 = String.Empty; - if (pdb_path != null) - { - bytes = File.ReadAllBytes(pdb_path); - pdb_base64 = Convert.ToBase64String(bytes); - } + string pdb_base64 = String.Empty; + if (pdb_path != null) + { + bytes = File.ReadAllBytes(pdb_path); + pdb_base64 = Convert.ToBase64String(bytes); + } - var expression = $@"MONO.mono_wasm_raise_debug_event({{ + var expression = $@"MONO.mono_wasm_raise_debug_event({{ eventName: 'AssemblyLoaded', assembly_name: '{asm_name}', assembly_b64: '{asm_base64}', pdb_b64: '{pdb_base64}' }});"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + var res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + res = await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); - if (t.IsFaulted) - throw t.Exception; + var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + if (t.IsFaulted) + throw t.Exception; - Assert.True(event_count <= expected_count, $"number of scriptParsed events received. Expected: {expected_count}, Actual: {event_count}"); - }); + Assert.True(event_count <= expected_count, $"number of scriptParsed events received. Expected: {expected_count}, Actual: {event_count}"); } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ProxyLauncherData.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ProxyLauncherData.cs new file mode 100644 index 00000000000000..40a3ba378263e1 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ProxyLauncherData.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.WebAssembly.Diagnostics +{ + public class ProxyLauncherData + { + public Dictionary IdToDevToolsUrl { get; } = new (); + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs index 68be3b4e17ff85..351c99556a6ce7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Linq; using System.Threading.Tasks; @@ -11,94 +14,74 @@ public class SteppingTests : DebuggerTestBase [Fact] public async Task TrivalStepping() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd", - wait_for_event_fn: (pause_location) => - { - //make sure we're on the right bp - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd", + wait_for_event_fn: (pause_location) => + { + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); - return Task.CompletedTask; - } - ); + var top_frame = pause_location["callFrames"][0]; + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + return Task.CompletedTask; + } + ); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 11, 8, "IntAdd", - wait_for_event_fn: (pause_location) => - { - var top_frame = pause_location["callFrames"][0]; - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); - return Task.CompletedTask; - } - ); - }); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 11, 8, "IntAdd", + wait_for_event_fn: (pause_location) => + { + var top_frame = pause_location["callFrames"][0]; + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + return Task.CompletedTask; + } + ); } [Fact] public async Task InspectLocalsDuringStepping() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(debugger_test_loc, 10, 8); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + debugger_test_loc, 10, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 0); + CheckNumber(locals, "e", 0); + } + ); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint(debugger_test_loc, 10, 8); + await StepAndCheck(StepKind.Over, debugger_test_loc, 11, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 50); + CheckNumber(locals, "e", 0); + } + ); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); }, 1);", - debugger_test_loc, 10, 8, "IntAdd", - locals_fn: (locals) => - { - CheckNumber(locals, "a", 10); - CheckNumber(locals, "b", 20); - CheckNumber(locals, "c", 30); - CheckNumber(locals, "d", 0); - CheckNumber(locals, "e", 0); - } - ); - - await StepAndCheck(StepKind.Over, debugger_test_loc, 11, 8, "IntAdd", - locals_fn: (locals) => - { - CheckNumber(locals, "a", 10); - CheckNumber(locals, "b", 20); - CheckNumber(locals, "c", 30); - CheckNumber(locals, "d", 50); - CheckNumber(locals, "e", 0); - } - ); - - //step and get locals - await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd", - locals_fn: (locals) => - { - CheckNumber(locals, "a", 10); - CheckNumber(locals, "b", 20); - CheckNumber(locals, "c", 30); - CheckNumber(locals, "d", 50); - CheckNumber(locals, "e", 60); - } - ); - }); + //step and get locals + await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd", + locals_fn: (locals) => + { + CheckNumber(locals, "a", 10); + CheckNumber(locals, "b", 20); + CheckNumber(locals, "c", 30); + CheckNumber(locals, "d", 50); + CheckNumber(locals, "e", 60); + } + ); } [Theory] @@ -106,75 +89,66 @@ await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd", [InlineData(true)] public async Task InspectLocalsInPreviousFramesDuringSteppingIn2(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; - var dep_cs_loc = "dotnet://debugger-test.dll/dependency.cs"; - await SetBreakpoint(dep_cs_loc, 33, 8); + var dep_cs_loc = "dotnet://debugger-test.dll/dependency.cs"; + await SetBreakpoint(dep_cs_loc, 33, 8); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - // Will stop in Complex.DoEvenMoreStuff - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_use_complex (); }, 1);", - dep_cs_loc, 33, 8, "DoEvenMoreStuff", - locals_fn: (locals) => - { - Assert.Single(locals); - CheckObject(locals, "this", "Simple.Complex"); - } - ); - - var props = await GetObjectOnFrame(pause_location["callFrames"][0], "this"); - Assert.Equal(3, props.Count()); - CheckNumber(props, "A", 10); - CheckString(props, "B", "xx"); - CheckString(props, "c", "20_xx"); - - // Check UseComplex frame - var locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][3], debugger_test_loc, 23, 8, "UseComplex"); - 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); - - props = await GetObjectOnFrame(pause_location["callFrames"][3], "complex"); - Assert.Equal(3, props.Count()); - CheckNumber(props, "A", 10); - CheckString(props, "B", "xx"); - CheckString(props, "c", "20_xx"); - - pause_location = await StepAndCheck(StepKind.Over, dep_cs_loc, 23, 8, "DoStuff", times: 2); - // Check UseComplex frame again - locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][1], debugger_test_loc, 23, 8, "UseComplex"); - 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); - - props = await GetObjectOnFrame(pause_location["callFrames"][1], "complex"); - Assert.Equal(3, props.Count()); - CheckNumber(props, "A", 10); - CheckString(props, "B", "xx"); - CheckString(props, "c", "20_xx"); - }); + // Will stop in Complex.DoEvenMoreStuff + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_use_complex (); }, 1);", + dep_cs_loc, 33, 8, "DoEvenMoreStuff", + locals_fn: (locals) => + { + Assert.Single(locals); + CheckObject(locals, "this", "Simple.Complex"); + } + ); + + var props = await GetObjectOnFrame(pause_location["callFrames"][0], "this"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckString(props, "c", "20_xx"); + + // Check UseComplex frame + var locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][3], debugger_test_loc, 23, 8, "UseComplex"); + 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); + + props = await GetObjectOnFrame(pause_location["callFrames"][3], "complex"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckString(props, "c", "20_xx"); + + pause_location = await StepAndCheck(StepKind.Over, dep_cs_loc, 23, 8, "DoStuff", times: 2); + // Check UseComplex frame again + locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][1], debugger_test_loc, 23, 8, "UseComplex"); + 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); + + props = await GetObjectOnFrame(pause_location["callFrames"][1], "complex"); + Assert.Equal(3, props.Count()); + CheckNumber(props, "A", 10); + CheckString(props, "B", "xx"); + CheckString(props, "c", "20_xx"); } [Theory] @@ -182,178 +156,159 @@ await insp.Ready(async (cli, token) => [InlineData(true)] public async Task InspectLocalsInPreviousFramesDuringSteppingIn(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(debugger_test_loc, 111, 12); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint(debugger_test_loc, 111, 12); + // Will stop in InnerMethod + var wait_res = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_outer_method(); }, 1);", + debugger_test_loc, 111, 12, "InnerMethod", + 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"); + } + ); - // Will stop in InnerMethod - var wait_res = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_outer_method(); }, 1);", - debugger_test_loc, 111, 12, "InnerMethod", - 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"); - } - ); - - var this_props = await GetObjectOnFrame(wait_res["callFrames"][0], "this"); - Assert.Equal(2, this_props.Count()); - CheckObject(this_props, "m", "Math"); - CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); - - var ss_props = await GetObjectOnLocals(this_props, "SimpleStructProperty"); - var dt = new DateTime(2020, 1, 2, 3, 4, 5); - await CheckProps(ss_props, new + var this_props = await GetObjectOnFrame(wait_res["callFrames"][0], "this"); + Assert.Equal(2, this_props.Count()); + CheckObject(this_props, "m", "Math"); + CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); + + var ss_props = await GetObjectOnLocals(this_props, "SimpleStructProperty"); + var dt = new DateTime(2020, 1, 2, 3, 4, 5); + await CheckProps(ss_props, new + { + dt = TDateTime(dt), + gs = TValueType("Math.GenericStruct") + }, "ss_props"); + + // Check OuterMethod frame + var locals_m1 = await GetLocalsForFrame(wait_res["callFrames"][1], debugger_test_loc, 87, 8, "OuterMethod"); + 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, debugger_test_loc, 91, 8, "OuterMethod", times: 9, + locals_fn: (locals) => { - dt = TDateTime(dt), - gs = TValueType("Math.GenericStruct") - }, "ss_props"); - - // Check OuterMethod frame - var locals_m1 = await GetLocalsForFrame(wait_res["callFrames"][1], debugger_test_loc, 87, 8, "OuterMethod"); - 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, debugger_test_loc, 91, 8, "OuterMethod", times: 9, - locals_fn: (locals) => - { - Assert.Equal(5, locals.Count()); + 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"); + } + ); - // 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", times: 2); - //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", times: 2); + // step into InnerMethod2 + await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 4, "InnerMethod2", + locals_fn: (locals) => + { + Assert.Equal(3, locals.Count()); - // step into InnerMethod2 - await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 4, "InnerMethod2", - locals_fn: (locals) => - { - Assert.Equal(3, locals.Count()); + CheckString(locals, "s", "test string"); + //out var: CheckNumber (locals, "k", 0); + CheckNumber(locals, "i", 24); + } + ); - 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", 100, 4, "InnerMethod2", times: 4, + locals_fn: (locals) => + { + Assert.Equal(3, locals.Count()); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 100, 4, "InnerMethod2", times: 4, - locals_fn: (locals) => - { - Assert.Equal(3, locals.Count()); + CheckString(locals, "s", "test string"); + // FIXME: Failing test CheckNumber (locals, "k", 34); + CheckNumber(locals, "i", 24); + } + ); - 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", 92, 8, "OuterMethod", times: 2, + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 92, 8, "OuterMethod", 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"); - } - ); - }); + 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 SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 86, 8); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + await EvaluateAndCheck("window.setTimeout(function() { invoke_outer_method(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 86, 8, "OuterMethod", + locals_fn: (locals) => + { + Assert.Equal(5, locals.Count()); - await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 86, 8); + CheckObject(locals, "nim", "Math.NestedInMath"); + CheckNumber(locals, "i", 5); + CheckNumber(locals, "k", 0); + CheckNumber(locals, "new_i", 0); + CheckString(locals, "text", null); + } + ); - await EvaluateAndCheck("window.setTimeout(function() { invoke_outer_method(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 86, 8, "OuterMethod", - locals_fn: (locals) => - { - Assert.Equal(5, locals.Count()); - - CheckObject(locals, "nim", "Math.NestedInMath"); - CheckNumber(locals, "i", 5); - CheckNumber(locals, "k", 0); - CheckNumber(locals, "new_i", 0); - CheckString(locals, "text", null); - } - ); - - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 87, 8, "OuterMethod", - 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", 105, 8, "InnerMethod"); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 109, 12, "InnerMethod", 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", 88, 8, "OuterMethod", times: 6, - 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"); - } - ); - }); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 87, 8, "OuterMethod", + 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", 105, 8, "InnerMethod"); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 109, 12, "InnerMethod", 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", 88, 8, "OuterMethod", times: 6, + 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"); + } + ); } [Theory] @@ -361,64 +316,54 @@ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", [InlineData(true)] public async Task InspectLocalsInAsyncMethods(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - - await SetBreakpoint(debugger_test_loc, 120, 12); - await SetBreakpoint(debugger_test_loc, 135, 12); - - // Will stop in Asyncmethod0 - var wait_res = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);", - debugger_test_loc, 120, 12, "MoveNext", //FIXME: - locals_fn: (locals) => - { - Assert.Equal(4, locals.Count()); - CheckString(locals, "s", "string from js"); - CheckNumber(locals, "i", 42); - CheckString(locals, "local0", "value0"); - CheckObject(locals, "this", "Math.NestedInMath"); - } - ); - Console.WriteLine(wait_res); + await SetBreakpoint(debugger_test_loc, 120, 12); + await SetBreakpoint(debugger_test_loc, 135, 12); + + // Will stop in Asyncmethod0 + var wait_res = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);", + debugger_test_loc, 120, 12, "MoveNext", //FIXME: + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + CheckString(locals, "s", "string from js"); + CheckNumber(locals, "i", 42); + CheckString(locals, "local0", "value0"); + CheckObject(locals, "this", "Math.NestedInMath"); + } + ); #if false // Disabled for now, as we don't have proper async traces - var locals = await GetProperties(wait_res["callFrames"][2]["callFrameId"].Value()); - Assert.Equal(4, locals.Count()); - CheckString(locals, "ls", "string from jstest"); - CheckNumber(locals, "li", 52); + var locals = await GetProperties(wait_res["callFrames"][2]["callFrameId"].Value()); + Assert.Equal(4, locals.Count()); + CheckString(locals, "ls", "string from jstest"); + CheckNumber(locals, "li", 52); #endif - // TODO: previous frames have async machinery details, so no point checking that right now + // TODO: previous frames have async machinery details, so no point checking that right now - var pause_loc = await SendCommandAndCheck(null, "Debugger.resume", debugger_test_loc, 135, 12, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext", - locals_fn: (locals) => - { - Assert.Equal(4, locals.Count()); - CheckString(locals, "str", "AsyncMethodNoReturn's local"); - CheckObject(locals, "this", "Math.NestedInMath"); - //FIXME: check fields - CheckValueType(locals, "ss", "Math.SimpleStruct"); - CheckArray(locals, "ss_arr", "Math.SimpleStruct[]", 0); - // TODO: struct fields - } - ); - - var this_props = await GetObjectOnFrame(pause_loc["callFrames"][0], "this"); - Assert.Equal(2, this_props.Count()); - CheckObject(this_props, "m", "Math"); - CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); - - // TODO: Check `this` properties - }); + var pause_loc = await SendCommandAndCheck(null, "Debugger.resume", debugger_test_loc, 135, 12, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext", + locals_fn: (locals) => + { + Assert.Equal(4, locals.Count()); + CheckString(locals, "str", "AsyncMethodNoReturn's local"); + CheckObject(locals, "this", "Math.NestedInMath"); + //FIXME: check fields + CheckValueType(locals, "ss", "Math.SimpleStruct"); + CheckArray(locals, "ss_arr", "Math.SimpleStruct[]", 0); + // TODO: struct fields + } + ); + + var this_props = await GetObjectOnFrame(pause_loc["callFrames"][0], "this"); + Assert.Equal(2, this_props.Count()); + CheckObject(this_props, "m", "Math"); + CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct"); + + // TODO: Check `this` properties } [Theory] @@ -426,159 +371,141 @@ await insp.Ready(async (cli, token) => [InlineData(true)] public async Task InspectValueTypeMethodArgsWhileStepping(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await SetBreakpoint(debugger_test_loc, 36, 12); + await SetBreakpoint(debugger_test_loc, 36, 12); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);", - debugger_test_loc, 36, 12, "MethodWithStructArgs"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - { - Assert.Equal(3, locals.Count()); - CheckString(locals, "label", "TestStructsAsMethodArgs#label"); - CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckNumber(locals, "x", 3); - } + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);", + debugger_test_loc, 36, 12, "MethodWithStructArgs"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + { + Assert.Equal(3, locals.Count()); + CheckString(locals, "label", "TestStructsAsMethodArgs#label"); + CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckNumber(locals, "x", 3); + } - var dt = new DateTime(2025, 6, 7, 8, 10, 11); - var ss_local_as_ss_arg = new - { - V = TGetter("V"), - str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Local") - }; - var ss_local_gs = new - { - StringField = TString("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option1") - }; + var dt = new DateTime(2025, 6, 7, 8, 10, 11); + var ss_local_as_ss_arg = new + { + V = TGetter("V"), + str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Local") + }; + var ss_local_gs = new + { + StringField = TString("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + }; - // Check ss_arg's properties - var ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); - await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_arg"); + // Check ss_arg's properties + var ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_arg"); - var res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); - await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); + var res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); + await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); - { - // Check ss_local.gs - await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs); - } + { + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs); + } - pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 40, 8, "MethodWithStructArgs", times: 4, - locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); - locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - { - Assert.Equal(3, locals.Count()); + pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 40, 8, "MethodWithStructArgs", times: 4, + locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + { + Assert.Equal(3, locals.Count()); - CheckString(locals, "label", "TestStructsAsMethodArgs#label"); - CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckNumber(locals, "x", 3); - } + CheckString(locals, "label", "TestStructsAsMethodArgs#label"); + CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckNumber(locals, "x", 3); + } - var ss_arg_updated = new - { - V = TGetter("V"), - str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Utc") - }; + var ss_arg_updated = new + { + V = TGetter("V"), + str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }; - ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); - await CheckProps(ss_arg_props, ss_arg_updated, "ss_arg"); + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg"); + await CheckProps(ss_arg_props, ss_arg_updated, "ss_arg"); - res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); - await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); + res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V"); + await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); + { + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", new { - // Check ss_local.gs - await CompareObjectPropertiesFor(ss_arg_props, "gs", new - { - StringField = TString("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option1") - }); - } + StringField = TString("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + }); + } - // Check locals on previous frame, same as earlier in this test - ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][1], "ss_local"); - await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); + // Check locals on previous frame, same as earlier in this test + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][1], "ss_local"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); - { - // Check ss_local.dt - await CheckDateTime(ss_arg_props, "dt", dt); + { + // Check ss_local.dt + await CheckDateTime(ss_arg_props, "dt", dt); - // Check ss_local.gs - var gs_props = await GetObjectOnLocals(ss_arg_props, "gs"); - CheckString(gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"); - CheckObject(gs_props, "List", "System.Collections.Generic.List"); - } + // Check ss_local.gs + var gs_props = await GetObjectOnLocals(ss_arg_props, "gs"); + CheckString(gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"); + CheckObject(gs_props, "List", "System.Collections.Generic.List"); + } - // ----------- Step back to the caller --------- + // ----------- Step back to the caller --------- - pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 30, 12, "TestStructsAsMethodArgs", - times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); - locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckProps(locals, new - { - ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), - ss_ret = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct") - }, - "locals#0"); + pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 30, 12, "TestStructsAsMethodArgs", + times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */ }); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + ss_ret = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct") + }, + "locals#0"); - ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); - await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); + ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local"); - { - // Check ss_local.gs - await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs"); - } + { + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs"); + } - // FIXME: check ss_local.gs.List's members - }); + // FIXME: check ss_local.gs.List's members } [Fact] public async Task CheckUpdatedValueTypeFieldsOnResume() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + var lines = new[] { 205, 208 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); - var lines = new[] { 205, 208 }; - await SetBreakpoint(debugger_test_loc, lines[0], 12); - await SetBreakpoint(debugger_test_loc, lines[1], 12); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);", + debugger_test_loc, lines[0], 12, "MethodUpdatingValueTypeMembers"); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);", - debugger_test_loc, lines[0], 12, "MethodUpdatingValueTypeMembers"); + await CheckLocals(pause_location, new DateTime(1, 2, 3, 4, 5, 6), new DateTime(4, 5, 6, 7, 8, 9)); - await CheckLocals(pause_location, new DateTime(1, 2, 3, 4, 5, 6), new DateTime(4, 5, 6, 7, 8, 9)); - - // Resume - pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingValueTypeMembers"); - await CheckLocals(pause_location, new DateTime(9, 8, 7, 6, 5, 4), new DateTime(5, 1, 3, 7, 9, 10)); - }); + // Resume + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingValueTypeMembers"); + await CheckLocals(pause_location, new DateTime(9, 8, 7, 6, 5, 4), new DateTime(5, 1, 3, 7, 9, 10)); async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt) { @@ -610,64 +537,46 @@ async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt) [Fact] public async Task CheckUpdatedValueTypeLocalsOnResumeAsync() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + var lines = new[] { 214, 216 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); - var lines = new[] { 214, 216 }; - await SetBreakpoint(debugger_test_loc, lines[0], 12); - await SetBreakpoint(debugger_test_loc, lines[1], 12); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);", + debugger_test_loc, lines[0], 12, "MoveNext"); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);", - debugger_test_loc, lines[0], 12, "MoveNext"); + var dt = new DateTime(1, 2, 3, 4, 5, 6); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckDateTime(locals, "dt", dt); - var dt = new DateTime(1, 2, 3, 4, 5, 6); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckDateTime(locals, "dt", dt); - - // Resume - dt = new DateTime(9, 8, 7, 6, 5, 4); - pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MoveNext"); - locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckDateTime(locals, "dt", dt); - }); + // Resume + dt = new DateTime(9, 8, 7, 6, 5, 4); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MoveNext"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckDateTime(locals, "dt", dt); } [Fact] public async Task CheckUpdatedVTArrayMembersOnResume() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - var lines = new[] { 225, 227 }; - await SetBreakpoint(debugger_test_loc, lines[0], 12); - await SetBreakpoint(debugger_test_loc, lines[1], 12); + var lines = new[] { 225, 227 }; + await SetBreakpoint(debugger_test_loc, lines[0], 12); + await SetBreakpoint(debugger_test_loc, lines[1], 12); - var dt = new DateTime(1, 2, 3, 4, 5, 6); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);", - debugger_test_loc, lines[0], 12, "MethodUpdatingVTArrayMembers"); - await CheckArrayElements(pause_location, dt); + var dt = new DateTime(1, 2, 3, 4, 5, 6); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);", + debugger_test_loc, lines[0], 12, "MethodUpdatingVTArrayMembers"); + await CheckArrayElements(pause_location, dt); - // Resume - dt = new DateTime(9, 8, 7, 6, 5, 4); - pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingVTArrayMembers"); - await CheckArrayElements(pause_location, dt); - }); + // Resume + dt = new DateTime(9, 8, 7, 6, 5, 4); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingVTArrayMembers"); + await CheckArrayElements(pause_location, dt); async Task CheckArrayElements(JToken pause_location, DateTime dt) { @@ -689,440 +598,268 @@ async Task CheckArrayElements(JToken pause_location, DateTime dt) [Fact] public async Task SteppingIntoMscorlib() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 83, 8); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 83, 8, + "OuterMethod"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 83, 8); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 83, 8, - "OuterMethod"); - - //make sure we're on the right bp - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - pause_location = await SendCommandAndCheck(null, $"Debugger.stepInto", null, -1, -1, null); - var top_frame = pause_location["callFrames"][0]; + pause_location = await SendCommandAndCheck(null, $"Debugger.stepInto", null, -1, -1, null); + var top_frame = pause_location["callFrames"][0]; - AssertEqual("WriteLine", top_frame["functionName"]?.Value(), "Expected to be in WriteLine method"); - var script_id = top_frame["functionLocation"]["scriptId"].Value(); - Assert.Matches ("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]); - }); + AssertEqual("WriteLine", top_frame["functionName"]?.Value(), "Expected to be in WriteLine method"); + var script_id = top_frame["functionLocation"]["scriptId"].Value(); + Assert.Matches("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]); } [Fact] public async Task CreateGoodBreakpointAndHitAndRemoveAndDontHit() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd"); - - Assert.Equal("other", pause_location["reason"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - - await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); - await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); - await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); - }); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd"); + + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); } [Fact] public async Task CreateGoodBreakpointAndHitAndRemoveTwice() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd"); - - Assert.Equal("other", pause_location["reason"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - - await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); - await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); - }); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd"); + + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); + await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); } [Fact] public async Task CreateGoodBreakpointAndHitAndRemoveAndDontHitAndCreateAgainAndHit() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); invoke_add(); invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd"); - - Assert.Equal("other", pause_location["reason"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - - await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); - await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); - await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); - bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd"); - - }); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); invoke_add(); invoke_add(); invoke_add()}, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd"); + + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + await RemoveBreakpoint(bp.Value["breakpointId"]?.ToString()); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 12, 8, "IntAdd"); + bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd"); } // [Fact] //https://github.com/dotnet/runtime/issues/42421 public async Task BreakAfterAwaitThenStepOverTillBackToCaller() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, - "MoveNext"); + var bp = await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, + "MoveNext"); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 23, 12, "MoveNext"); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 23, 12, "MoveNext"); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 24, 8, "MoveNext"); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 24, 8, "MoveNext"); - await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 15, 12, "MoveNext"); - }); + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-async-step.cs", 15, 12, "MoveNext"); } // [Fact] //[ActiveIssue("https://github.com/dotnet/runtime/issues/42421")] public async Task StepOutOfAsyncMethod() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, - "MoveNext"); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, + "MoveNext"); - await StepAndCheck(StepKind.Out, source_file, 15, 4, "TestAsyncStepOut"); - }); + await StepAndCheck(StepKind.Out, source_file, 15, 4, "TestAsyncStepOut"); } [Fact] public async Task ResumeOutOfAsyncMethodToAsyncCallerWithBreakpoint() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, - "MoveNext"); - - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut", 2); - await SendCommandAndCheck(null, "Debugger.resume", source_file, 16, 8, "MoveNext"); - }); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut2", 2); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:TestAsyncStepOut'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 21, 12, + "MoveNext"); + + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "TestAsyncStepOut", 2); + await SendCommandAndCheck(null, "Debugger.resume", source_file, 16, 8, "MoveNext"); } [Fact] public async Task StepOutOfNonAsyncMethod() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "OtherMethod0", 1); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:SimpleMethod'); }, 1);", - source_file, -1, -1, - "OtherMethod0"); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "OtherMethod0", 1); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:SimpleMethod'); }, 1);", + source_file, -1, -1, + "OtherMethod0"); - await StepAndCheck(StepKind.Out, source_file, 29, 12, "SimpleMethod"); - }); + await StepAndCheck(StepKind.Out, source_file, 29, 12, "SimpleMethod"); } [Fact] public async Task BreakOnAwaitThenStepOverToNextAwaitCall() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 2); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 53, 12, - "MoveNext"); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 2); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 53, 12, + "MoveNext"); - await StepAndCheck(StepKind.Over, source_file, 54, 12, "MoveNext"); - }); + await StepAndCheck(StepKind.Over, source_file, 54, 12, "MoveNext"); } [Fact] public async Task BreakOnAwaitThenStepOverToNextLine() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 1); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 46, 12, - "MoveNext"); - - // BUG: chrome: not able to show any bp line indicator - await StepAndCheck(StepKind.Over, source_file, 47, 12, "MoveNext"); - }); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 1); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 46, 12, + "MoveNext"); + + // BUG: chrome: not able to show any bp line indicator + await StepAndCheck(StepKind.Over, source_file, 47, 12, "MoveNext"); } [Fact] public async Task BreakOnAwaitThenResumeToNextBreakpoint() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 1); - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 3); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 1); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "StepOverTestAsync", 3); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 46, 12, - "MoveNext"); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 46, 12, + "MoveNext"); - await StepAndCheck(StepKind.Resume, source_file, 48, 8, "MoveNext"); - }); + await StepAndCheck(StepKind.Resume, source_file, 48, 8, "MoveNext"); } [Fact] public async Task BreakOnAwaitThenResumeToNextBreakpointAfterSecondAwaitInSameMethod() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; + string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 1); - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 5); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 1); + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.AsyncStepClass", "MethodWithTwoAwaitsAsync", 5); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", - "dotnet://debugger-test.dll/debugger-async-step.cs", 52, 12, - "MoveNext"); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] DebuggerTests.AsyncStepClass:StepOverTestAsync'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-step.cs", 52, 12, + "MoveNext"); - await StepAndCheck(StepKind.Resume, source_file, 56, 12, "MoveNext"); - }); + await StepAndCheck(StepKind.Resume, source_file, 56, 12, "MoveNext"); } [Fact] public async Task BreakOnMethodCalledFromHiddenLine() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + await SetBreakpointInMethod("debugger-test.dll", "HiddenSequencePointTest", "StepOverHiddenSP2", 0); - await SetBreakpointInMethod("debugger-test.dll", "HiddenSequencePointTest", "StepOverHiddenSP2", 0); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, + "StepOverHiddenSP2"); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, - "StepOverHiddenSP2"); + // Check previous frame + var top_frame = pause_location["callFrames"][1]; + Assert.Equal("StepOverHiddenSP", top_frame["functionName"].Value()); + Assert.Contains("debugger-test.cs", top_frame["url"].Value()); - // Check previous frame - var top_frame = pause_location["callFrames"][1]; - Assert.Equal("StepOverHiddenSP", top_frame["functionName"].Value()); - Assert.Contains("debugger-test.cs", top_frame["url"].Value()); - - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 537, 8, scripts, top_frame["location"]); - }); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 537, 8, scripts, top_frame["location"]); } [Fact] public async Task StepOverHiddenLinesShouldResumeAtNextAvailableLineInTheMethod() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - string source_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint(source_loc, 537, 8); + string source_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(source_loc, 537, 8); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 537, 8, - "StepOverHiddenSP"); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 537, 8, + "StepOverHiddenSP"); - await StepAndCheck(StepKind.Over, source_loc, 542, 8, "StepOverHiddenSP"); - }); + await StepAndCheck(StepKind.Over, source_loc, 542, 8, "StepOverHiddenSP"); } [Fact] async Task StepOverHiddenLinesInMethodWithNoNextAvailableLineShouldResumeAtCallSite() { - var insp = new Inspector(); + string source_loc = "dotnet://debugger-test.dll/debugger-test.cs"; + await SetBreakpoint(source_loc, 552, 8); - //Collect events - var scripts = SubscribeToScripts(insp); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 552, 8, + "MethodWithHiddenLinesAtTheEnd"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - string source_loc = "dotnet://debugger-test.dll/debugger-test.cs"; - await SetBreakpoint(source_loc, 552, 8); - - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 552, 8, - "MethodWithHiddenLinesAtTheEnd"); - - await StepAndCheck(StepKind.Over, source_loc, 544, 4, "StepOverHiddenSP"); - }); + await StepAndCheck(StepKind.Over, source_loc, 544, 4, "StepOverHiddenSP"); } // [Fact] // Issue: https://github.com/dotnet/runtime/issues/42704 async Task BreakpointOnHiddenLineShouldStopAtEarliestNextAvailableLine() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 539, 8); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, - "StepOverHiddenSP2"); - }); + await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 539, 8); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, + "StepOverHiddenSP2"); } [Fact] public async Task BreakpointOnHiddenLineOfMethodWithNoNextVisibleLineShouldNotPause() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 554, 12); + await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 554, 12); - string expression = "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);"; - await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); + string expression = "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);"; + await SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); - Task pause_task = insp.WaitFor(Inspector.PAUSE); - Task t = await Task.WhenAny(pause_task, Task.Delay(2000)); - Assert.True(t != pause_task, "Debugger unexpectedly paused"); - }); + Task pause_task = insp.WaitFor(Inspector.PAUSE); + Task t = await Task.WhenAny(pause_task, Task.Delay(2000)); + Assert.True(t != pause_task, "Debugger unexpectedly paused"); } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs index d50a8454949311..0427d528e5f8b9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs @@ -2,32 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; + +#nullable enable namespace Microsoft.WebAssembly.Diagnostics { public class TestHarnessProxy { - static IWebHost host; - static Task hostTask; + static IWebHost? host; + static Task? hostTask; static CancellationTokenSource cts = new CancellationTokenSource(); static object proxyLock = new object(); - public static readonly Uri Endpoint = new Uri("http://localhost:9400"); + private static Uri? s_endpoint = null; + public static Uri Endpoint => s_endpoint ?? throw new ArgumentException($"Cannot access `{nameof(Endpoint)}` before `{nameof(TestHarnessProxy)}` has been started"); + public static ProxyLauncherData LauncherData { get; } = new (); - public static Task Start(string chromePath, string appPath, string pagePath) + public static Task Start(string appPath, ILogger logger, CancellationToken token) { lock (proxyLock) { - if (host != null) + if (host != null && hostTask != null) return hostTask; host = WebHost.CreateDefaultBuilder() @@ -38,27 +43,38 @@ public static Task Start(string chromePath, string appPath, string pagePath) }) .ConfigureLogging(logging => { - logging.AddConsole(); + logging.AddSimpleConsole(c => + { + c.ColorBehavior = LoggerColorBehavior.Enabled; + c.TimestampFormat = "[HH:mm:ss.fff] "; + c.SingleLine = true; + }); }) .ConfigureServices((ctx, services) => { services.Configure(ctx.Configuration); services.Configure(options => { - options.ChromePath = options.ChromePath ?? chromePath; options.AppPath = appPath; - options.PagePath = pagePath; options.DevToolsUrl = new Uri("http://localhost:0"); }); }) .UseStartup() - .UseUrls(Endpoint.ToString()) + .UseUrls("http://127.0.0.1:0") .Build(); - hostTask = host.StartAsync(cts.Token); + + logger.LogDebug("Starting webserver, and the proxy launcher"); + hostTask = host.StartAsync(cts.Token).ContinueWith(t => + { + s_endpoint = new Uri(host.ServerFeatures + .Get() + .Addresses + .First()); + }, TaskContinuationOptions.OnlyOnRanToCompletion); } - Console.WriteLine("WebServer Ready!"); + logger.LogDebug("WebServer Ready!"); return hostTask; } } -} \ No newline at end of file +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs index e7be4958421bed..afb8485f85b335 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs @@ -2,10 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; using System.IO; -using System.Net.Http; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -18,22 +15,20 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace Microsoft.WebAssembly.Diagnostics { public class TestHarnessStartup { - static Regex parseConnection = new Regex(@"listening on (ws?s://[^\s]*)"); public TestHarnessStartup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; set; } - public ILogger Logger { get; private set; } - + public ILogger ServerLogger { get; private set; } private ILoggerFactory _loggerFactory; // This method gets called by the runtime. Use this method to add services to the container. @@ -46,7 +41,7 @@ public void ConfigureServices(IServiceCollection services) async Task SendNodeVersion(HttpContext context) { - Logger.LogTrace("hello chrome! json/version"); + ServerLogger.LogTrace("hello chrome! json/version"); var resp_obj = new JObject(); resp_obj["Browser"] = "node.js/v9.11.1"; resp_obj["Protocol-Version"] = "1.1"; @@ -57,7 +52,7 @@ async Task SendNodeVersion(HttpContext context) async Task SendNodeList(HttpContext context) { - Logger.LogTrace("webserver: hello chrome! json/list"); + ServerLogger.LogTrace("webserver: hello chrome! json/list"); try { var response = new JArray(JObject.FromObject(new @@ -71,190 +66,108 @@ async Task SendNodeList(HttpContext context) webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4" })).ToString(); - Logger.LogTrace($"webserver: sending: {response}"); + ServerLogger.LogTrace($"webserver: sending: {response}"); await context.Response.WriteAsync(response, new CancellationTokenSource().Token); } - catch (Exception e) { Logger.LogError(e, "webserver: SendNodeList failed"); } - } - - public async Task LaunchAndServe(ProcessStartInfo psi, HttpContext context, Func> extract_conn_url) - { - - if (!context.WebSockets.IsWebSocketRequest) - { - context.Response.StatusCode = 400; - return; - } - - var tcs = new TaskCompletionSource(); - - var proc = Process.Start(psi); - try - { - proc.ErrorDataReceived += (sender, e) => - { - var str = e.Data; - Logger.LogTrace($"browser-stderr: {str}"); - - if (tcs.Task.IsCompleted) - return; - - var match = parseConnection.Match(str); - if (match.Success) - { - tcs.TrySetResult(match.Groups[1].Captures[0].Value); - } - }; - - proc.OutputDataReceived += (sender, e) => - { - Logger.LogTrace($"browser-stdout: {e.Data}"); - }; - - proc.BeginErrorReadLine(); - proc.BeginOutputReadLine(); - - if (await Task.WhenAny(tcs.Task, Task.Delay(5000)) != tcs.Task) - { - Logger.LogError("Didnt get the con string after 5s."); - throw new Exception("node.js timedout"); - } - var line = await tcs.Task; - var con_str = extract_conn_url != null ? await extract_conn_url(line) : line; - - Logger.LogInformation($"launching proxy for {con_str}"); - - var proxy = new DebuggerProxy(_loggerFactory, null); - var browserUri = new Uri(con_str); - var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); - - await proxy.Run(browserUri, ideSocket); - Logger.LogInformation("Proxy done"); - } - catch (Exception e) - { - Logger.LogError("got exception {0}", e); - } - finally - { - proc.CancelErrorRead(); - proc.CancelOutputRead(); - proc.Kill(); - proc.WaitForExit(); - proc.Close(); - } + catch (Exception e) { ServerLogger.LogError(e, "webserver: SendNodeList failed"); } } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env, ILogger logger, ILoggerFactory loggerFactory) { - this.Logger = logger; + this.ServerLogger = logger; this._loggerFactory = loggerFactory; + // _launcherData = app.ApplicationServices.GetRequiredService(); + app.UseWebSockets(); - app.UseStaticFiles(); + // app.UseStaticFiles(); TestHarnessOptions options = optionsAccessor.CurrentValue; var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; + foreach (var extn in new string[] { ".dll", ".pdb", ".dat", ".blat" }) + { + provider.Mappings[extn] = "application/octet-stream"; + } + + ServerLogger.LogInformation($"Starting webserver with appPath: {options.AppPath}"); + app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(options.AppPath), ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry: - RequestPath = "", + // RequestPath = "", ContentTypeProvider = provider }); var devToolsUrl = options.DevToolsUrl; app.UseRouter(router => { - router.MapGet("launch-chrome-and-connect", async context => + router.MapGet("/connect-to-devtools/{browserInstanceId:int:required}", async context => { - Logger.LogInformation("New test request"); try { - var client = new HttpClient(); - var psi = new ProcessStartInfo(); - - psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}"; - psi.UseShellExecute = false; - psi.FileName = options.ChromePath; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; - - await LaunchAndServe(psi, context, async (str) => - { - var start = DateTime.Now; - JArray obj = null; - - while (true) - { - // Unfortunately it does look like we have to wait - // for a bit after getting the response but before - // making the list request. We get an empty result - // if we make the request too soon. - await Task.Delay(100); - - var res = await client.GetStringAsync(new Uri(new Uri(str), "/json/list")); - Logger.LogTrace("res is {0}", res); - - if (!String.IsNullOrEmpty(res)) - { - // Sometimes we seem to get an empty array `[ ]` - obj = JArray.Parse(res); - if (obj != null && obj.Count >= 1) - break; - } - - var elapsed = DateTime.Now - start; - if (elapsed.Milliseconds > 5000) - { - Logger.LogError($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping"); - return null; - } - } - - var wsURl = obj[0]?["webSocketDebuggerUrl"]?.Value(); - Logger.LogTrace(">>> {0}", wsURl); - - return wsURl; - }); + var id = context.Request.RouteValues["browserInstanceId"].ToString(); + string testId = "unknown"; + if (context.Request.Query.TryGetValue("testId", out StringValues values)) + testId = values.ToString(); + + var testLogger = _loggerFactory.CreateLogger($"{typeof(TestHarnessProxy)}-{testId}"); + + testLogger.LogDebug($"New test request for browserId: {id}, test_id: {testId}, with kestrel connection id: {context.Connection.Id}"); + if (!TestHarnessProxy.LauncherData.IdToDevToolsUrl.TryGetValue(id, out Uri remoteConnectionUri)) + throw new Exception($"Unknown browser id {id}"); + + // string logFilename = $"{testId}-proxy.log"; + // var proxyLoggerFactory = LoggerFactory.Create( + // builder => builder + // // .AddFile(logFilename, minimumLevel: LogLevel.Debug) + // .AddFilter(null, LogLevel.Trace)); + + var proxy = new DebuggerProxy(_loggerFactory, null, testId); + var browserUri = remoteConnectionUri;// options.RemoteConnectionUri; + var ideSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); + + await proxy.Run(browserUri, ideSocket).ConfigureAwait(false); + // Console.WriteLine("Proxy done"); + testLogger.LogDebug($"Closing proxy for browser {context.Request.Path}{context.Request.QueryString}"); } catch (Exception ex) { - Logger.LogError($"launch-chrome-and-connect failed with {ex.ToString()}"); + ServerLogger.LogError($"{context.Request.Path}{context.Request.QueryString} failed with {ex}"); } }); }); - if (options.NodeApp != null) - { - Logger.LogTrace($"Doing the nodejs: {options.NodeApp}"); - var nodeFullPath = Path.GetFullPath(options.NodeApp); - Logger.LogTrace(nodeFullPath); - var psi = new ProcessStartInfo(); - - psi.UseShellExecute = false; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; - - psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}"; - psi.FileName = "node"; - - app.UseRouter(router => - { - //Inspector API for using chrome devtools directly - router.MapGet("json", SendNodeList); - router.MapGet("json/list", SendNodeList); - router.MapGet("json/version", SendNodeVersion); - router.MapGet("launch-done-and-connect", async context => - { - await LaunchAndServe(psi, context, null); - }); - }); - } + // if (options.NodeApp != null) + // { + // Logger.LogTrace($"Doing the nodejs: {options.NodeApp}"); + // var nodeFullPath = Path.GetFullPath(options.NodeApp); + // Logger.LogTrace(nodeFullPath); + // var psi = new ProcessStartInfo(); + + // psi.UseShellExecute = false; + // psi.RedirectStandardError = true; + // psi.RedirectStandardOutput = true; + + // psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}"; + // psi.FileName = "node"; + + // app.UseRouter(router => + // { + // //Inspector API for using chrome devtools directly + // router.MapGet("json", SendNodeList); + // router.MapGet("json/list", SendNodeList); + // router.MapGet("json/version", SendNodeVersion); + // router.MapGet("launch-done-and-connect", async context => + // { + // await LaunchAndServe(psi, context, null); + // }); + // }); + // } } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs index 2ac3cb620e4689..5207da11f5154c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs @@ -18,15 +18,8 @@ public class SourceList : DebuggerTestBase { [Fact] - public async Task CheckThatAllSourcesAreSent() + public void CheckThatAllSourcesAreSent() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - //all sources are sent before runtime ready is sent, nothing to check - await insp.Ready(); Assert.Contains("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values); Assert.Contains("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values); Assert.Contains("dotnet://debugger-test.dll/dependency.cs", scripts.Values); @@ -35,106 +28,74 @@ public async Task CheckThatAllSourcesAreSent() [Fact] public async Task CreateGoodBreakpoint() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc = bp1_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc["scriptId"]); - Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); - Assert.Equal(10, loc["lineNumber"]); - Assert.Equal(8, loc["columnNumber"]); - }); + Assert.NotNull(loc["scriptId"]); + Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); + Assert.Equal(10, loc["lineNumber"]); + Assert.Equal(8, loc["columnNumber"]); } [Fact] public async Task CreateJSBreakpoint() { // Test that js breakpoints get set correctly - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24); + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + var loc = bp1_res.Value["locations"]?.Value()[0]; - var loc = bp1_res.Value["locations"]?.Value()[0]; + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); - Assert.NotNull(loc["scriptId"]); - Assert.Equal(13, loc["lineNumber"]); - Assert.Equal(24, loc["columnNumber"]); + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); - var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - var loc2 = bp2_res.Value["locations"]?.Value()[0]; - - Assert.NotNull(loc2["scriptId"]); - Assert.Equal(13, loc2["lineNumber"]); - Assert.Equal(31, loc2["columnNumber"]); - }); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); } [Fact] public async Task CreateJS0Breakpoint() { - // Test that js column 0 does as expected - var insp = new Inspector(); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0); - //Collect events - var scripts = SubscribeToScripts(insp); + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0); - - Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc = bp1_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc["scriptId"]); - Assert.Equal(13, loc["lineNumber"]); - Assert.Equal(24, loc["columnNumber"]); + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); - var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); - Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - var loc2 = bp2_res.Value["locations"]?.Value()[0]; + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc2["scriptId"]); - Assert.Equal(13, loc2["lineNumber"]); - Assert.Equal(31, loc2["columnNumber"]); - }); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); } [Theory] @@ -142,160 +103,109 @@ await insp.Ready(async (cli, token) => [InlineData(50)] public async Task CheckMultipleBreakpointsOnSameLine(int col) { - var insp = new Inspector(); + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col); + Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var scripts = SubscribeToScripts(insp); + var loc = bp1_res.Value["locations"]?.Value()[0]; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc); - var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col); - Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55); + Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc); - - var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55); - Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - - var loc2 = bp2_res.Value["locations"]?.Value()[0]; - - CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2); - }); + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2); } [Fact] public async Task CreateBadBreakpoint() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var bp1_req = JObject.FromObject(new { - var bp1_req = JObject.FromObject(new - { - lineNumber = 8, - columnNumber = 2, - url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", - }); + lineNumber = 8, + columnNumber = 2, + url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", + }); - var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); + var bp1_res = await SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); - Assert.True(bp1_res.IsOk); - Assert.Empty(bp1_res.Value["locations"].Values()); - //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); - }); + Assert.True(bp1_res.IsOk); + Assert.Empty(bp1_res.Value["locations"].Values()); + //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); } [Fact] public async Task CreateGoodBreakpointAndHit() { - var insp = new Inspector(); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + expression = "window.setTimeout(function() { invoke_add(); }, 1);", + }); - var eval_req = JObject.FromObject(new + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd", + wait_for_event_fn: (pause_location) => { - expression = "window.setTimeout(function() { invoke_add(); }, 1);", - }); - - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd", - wait_for_event_fn: (pause_location) => - { - Assert.Equal("other", pause_location["reason"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; - Assert.Equal("IntAdd", top_frame["functionName"].Value()); - Assert.Contains("debugger-test.cs", top_frame["url"].Value()); + var top_frame = pause_location["callFrames"][0]; + Assert.Equal("IntAdd", top_frame["functionName"].Value()); + Assert.Contains("debugger-test.cs", top_frame["url"].Value()); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); - //now check the scope - var scope = top_frame["scopeChain"][0]; - Assert.Equal("local", scope["type"]); - Assert.Equal("IntAdd", scope["name"]); + //now check the scope + var scope = top_frame["scopeChain"][0]; + Assert.Equal("local", scope["type"]); + Assert.Equal("IntAdd", scope["name"]); - Assert.Equal("object", scope["object"]["type"]); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]); - return Task.CompletedTask; - } - ); - - }); + Assert.Equal("object", scope["object"]["type"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]); + return Task.CompletedTask; + } + ); } [Fact] public async Task ExceptionThrownInJS() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - var eval_req = JObject.FromObject(new - { - expression = "invoke_bad_js_test();" - }); - - var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); - Assert.True(eval_res.IsErr); - Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value()); + expression = "invoke_bad_js_test();" }); + + var eval_res = await SendCommand("Runtime.evaluate", eval_req, token); + Assert.True(eval_res.IsErr); + Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value()); } [Fact] public async Task ExceptionThrownInJSOutOfBand() { - var insp = new Inspector(); + await SetBreakpoint("/debugger-driver.html", 27, 2); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetBreakpoint("/debugger-driver.html", 27, 2); - - var eval_req = JObject.FromObject(new - { - expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", - }); - - var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); - // Response here will be the id for the timer from JS! - Assert.True(eval_res.IsOk); - - var ex = await Assert.ThrowsAsync(async () => await insp.WaitFor("Runtime.exceptionThrown")); - var ex_json = JObject.Parse(ex.Message); - Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); + expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", }); + var task = insp.WaitFor("Runtime.exceptionThrown"); + var eval_res = await SendCommand("Runtime.evaluate", eval_req, token); + // Response here will be the id for the timer from JS! + Assert.True(eval_res.IsOk); + + var ex = await Assert.ThrowsAsync(async () => await task); + var ex_json = JObject.Parse(ex.Message); + Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); } [Theory] @@ -409,25 +319,25 @@ public async Task InspectNullableLocals(string method_name, bool is_async) => aw var dt = new DateTime(2310, 1, 2, 3, 4, 5); await CheckProps(locals, new { - n_int = TNumber(5), - n_int_null = TObject("System.Nullable", null), + n_int = TNumber(5), + n_int_null = TObject("System.Nullable", null), - n_dt = TDateTime(dt), - n_dt_null = TObject("System.Nullable", null), + n_dt = TDateTime(dt), + n_dt_null = TObject("System.Nullable", null), - n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - n_gs_null = TObject("System.Nullable>", null), + n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + n_gs_null = TObject("System.Nullable>", null), }, "locals"); // check gs var n_gs = GetAndAssertObjectWithName(locals, "n_gs"); - var n_gs_props = await GetProperties(n_gs["value"]?["objectId"]?.Value ()); + var n_gs_props = await GetProperties(n_gs["value"]?["objectId"]?.Value()); await CheckProps(n_gs_props, new { - List = TObject("System.Collections.Generic.List", is_null: true), + List = TObject("System.Collections.Generic.List", is_null: true), StringField = TString("n_gs#StringField"), - Options = TEnum ("DebuggerTests.Options", "None") + Options = TEnum("DebuggerTests.Options", "None") }, nameof(n_gs)); }); @@ -459,41 +369,31 @@ await CheckInspectLocalsAtBreakpointSite( [Fact] public async Task RuntimeGetPropertiesWithInvalidScopeIdTest() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_delegates_test (); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 49, 8, - "DelegatesTest", - wait_for_event_fn: async (pause_location) => - { - //make sure we're on the right bp - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_delegates_test (); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 49, 8, + "DelegatesTest", + wait_for_event_fn: async (pause_location) => + { + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location["callFrames"][0]; - var scope = top_frame["scopeChain"][0]; + var scope = top_frame["scopeChain"][0]; - // Try to get an invalid scope! - var get_prop_req = JObject.FromObject(new - { - objectId = "dotnet:scope:23490871", - }); + // Try to get an invalid scope! + var get_prop_req = JObject.FromObject(new + { + objectId = "dotnet:scope:23490871", + }); - var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); - Assert.True(frame_props.IsErr); - } - ); - }); + var frame_props = await SendCommand("Runtime.getProperties", get_prop_req, token); + Assert.True(frame_props.IsErr); + } + ); } [Theory] @@ -501,99 +401,90 @@ await EvaluateAndCheck( [InlineData(true)] public async Task InspectLocalsWithStructs(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint(debugger_test_loc, 24, 8); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", - debugger_test_loc, 24, 8, "MethodWithLocalStructs"); - - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckProps(locals, new - { - ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - vt_local = TObject("DebuggerTests.ValueTypesTest") - }, "locals"); + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - var dt = new DateTime(2021, 2, 3, 4, 6, 7); - var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local"); - Assert.Equal(5, vt_local_props.Count()); + await SetBreakpoint(debugger_test_loc, 24, 8); - CheckString(vt_local_props, "StringField", "string#0"); - CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); - await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5)); - CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", + debugger_test_loc, 24, 8, "MethodWithLocalStructs"); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); - await CheckProps(ss_local_props, new - { - V = TGetter("V"), - str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Utc") - }, "ss_local"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + vt_local = TObject("DebuggerTests.ValueTypesTest") + }, "locals"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local"); + Assert.Equal(5, vt_local_props.Count()); + + CheckString(vt_local_props, "StringField", "string#0"); + CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); + await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5)); + CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); + + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); - { - var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - // Check ss_local.gs - var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); - CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); - CheckObject(gs_props, "List", "System.Collections.Generic.List"); - } + // Check ss_local.gs + var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); + CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); + CheckObject(gs_props, "List", "System.Collections.Generic.List"); + } - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); - await CheckProps(gs_local_props, new - { - StringField = TString("gs_local#GenericStruct#StringField"), - List = TObject("System.Collections.Generic.List", is_null: true), - Options = TEnum("DebuggerTests.Options", "None") - }, "gs_local"); + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List", is_null: true), + Options = TEnum("DebuggerTests.Options", "None") + }, "gs_local"); - // Check vt_local's properties + // Check vt_local's properties - var exp = new[] - { + var exp = new[] + { ("SimpleStructProperty", 2, "Utc"), ("SimpleStructField", 5, "Local") }; - foreach (var (name, bias, dt_kind) in exp) - { - dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); - await CompareObjectPropertiesFor(vt_local_props, name, - new - { - V = TGetter("V"), - str_member = TString($"{name}#string#0#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", dt_kind) - }, - label: $"vt_local_props.{name}"); - - var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); - } + foreach (var (name, bias, dt_kind) in exp) + { + dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); + await CompareObjectPropertiesFor(vt_local_props, name, + new + { + V = TGetter("V"), + str_member = TString($"{name}#string#0#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", dt_kind) + }, + label: $"vt_local_props.{name}"); + + var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); + } - // FIXME: check ss_local.gs.List's members - }); + // FIXME: check ss_local.gs.List's members } [Theory] @@ -611,33 +502,33 @@ public async Task InspectBoxedLocals(string method_name, bool is_async) => await var dt = new DateTime(2310, 1, 2, 3, 4, 5); await CheckProps(locals, new { - n_i = TNumber(5), - o_i = TNumber(5), - o_n_i = TNumber(5), - o_s = TString("foobar"), - o_obj = TObject("Math"), - - n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - o_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + n_i = TNumber(5), + o_i = TNumber(5), + o_n_i = TNumber(5), + o_s = TString("foobar"), + o_obj = TObject("Math"), + + n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + o_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), o_n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - n_dt = TDateTime(dt), - o_dt = TDateTime(dt), + n_dt = TDateTime(dt), + o_dt = TDateTime(dt), o_n_dt = TDateTime(dt), o_null = TObject("object", is_null: true), - o_ia = TArray("int[]", 2), + o_ia = TArray("int[]", 2), }, "locals"); foreach (var name in new[] { "n_gs", "o_gs", "o_n_gs" }) { var gs = GetAndAssertObjectWithName(locals, name); - var gs_props = await GetProperties(gs["value"]?["objectId"]?.Value ()); + var gs_props = await GetProperties(gs["value"]?["objectId"]?.Value()); await CheckProps(gs_props, new { - List = TObject("System.Collections.Generic.List", is_null: true), + List = TObject("System.Collections.Generic.List", is_null: true), StringField = TString("n_gs#StringField"), - Options = TEnum ("DebuggerTests.Options", "None") + Options = TEnum("DebuggerTests.Options", "None") }, name); } @@ -688,14 +579,13 @@ public async Task InspectBoxedAsClassLocals(string method_name, bool is_async) = { var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); var dt = new DateTime(2310, 1, 2, 3, 4, 5); - Console.WriteLine (locals); await CheckProps(locals, new { vt_dt = TDateTime(new DateTime(4819, 5, 6, 7, 8, 9)), vt_gs = TValueType("Math.GenericStruct"), - e = TEnum("System.IO.FileMode", "0"), - ee = TEnum("System.IO.FileMode", "Append") + e = TEnum("System.IO.FileMode", "0"), + ee = TEnum("System.IO.FileMode", "Append") }, "locals"); }); @@ -704,72 +594,63 @@ public async Task InspectBoxedAsClassLocals(string method_name, bool is_async) = [InlineData(true)] public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + await SetBreakpoint(debugger_test_loc, 54, 12); - await SetBreakpoint(debugger_test_loc, 54, 12); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async (" + + "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" + + "); }, 1);", + debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async (" + - "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" + - "); }, 1);", - debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + result = TBool(true) + }, + "locals#0"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckProps(locals, new - { - ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - result = TBool(true) - }, - "locals#0"); - - var dt = new DateTime(2021, 2, 3, 4, 6, 7); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); - await CheckProps(ss_local_props, new - { - V = TGetter("V"), - str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Utc") - }, "ss_local"); + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - { - var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - - // Check ss_local.gs - await CompareObjectPropertiesFor(ss_local_props, "gs", - new - { - StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option1") - } - ); - } + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_local_props, "gs", + new + { + StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + } + ); + } - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); - await CheckProps(gs_local_props, new - { - StringField = TString("gs_local#GenericStruct#StringField"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option2") - }, "gs_local"); + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option2") + }, "gs_local"); - // FIXME: check ss_local.gs.List's members - }); + // FIXME: check ss_local.gs.List's members } [Theory] @@ -779,113 +660,94 @@ await CompareObjectPropertiesFor(ss_local_props, "gs", [InlineData(182, 12, "MethodWithArgumentsForToStringTestAsync", false, true)] public async Task InspectLocalsForToStringDescriptions(int line, int col, string method_name, bool call_other, bool invoke_async) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}"; int frame_idx = 0; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + await SetBreakpoint(debugger_test_loc, line, col); - await SetBreakpoint(debugger_test_loc, line, col); + var eval_expr = "window.setTimeout(function() {" + + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + + $"'{entry_method_name}'," + + (call_other ? "true" : "false") + + "); }, 1);"; - var eval_expr = "window.setTimeout(function() {" + - (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + - $"'{entry_method_name}'," + - (call_other ? "true" : "false") + - "); }, 1);"; - Console.WriteLine($"{eval_expr}"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); + var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); - var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); - var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); - var ts = dt0 - dt1; - var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); - - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(call_other), + dt0 = TDateTime(dt0), + dt1 = TDateTime(dt1), + dto = TValueType("System.DateTimeOffset", dto.ToString()), + ts = TValueType("System.TimeSpan", ts.ToString()), + dec = TValueType("System.Decimal", "123987123"), + guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), + dts = TArray("System.DateTime[]", 2), + obj = TObject("DebuggerTests.ClassForToStringTests"), + sst = TObject("DebuggerTests.StructForToStringTests") + }, "locals#0"); + + var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10); + var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3); + var dts_elements = await GetObjectOnLocals(frame_locals, "dts"); + await CheckDateTime(dts_elements, "0", dts_0); + await CheckDateTime(dts_elements, "1", dts_1); + + // TimeSpan + await CompareObjectPropertiesFor(frame_locals, "ts", + new { - call_other = TBool(call_other), - dt0 = TDateTime(dt0), - dt1 = TDateTime(dt1), - dto = TValueType("System.DateTimeOffset", dto.ToString()), - ts = TValueType("System.TimeSpan", ts.ToString()), - dec = TValueType("System.Decimal", "123987123"), - guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), - dts = TArray("System.DateTime[]", 2), - obj = TObject("DebuggerTests.ClassForToStringTests"), - sst = TObject("DebuggerTests.StructForToStringTests") - }, "locals#0"); - - var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10); - var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3); - var dts_elements = await GetObjectOnLocals(frame_locals, "dts"); - await CheckDateTime(dts_elements, "0", dts_0); - await CheckDateTime(dts_elements, "1", dts_1); - - // TimeSpan - await CompareObjectPropertiesFor(frame_locals, "ts", - new - { - Days = TNumber(3530), - Minutes = TNumber(2), - Seconds = TNumber(4), - }, "ts_props", num_fields: 12); - - // DateTimeOffset - await CompareObjectPropertiesFor(frame_locals, "dto", - new - { - Day = TNumber(2), - Year = TNumber(2020), - DayOfWeek = TEnum("System.DayOfWeek", "Thursday") - }, "dto_props", num_fields: 22); + Days = TNumber(3530), + Minutes = TNumber(2), + Seconds = TNumber(4), + }, "ts_props", num_fields: 12); + + // DateTimeOffset + await CompareObjectPropertiesFor(frame_locals, "dto", + new + { + Day = TNumber(2), + Year = TNumber(2020), + DayOfWeek = TEnum("System.DayOfWeek", "Thursday") + }, "dto_props", num_fields: 22); - var DT = new DateTime(2004, 10, 15, 1, 2, 3); - var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); + var DT = new DateTime(2004, 10, 15, 1, 2, 3); + var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); - await CompareObjectPropertiesFor(frame_locals, "obj", - new - { - DT = TDateTime(DT), - DTO = TValueType("System.DateTimeOffset", DTO.ToString()), - TS = TValueType("System.TimeSpan", ts.ToString()), - Dec = TValueType("System.Decimal", "1239871"), - Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "obj_props"); - - DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)); - var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", - new - { - DT = TDateTime(DT), - DTO = TValueType("System.DateTimeOffset", DTO.ToString()), - TS = TValueType("System.TimeSpan", ts.ToString()), - Dec = TValueType("System.Decimal", "1239871"), - Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "sst_props"); - }); + await CompareObjectPropertiesFor(frame_locals, "obj", + new + { + DT = TDateTime(DT), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "obj_props"); + + DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)); + var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", + new + { + DT = TDateTime(DT), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "sst_props"); } [Fact] public async Task InspectLocals() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var wait_res = await RunUntil("locals_inner"); - var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value()); - }); + var wait_res = await RunUntil("locals_inner"); + var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value()); } [Theory] @@ -948,7 +810,7 @@ await CheckInspectLocalsAtBreakpointSite( async Task CreateNewId(string expr) { - var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + var res = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed"); AssertEqual("string", res.Value["result"]?["type"]?.Value(), "Expected Runtime.evaluate to return a string type result"); return res.Value["result"]?["value"]?.Value(); @@ -957,7 +819,7 @@ async Task CreateNewId(string expr) async Task _invoke_getter(string obj_id, string property_name, bool expect_ok) { var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')"; - var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + var res = await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}"); return res; @@ -1057,84 +919,59 @@ JObject FindFrame(JObject pause_location, string function_name) [Fact] public async Task DebugLazyLoadedAssemblyWithPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - int line = 9; - await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); - await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb")); - - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; - Assert.Contains(source_location, scripts.Values); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", - source_location, line, 8, - "IntAdd"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - CheckNumber(locals, "a", 5); - CheckNumber(locals, "b", 10); - }); + int line = 9; + await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); + await LoadAssemblyDynamically( + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb")); + + var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + Assert.Contains(source_location, scripts.Values); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", + source_location, line, 8, + "IntAdd"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 5); + CheckNumber(locals, "b", 10); } [Fact] public async Task DebugLazyLoadedAssemblyWithEmbeddedPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - int line = 9; - await SetBreakpoint(".*/lazy-debugger-test-embedded.cs$", line, 0, use_regex: true); - await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"), - null); - - var source_location = "dotnet://lazy-debugger-test-embedded.dll/lazy-debugger-test-embedded.cs"; - Assert.Contains(source_location, scripts.Values); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test-embedded] LazyMath:IntAdd', 5, 10); }, 1);", - source_location, line, 8, - "IntAdd"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - CheckNumber(locals, "a", 5); - CheckNumber(locals, "b", 10); - }); + int line = 9; + await SetBreakpoint(".*/lazy-debugger-test-embedded.cs$", line, 0, use_regex: true); + await LoadAssemblyDynamically( + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"), + null); + + var source_location = "dotnet://lazy-debugger-test-embedded.dll/lazy-debugger-test-embedded.cs"; + Assert.Contains(source_location, scripts.Values); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test-embedded] LazyMath:IntAdd', 5, 10); }, 1);", + source_location, line, 8, + "IntAdd"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 5); + CheckNumber(locals, "b", 10); } [Fact] public async Task CannotDebugLazyLoadedAssemblyWithoutPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + int line = 9; + await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); + await LoadAssemblyDynamically( + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), + null); - int line = 9; - await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); - await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), - null); + // wait to bit to catch if the event might be raised a bit late + await Task.Delay(1000); - // wait to bit to catch if the event might be raised a bit late - await Task.Delay(1000); - - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; - Assert.DoesNotContain(source_location, scripts.Values); - }); + var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + Assert.DoesNotContain(source_location, scripts.Values); } async Task LoadAssemblyDynamically(string asm_file, string pdb_file) @@ -1144,7 +981,8 @@ async Task LoadAssemblyDynamically(string asm_file, string pdb_file) string asm_base64 = Convert.ToBase64String(bytes); string pdb_base64 = null; - if (pdb_file != null) { + if (pdb_file != null) + { bytes = File.ReadAllBytes(pdb_file); pdb_base64 = Convert.ToBase64String(bytes); } @@ -1154,26 +992,17 @@ async Task LoadAssemblyDynamically(string asm_file, string pdb_file) expression = $"{{ let asm_b64 = '{asm_base64}'; let pdb_b64 = '{pdb_base64}'; invoke_static_method('[debugger-test] LoadDebuggerTest:LoadLazyAssembly', asm_b64, pdb_b64); }}" }); - Result load_assemblies_res = await ctx.cli.SendCommand("Runtime.evaluate", load_assemblies, ctx.token); + Result load_assemblies_res = await SendCommand("Runtime.evaluate", load_assemblies, token); Assert.True(load_assemblies_res.IsOk); } [Fact] public async Task BreakOnDebuggerBreak() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] UserBreak:BreakOnDebuggerBreakCommand'); }, 1);", - "dotnet://debugger-test.dll/debugger-test2.cs", 56, 4, - "BreakOnDebuggerBreakCommand"); - }); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] UserBreak:BreakOnDebuggerBreakCommand'); }, 1);", + "dotnet://debugger-test.dll/debugger-test2.cs", 56, 4, + "BreakOnDebuggerBreakCommand"); } //TODO add tests covering basic stepping behavior as step in/out/over } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/appsettings.json b/src/mono/wasm/debugger/DebuggerTestSuite/appsettings.json index 4d7c82ac0ee05b..b18e86ec689b35 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/appsettings.json +++ b/src/mono/wasm/debugger/DebuggerTestSuite/appsettings.json @@ -4,9 +4,9 @@ "Default": "Error", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.WebAssembly.Diagnostics.TestHarnessProxy": "Information", + "Microsoft.WebAssembly.Diagnostics.TestHarnessProxy": "Debug", "Microsoft.WebAssembly.Diagnostics.DevToolsProxy": "Information", - "DebuggerTests.Inspector": "Information" + "DebuggerTests.Inspector": "Trace" } } } diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 14a59c43c6da87..2f50ad3082dd7b 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -19,19 +19,19 @@ if (is_browser) { console.warn = print; console.info = print; - const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + //const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); - consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.onopen = function(event) { - consoleWebSocket.send("browser: Console websocket connected."); + //consoleWebSocket = new WebSocket(consoleUrl); + //consoleWebSocket.onopen = function(event) { + //consoleWebSocket.send("browser: Console websocket connected."); - window.real_print = function(msg) { - consoleWebSocket.send(msg); - }; - }; - consoleWebSocket.onerror = function(event) { - console.log(`websocket error: ${event}`); - }; + //window.real_print = function(msg) { + //consoleWebSocket.send(msg); + //}; + //}; + //consoleWebSocket.onerror = function(event) { + //console.log(`websocket error: ${event}`); + //}; var url = new URL (decodeURI (window.location)); arguments = []; @@ -231,7 +231,7 @@ var Module = { } FS.chdir (working_dir); - App.init (); + //App.init (); }; config.fetch_file_cb = function (asset) { // console.log("fetch_file_cb('" + asset + "')"); diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 734e5cdc2c855c..a99182f295821a 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -1662,6 +1662,7 @@ var MonoSupportLib = { // "auto" (default): if "icu" behavior assets are present, use ICU, otherwise invariant. // diagnostic_tracing: (optional) enables diagnostic log messages during startup mono_load_runtime_and_bcl_args: function (args) { + console.log(`-- mono_load_runtime_and_bcl_args: ${JSON.stringify(args)}`); try { return this._load_assets_and_runtime (args); } catch (exc) { @@ -1753,7 +1754,7 @@ var MonoSupportLib = { throw new Error ("loaded_cb not provided"); var ctx = { - tracing: args.diagnostic_tracing || false, + tracing: true, pending_count: args.assets.length, mono_wasm_add_assembly: Module.cwrap ('mono_wasm_add_assembly', 'number', ['string', 'number', 'number']), mono_wasm_add_satellite_assembly: Module.cwrap ('mono_wasm_add_satellite_assembly', 'void', ['string', 'string', 'number', 'number']), diff --git a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs index fce387d996751c..8b77253425aa68 100644 --- a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -189,7 +189,7 @@ public override bool Execute () File.Copy(Path.Join (runtimeSourceDir, f), Path.Join(AppDir, f), true); File.Copy(MainJS!, Path.Join(AppDir, "runtime.js"), true); - var html = @""; + var html = @""; File.WriteAllText(Path.Join(AppDir, "index.html"), html); foreach (var assembly in _assemblies.Values) {