From 8973165e38141d98fc4bc8403270f588b3dc0eae Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 18 May 2021 13:27:21 +0200 Subject: [PATCH 01/15] - host multiple Kestrel midlewares - set env variables to pass server host to unit test --- .../WASM/WasmTestBrowserCommandArguments.cs | 15 ++++++ .../WASM/Browser/WasmBrowserTestRunner.cs | 54 ++++++++++++++++--- .../WASM/Browser/WasmTestBrowserCommand.cs | 1 + .../WASM/Browser/WasmTestWebServerStartup.cs | 10 +++- .../WasmApplicationEntryPoint.cs | 2 +- 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs index 514569fae..dd46a2340 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs @@ -48,6 +48,8 @@ internal class WasmTestBrowserCommandArguments : TestCommandArguments public bool Incognito { get; set; } = true; public bool Headless { get; set; } = true; public bool QuitAppAtEnd { get; set; } = true; + public IList WebServerMiddlewarePaths { get; set; } = new List(); + public bool SetWebServerEnvironmentVariables { get; set; } = false; protected override OptionSet GetTestCommandOptions() => new() { @@ -69,6 +71,19 @@ internal class WasmTestBrowserCommandArguments : TestCommandArguments { "no-incognito", "Don't run in incognito mode.", v => Incognito = false }, + { "web-server-middleware=", "Path to assembly which contains middleware for endpoints for local test server.", + v => + { + if (!File.Exists(v)) + { + throw new ArgumentException($"Failed to find the middleware assembly at {v}"); + } + WebServerMiddlewarePaths.Add(v); + } + }, + { "set-web-server-env", "Set environment variables, so that unit test use xharness as test web server.", + v => SetWebServerEnvironmentVariables = true + }, { "no-headless", "Don't run in headless mode.", v => Headless = false }, diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index fdebf1dd2..c1041c589 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -26,6 +26,7 @@ using SeleniumLogLevel = OpenQA.Selenium.LogLevel; using LogLevel = Microsoft.Extensions.Logging.LogLevel; +using System.Runtime.Loader; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm { @@ -62,12 +63,12 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I try { var consolePumpTcs = new TaskCompletionSource(); - string webServerAddr = await StartWebServer( + var (webServerAddr, webServerAddrSecure) = await StartWebServer( _arguments.AppPackagePath, socket => RunConsoleMessagesPump(socket, consolePumpTcs, cts.Token), cts.Token); - string testUrl = BuildUrl(webServerAddr); + string testUrl = BuildUrl(webServerAddr, webServerAddrSecure); var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, cts.Token), cts.Token); cts.CancelAfter(_arguments.Timeout); @@ -232,14 +233,30 @@ private void RunSeleniumLogMessagePump(IWebDriver driver, CancellationToken toke } } - private string BuildUrl(string webServerAddr) + private string BuildUrl(string webServerAddr, string webServerAddrSecure) { var uriBuilder = new UriBuilder($"{webServerAddr}/{_arguments.HTMLFile}"); var sb = new StringBuilder(); + var hostAndPort = webServerAddr.Substring(webServerAddr.LastIndexOf('/') + 1); + var hostAndPortSecure = webServerAddrSecure.Substring(webServerAddrSecure.LastIndexOf('/') + 1); + if (_arguments.DebuggerPort != null) sb.Append($"arg=--debug"); + if (_arguments.SetWebServerEnvironmentVariables) + { + if (sb.Length > 0) + sb.Append('&'); + // see runtime\src\libraries\Common\tests\System\Net\Configuration.WebSockets.cs + // see runtime\src\libraries\Common\tests\System\Net\Configuration.Http.cs + sb.Append($"arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_WEBSOCKETHOST={hostAndPort}")}"); + sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREWEBSOCKETHOST={hostAndPortSecure}")}"); + sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_HTTPHOST={hostAndPort}")}"); + sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREHTTPHOST={hostAndPortSecure}")}"); + sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_NET_SECURITY_TLSSERVERURI={webServerAddrSecure}")}"); + } + foreach (var arg in _passThroughArguments) { if (sb.Length > 0) @@ -252,7 +269,7 @@ private string BuildUrl(string webServerAddr) return uriBuilder.ToString(); } - private static async Task StartWebServer(string contentRoot, Func onConsoleConnected, CancellationToken token) + private async Task<(string,string)> StartWebServer(string contentRoot, Func onConsoleConnected, CancellationToken token) { var host = new WebHostBuilder() .UseKestrel() @@ -265,13 +282,27 @@ private static async Task StartWebServer(string contentRoot, Func { services.AddRouting(); + services.AddSingleton(_logger); services.Configure(ctx.Configuration); services.Configure(options => { options.OnConsoleConnected = onConsoleConnected; + foreach(var middlewarePath in _arguments.WebServerMiddlewarePaths) + { + var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(middlewarePath); + var middlewareType = extensionAssembly?.GetTypes().Where(type => type.Name == "GenericHandler").FirstOrDefault(); + if (middlewareType == null) + { + var message = $"Can't find GenericHandler middleware in {middlewarePath}"; + _logger.LogError(message); + throw new Exception(message); + } + _logger.LogInformation($"Loaded {middlewareType.FullName} middleware"); + options.EchoServerMiddlewares.Add(middlewareType); + } }); }) - .UseUrls("http://127.0.0.1:0") + .UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0") .Build(); await host.StartAsync(token); @@ -279,9 +310,20 @@ private static async Task StartWebServer(string contentRoot, Func()? .Addresses + .Where(a=>a.StartsWith("http:")) + .FirstOrDefault(); + + var ipAddressSecure = host.ServerFeatures + .Get()? + .Addresses + .Where(a=>a.StartsWith("https:")) .FirstOrDefault(); - return ipAddress ?? throw new InvalidOperationException("Failed to determine web server's IP address"); + if (ipAddress == null || ipAddressSecure == null) + { + throw new InvalidOperationException("Failed to determine web server's IP address or port"); + } + return (ipAddress,ipAddressSecure); } } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestBrowserCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestBrowserCommand.cs index 31df5a3e5..e135fbe2c 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestBrowserCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestBrowserCommand.cs @@ -175,6 +175,7 @@ protected override async Task InvokeInternal(ILogger logger) { // added based on https://github.com/puppeteer/puppeteer/blob/main/src/node/Launcher.ts#L159-L181 "--enable-features=NetworkService,NetworkServiceInProcess", + "--allow-insecure-localhost", "--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-breakpad", diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs index 2871fad2b..9efc4107d 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs @@ -8,20 +8,24 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; using System.Net.WebSockets; +using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm { public class WasmTestWebServerStartup { private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ILogger _logger; - public WasmTestWebServerStartup(IWebHostEnvironment hostingEnvironment) + public WasmTestWebServerStartup(IWebHostEnvironment hostingEnvironment, ILogger logger) { _hostingEnvironment = hostingEnvironment; + _logger = logger; } public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) @@ -62,11 +66,15 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor? OnConsoleConnected { get; set; } + public IList EchoServerMiddlewares { get; set; } = new List(); } } diff --git a/src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmApplicationEntryPoint.cs b/src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmApplicationEntryPoint.cs index a93b50290..d7aff3882 100644 --- a/src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmApplicationEntryPoint.cs +++ b/src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmApplicationEntryPoint.cs @@ -40,7 +40,7 @@ private static void ParseEqualSeparatedArgument(Dictionary> var parts = argument.Split('='); if (parts.Length != 2 || string.IsNullOrEmpty(parts[0]) || string.IsNullOrEmpty(parts[1])) { - throw new ArgumentException("Invalid argument value '{argument}'.", nameof(argument)); + throw new ArgumentException($"Invalid argument value '{argument}'.", nameof(argument)); } var name = parts[0]; From d9e567c2e450d8fb6b508270be607da1b1b15491 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 18 May 2021 14:07:03 +0200 Subject: [PATCH 02/15] fix --- .../Commands/WASM/Browser/WasmBrowserTestRunner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index c1041c589..876407613 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -254,7 +254,6 @@ private string BuildUrl(string webServerAddr, string webServerAddrSecure) sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREWEBSOCKETHOST={hostAndPortSecure}")}"); sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_HTTPHOST={hostAndPort}")}"); sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREHTTPHOST={hostAndPortSecure}")}"); - sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_NET_SECURITY_TLSSERVERURI={webServerAddrSecure}")}"); } foreach (var arg in _passThroughArguments) From 478bc6b99e7d10cf2bae8ec8f31ac27cdc90138e Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 18 May 2021 16:41:11 +0200 Subject: [PATCH 03/15] refactor web server to be usable from other commands --- .../CommandArguments/TestCommandArguments.cs | 17 ++ .../WASM/WasmTestBrowserCommandArguments.cs | 16 -- .../WASM/Browser/WasmBrowserTestRunner.cs | 78 +-------- .../WASM/Browser/WasmTestWebServerStartup.cs | 80 --------- .../Commands/WASM/JS/WasmTestCommand.cs | 76 ++++++--- .../Commands/WebServer.cs | 152 ++++++++++++++++++ 6 files changed, 230 insertions(+), 189 deletions(-) delete mode 100644 src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs create mode 100644 src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs index 8e1d8bffa..1c5519ce4 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.IO; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments @@ -21,6 +23,8 @@ internal abstract class TestCommandArguments : AppRunCommandArguments /// Tests classes to be included in the run while all others are ignored. /// public IEnumerable ClassMethodFilters => _classMethodFilters; + public IList WebServerMiddlewarePaths { get; set; } = new List(); + public bool SetWebServerEnvironmentVariables { get; set; } = false; protected override OptionSet GetCommandOptions() { @@ -40,6 +44,19 @@ protected override OptionSet GetCommandOptions() "ignored. Can be used more than once.", v => _classMethodFilters.Add(v) }, + { "web-server-middleware=", "Path to assembly which contains middleware for endpoints for local test server.", + v => + { + if (!File.Exists(v)) + { + throw new ArgumentException($"Failed to find the middleware assembly at {v}"); + } + WebServerMiddlewarePaths.Add(v); + } + }, + { "set-web-server-env", "Set environment variables, so that unit test use xharness as test web server.", + v => SetWebServerEnvironmentVariables = true + }, }; foreach (var option in testOptions) diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs index dd46a2340..d83f8c6fc 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs @@ -48,9 +48,6 @@ internal class WasmTestBrowserCommandArguments : TestCommandArguments public bool Incognito { get; set; } = true; public bool Headless { get; set; } = true; public bool QuitAppAtEnd { get; set; } = true; - public IList WebServerMiddlewarePaths { get; set; } = new List(); - public bool SetWebServerEnvironmentVariables { get; set; } = false; - protected override OptionSet GetTestCommandOptions() => new() { { "browser=|b=", "Specifies the browser to be used. Default is Chrome", @@ -71,19 +68,6 @@ internal class WasmTestBrowserCommandArguments : TestCommandArguments { "no-incognito", "Don't run in incognito mode.", v => Incognito = false }, - { "web-server-middleware=", "Path to assembly which contains middleware for endpoints for local test server.", - v => - { - if (!File.Exists(v)) - { - throw new ArgumentException($"Failed to find the middleware assembly at {v}"); - } - WebServerMiddlewarePaths.Add(v); - } - }, - { "set-web-server-env", "Set environment variables, so that unit test use xharness as test web server.", - v => SetWebServerEnvironmentVariables = true - }, { "no-headless", "Don't run in headless mode.", v => Headless = false }, diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index 876407613..1af87ea56 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -7,26 +7,20 @@ using System.Diagnostics; using System.Net.WebSockets; using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Web; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; using SeleniumLogLevel = OpenQA.Selenium.LogLevel; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; -using System.Runtime.Loader; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm { @@ -63,12 +57,12 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I try { var consolePumpTcs = new TaskCompletionSource(); - var (webServerAddr, webServerAddrSecure) = await StartWebServer( - _arguments.AppPackagePath, + ServerURLs serverURLs = await WebServer.Start( + _arguments, _logger, socket => RunConsoleMessagesPump(socket, consolePumpTcs, cts.Token), cts.Token); - string testUrl = BuildUrl(webServerAddr, webServerAddrSecure); + string testUrl = BuildUrl(serverURLs); var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, cts.Token), cts.Token); cts.CancelAfter(_arguments.Timeout); @@ -233,19 +227,19 @@ private void RunSeleniumLogMessagePump(IWebDriver driver, CancellationToken toke } } - private string BuildUrl(string webServerAddr, string webServerAddrSecure) + private string BuildUrl(ServerURLs serverURLs) { - var uriBuilder = new UriBuilder($"{webServerAddr}/{_arguments.HTMLFile}"); + var uriBuilder = new UriBuilder($"{serverURLs.Http}/{_arguments.HTMLFile}"); var sb = new StringBuilder(); - var hostAndPort = webServerAddr.Substring(webServerAddr.LastIndexOf('/') + 1); - var hostAndPortSecure = webServerAddrSecure.Substring(webServerAddrSecure.LastIndexOf('/') + 1); - if (_arguments.DebuggerPort != null) sb.Append($"arg=--debug"); if (_arguments.SetWebServerEnvironmentVariables) { + var hostAndPort = serverURLs.Http.Substring(serverURLs.Http.LastIndexOf('/') + 1); + var hostAndPortSecure = serverURLs.Https.Substring(serverURLs.Https.LastIndexOf('/') + 1); + if (sb.Length > 0) sb.Append('&'); // see runtime\src\libraries\Common\tests\System\Net\Configuration.WebSockets.cs @@ -268,61 +262,5 @@ private string BuildUrl(string webServerAddr, string webServerAddrSecure) return uriBuilder.ToString(); } - private async Task<(string,string)> StartWebServer(string contentRoot, Func onConsoleConnected, CancellationToken token) - { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(contentRoot) - .UseStartup() - .ConfigureLogging(logging => - { - logging.AddConsole().AddFilter(null, LogLevel.Warning); - }) - .ConfigureServices((ctx, services) => - { - services.AddRouting(); - services.AddSingleton(_logger); - services.Configure(ctx.Configuration); - services.Configure(options => - { - options.OnConsoleConnected = onConsoleConnected; - foreach(var middlewarePath in _arguments.WebServerMiddlewarePaths) - { - var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(middlewarePath); - var middlewareType = extensionAssembly?.GetTypes().Where(type => type.Name == "GenericHandler").FirstOrDefault(); - if (middlewareType == null) - { - var message = $"Can't find GenericHandler middleware in {middlewarePath}"; - _logger.LogError(message); - throw new Exception(message); - } - _logger.LogInformation($"Loaded {middlewareType.FullName} middleware"); - options.EchoServerMiddlewares.Add(middlewareType); - } - }); - }) - .UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0") - .Build(); - - await host.StartAsync(token); - - var ipAddress = host.ServerFeatures - .Get()? - .Addresses - .Where(a=>a.StartsWith("http:")) - .FirstOrDefault(); - - var ipAddressSecure = host.ServerFeatures - .Get()? - .Addresses - .Where(a=>a.StartsWith("https:")) - .FirstOrDefault(); - - if (ipAddress == null || ipAddressSecure == null) - { - throw new InvalidOperationException("Failed to determine web server's IP address or port"); - } - return (ipAddress,ipAddressSecure); - } } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs deleted file mode 100644 index 9efc4107d..000000000 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestWebServerStartup.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; - -using System; -using System.Threading.Tasks; -using System.Net.WebSockets; -using System.Collections.Generic; - -namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm -{ - public class WasmTestWebServerStartup - { - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly ILogger _logger; - - public WasmTestWebServerStartup(IWebHostEnvironment hostingEnvironment, ILogger logger) - { - _hostingEnvironment = hostingEnvironment; - _logger = logger; - } - - public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) - { - 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"; - } - - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), - ContentTypeProvider = provider, - ServeUnknownFileTypes = true - }); - - var options = optionsAccessor.CurrentValue; - if (options.OnConsoleConnected == null) - { - throw new ArgumentException("Bug: OnConsoleConnected callback not set"); - } - - app.UseWebSockets(); - app.UseRouter(router => - { - router.MapGet("/console", async context => - { - if (!context.WebSockets.IsWebSocketRequest) - { - context.Response.StatusCode = 400; - return; - } - - var socket = await context.WebSockets.AcceptWebSocketAsync(); - await options.OnConsoleConnected(socket); - }); - }); - foreach(var middleware in options.EchoServerMiddlewares){ - app.UseMiddleware(middleware); - } - } - } - - public class WasmTestWebServerOptions - { - public Func? OnConsoleConnected { get; set; } - public IList EchoServerMiddlewares { get; set; } = new List(); - } -} diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index c849789f5..7db61e40f 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -15,6 +15,7 @@ using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.Extensions.Logging; +using System.Threading; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm { @@ -34,7 +35,7 @@ public WasmTestCommand() : base("test", true, CommandHelp) private static string FindEngineInPath(string engineBinary) { - if (File.Exists (engineBinary) || Path.IsPathRooted(engineBinary)) + if (File.Exists(engineBinary) || Path.IsPathRooted(engineBinary)) return engineBinary; var path = Environment.GetEnvironmentVariable("PATH"); @@ -54,6 +55,7 @@ private static string FindEngineInPath(string engineBinary) protected override async Task InvokeInternal(ILogger logger) { + var processManager = ProcessManagerFactory.CreateProcessManager(); var engineBinary = _arguments.Engine switch @@ -67,33 +69,51 @@ protected override async Task InvokeInternal(ILogger logger) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) engineBinary = FindEngineInPath(engineBinary + ".cmd"); - var engineArgs = new List(); - - if (_arguments.Engine == JavaScriptEngine.V8) + var cts = new CancellationTokenSource(); + try { - // v8 needs this flag to enable WASM support - engineArgs.Add("--expose_wasm"); - } + var serverURLs = await WebServer.Start( + _arguments, logger, + null, + cts.Token); - engineArgs.AddRange(_arguments.EngineArgs); - engineArgs.Add(_arguments.JSFile); + cts.CancelAfter(_arguments.Timeout); - if (_arguments.Engine == JavaScriptEngine.V8 || _arguments.Engine == JavaScriptEngine.JavaScriptCore) - { - // v8/jsc want arguments to the script separated by "--", others don't - engineArgs.Add("--"); - } + var engineArgs = new List(); - engineArgs.AddRange(PassThroughArguments); + if (_arguments.Engine == JavaScriptEngine.V8) + { + // v8 needs this flag to enable WASM support + engineArgs.Add("--expose_wasm"); + } - var xmlResultsFilePath = Path.Combine(_arguments.OutputDirectory, "testResults.xml"); - File.Delete(xmlResultsFilePath); + engineArgs.AddRange(_arguments.EngineArgs); + engineArgs.Add(_arguments.JSFile); - var stdoutFilePath = Path.Combine(_arguments.OutputDirectory, "wasm-console.log"); - File.Delete(stdoutFilePath); + if (_arguments.Engine == JavaScriptEngine.V8 || _arguments.Engine == JavaScriptEngine.JavaScriptCore) + { + // v8/jsc want arguments to the script separated by "--", others don't + engineArgs.Add("--"); + } + + if (_arguments.SetWebServerEnvironmentVariables) + { + var hostAndPort = serverURLs.Http.Substring(serverURLs.Http.LastIndexOf('/') + 1); + var hostAndPortSecure = serverURLs.Https.Substring(serverURLs.Https.LastIndexOf('/') + 1); + + engineArgs.Add($"--setenv=DOTNET_TEST_WEBSOCKETHOST={hostAndPort}"); + engineArgs.Add($"--setenv=DOTNET_TEST_SECUREWEBSOCKETHOST={hostAndPortSecure}"); + engineArgs.Add($"--setenv=DOTNET_TEST_HTTPHOST={hostAndPort}"); + engineArgs.Add($"--setenv=DOTNET_TEST_SECUREHTTPHOST={hostAndPortSecure}"); + } + engineArgs.AddRange(PassThroughArguments); + + var xmlResultsFilePath = Path.Combine(_arguments.OutputDirectory, "testResults.xml"); + File.Delete(xmlResultsFilePath); + + var stdoutFilePath = Path.Combine(_arguments.OutputDirectory, "wasm-console.log"); + File.Delete(stdoutFilePath); - try - { var logProcessor = new WasmTestMessagesProcessor(xmlResultsFilePath, stdoutFilePath, logger); var result = await processManager.ExecuteCommandAsync( engineBinary, @@ -102,11 +122,14 @@ protected override async Task InvokeInternal(ILogger logger) stdoutLog: new CallbackLog(logProcessor.Invoke), stderrLog: new CallbackLog(m => logger.LogError(m)), _arguments.Timeout); + if (cts.IsCancellationRequested) + { + return ExitCode.TIMED_OUT; + } if (result.ExitCode != _arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {result.ExitCode} but {_arguments.ExpectedExitCode} was expected"); - return ExitCode.GENERAL_FAILURE; - + return ExitCode.GENERAL_FAILURE; } else { @@ -119,6 +142,13 @@ protected override async Task InvokeInternal(ILogger logger) logger.LogCritical($"The engine binary `{engineBinary}` was not found"); return ExitCode.APP_LAUNCH_FAILURE; } + finally + { + if (!cts.IsCancellationRequested) + { + cts.Cancel(); + } + } } } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs new file mode 100644 index 000000000..92a9da62b --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -0,0 +1,152 @@ +using System; +using System.Net.WebSockets; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; + +using LogLevel = Microsoft.Extensions.Logging.LogLevel; +using System.Runtime.Loader; +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using Microsoft.DotNet.XHarness.CLI.CommandArguments; + +public class WebServer +{ + internal static async Task Start(TestCommandArguments arguments, ILogger logger, Func? onConsoleConnected, CancellationToken token) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(arguments.AppPackagePath) + .UseStartup() + .ConfigureLogging(logging => + { + logging.AddConsole().AddFilter(null, LogLevel.Warning); + }) + .ConfigureServices((ctx, services) => + { + services.AddRouting(); + services.AddSingleton(logger); + services.Configure(ctx.Configuration); + services.Configure(options => + { + options.OnConsoleConnected = onConsoleConnected; + foreach (var middlewarePath in arguments.WebServerMiddlewarePaths) + { + var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(middlewarePath); + var middlewareType = extensionAssembly?.GetTypes().Where(type => type.Name == "GenericHandler").FirstOrDefault(); + if (middlewareType == null) + { + var message = $"Can't find GenericHandler middleware in {middlewarePath}"; + logger.LogError(message); + throw new Exception(message); + } + options.EchoServerMiddlewares.Add(middlewareType); + } + }); + }) + .UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0") + .Build(); + + await host.StartAsync(token); + + var ipAddress = host.ServerFeatures + .Get()? + .Addresses + .Where(a => a.StartsWith("http:")) + .FirstOrDefault(); + + var ipAddressSecure = host.ServerFeatures + .Get()? + .Addresses + .Where(a => a.StartsWith("https:")) + .FirstOrDefault(); + + if (ipAddress == null || ipAddressSecure == null) + { + throw new InvalidOperationException("Failed to determine web server's IP address or port"); + } + return new ServerURLs(ipAddress, ipAddressSecure); + } + + internal class TestWebServerStartup + { + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ILogger _logger; + + public TestWebServerStartup(IWebHostEnvironment hostingEnvironment, ILogger logger) + { + _hostingEnvironment = hostingEnvironment; + _logger = logger; + } + + public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) + { + 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"; + } + + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), + ContentTypeProvider = provider, + ServeUnknownFileTypes = true + }); + + var options = optionsAccessor.CurrentValue; + + app.UseWebSockets(); + if (options.OnConsoleConnected != null) + { + app.UseRouter(router => + { + router.MapGet("/console", async context => + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + var socket = await context.WebSockets.AcceptWebSocketAsync(); + await options.OnConsoleConnected(socket); + }); + }); + } + foreach (var middleware in options.EchoServerMiddlewares) + { + app.UseMiddleware(middleware); + _logger.LogInformation($"Loaded {middleware.FullName} middleware"); + } + } + } + + internal class TestWebServerOptions + { + public Func? OnConsoleConnected { get; set; } + public IList EchoServerMiddlewares { get; set; } = new List(); + } +} + +public class ServerURLs +{ + public ServerURLs(string http, string https) + { + this.Http = http; + this.Https = https; + } + public string Http { get; init; } + public string Https { get; init; } +} From c6695a3a7c284b5d8a7171fb6356397aa1db6a1d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 10:13:43 +0200 Subject: [PATCH 04/15] core review feedback --- .../CommandArguments/TestCommandArguments.cs | 23 +- .../WASM/Browser/WasmBrowserTestRunner.cs | 19 +- .../Commands/WASM/JS/WasmTestCommand.cs | 29 +-- .../Commands/WebServer.cs | 204 +++++++++--------- 4 files changed, 147 insertions(+), 128 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs index 1c5519ce4..18bceb884 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs @@ -23,8 +23,9 @@ internal abstract class TestCommandArguments : AppRunCommandArguments /// Tests classes to be included in the run while all others are ignored. /// public IEnumerable ClassMethodFilters => _classMethodFilters; - public IList WebServerMiddlewarePaths { get; set; } = new List(); - public bool SetWebServerEnvironmentVariables { get; set; } = false; + public IList<(string path, string type)> WebServerMiddlewarePathsAndTypes { get; set; } = new List<(string, string)>(); + public IList SetWebServerEnvironmentVariablesHttp { get; set; } = new List(); + public IList SetWebServerEnvironmentVariablesHttps { get; set; } = new List(); protected override OptionSet GetCommandOptions() { @@ -44,18 +45,26 @@ protected override OptionSet GetCommandOptions() "ignored. Can be used more than once.", v => _classMethodFilters.Add(v) }, - { "web-server-middleware=", "Path to assembly which contains middleware for endpoints for local test server.", + { "web-server-middleware=", ", to assembly and type which contains Kestrel middleware for local test server. Could be used multiple times to load multiple middlewares.", v => { - if (!File.Exists(v)) + var split = v.Split(','); + var file = split[0]; + var type = split.Length > 1 + ? split[1] + : "GenericHandler"; + if (!File.Exists(file)) { throw new ArgumentException($"Failed to find the middleware assembly at {v}"); } - WebServerMiddlewarePaths.Add(v); + WebServerMiddlewarePathsAndTypes.Add((file,type)); } }, - { "set-web-server-env", "Set environment variables, so that unit test use xharness as test web server.", - v => SetWebServerEnvironmentVariables = true + { "set-web-server-http-env=", "Comma separated list of environment variable names, which should be set to HTTP host and port, for the unit test, which use xharness as test web server.", + v => SetWebServerEnvironmentVariablesHttp = v.Split(',') + }, + { "set-web-server-https-env=", "Comma separated list of environment variable names, which should be set to HTTPS host and port, for the unit test, which use xharness as test web server.", + v => SetWebServerEnvironmentVariablesHttps = v.Split(',') }, }; diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index 1af87ea56..d80b2e3a3 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -235,19 +235,19 @@ private string BuildUrl(ServerURLs serverURLs) if (_arguments.DebuggerPort != null) sb.Append($"arg=--debug"); - if (_arguments.SetWebServerEnvironmentVariables) + + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp) { - var hostAndPort = serverURLs.Http.Substring(serverURLs.Http.LastIndexOf('/') + 1); - var hostAndPortSecure = serverURLs.Https.Substring(serverURLs.Https.LastIndexOf('/') + 1); + if (sb.Length > 0) + sb.Append('&'); + sb.Append($"arg={HttpUtility.UrlEncode($"--setenv={envVariable}={serverURLs!.Http}")}"); + } + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps) + { if (sb.Length > 0) sb.Append('&'); - // see runtime\src\libraries\Common\tests\System\Net\Configuration.WebSockets.cs - // see runtime\src\libraries\Common\tests\System\Net\Configuration.Http.cs - sb.Append($"arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_WEBSOCKETHOST={hostAndPort}")}"); - sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREWEBSOCKETHOST={hostAndPortSecure}")}"); - sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_HTTPHOST={hostAndPort}")}"); - sb.Append($"&arg={HttpUtility.UrlEncode($"--setenv=DOTNET_TEST_SECUREHTTPHOST={hostAndPortSecure}")}"); + sb.Append($"arg={HttpUtility.UrlEncode($"--setenv={envVariable}={serverURLs!.Https}")}"); } foreach (var arg in _passThroughArguments) @@ -261,6 +261,5 @@ private string BuildUrl(ServerURLs serverURLs) uriBuilder.Query = sb.ToString(); return uriBuilder.ToString(); } - } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index 7db61e40f..7f805941e 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -55,7 +55,6 @@ private static string FindEngineInPath(string engineBinary) protected override async Task InvokeInternal(ILogger logger) { - var processManager = ProcessManagerFactory.CreateProcessManager(); var engineBinary = _arguments.Engine switch @@ -72,10 +71,14 @@ protected override async Task InvokeInternal(ILogger logger) var cts = new CancellationTokenSource(); try { - var serverURLs = await WebServer.Start( - _arguments, logger, - null, - cts.Token); + ServerURLs? serverURLs = null; + if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0 || _arguments.SetWebServerEnvironmentVariablesHttp.Count > 0 || _arguments.SetWebServerEnvironmentVariablesHttps.Count > 0) + { + serverURLs = await WebServer.Start( + _arguments, logger, + null, + cts.Token); + } cts.CancelAfter(_arguments.Timeout); @@ -96,16 +99,16 @@ protected override async Task InvokeInternal(ILogger logger) engineArgs.Add("--"); } - if (_arguments.SetWebServerEnvironmentVariables) + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp) { - var hostAndPort = serverURLs.Http.Substring(serverURLs.Http.LastIndexOf('/') + 1); - var hostAndPortSecure = serverURLs.Https.Substring(serverURLs.Https.LastIndexOf('/') + 1); + engineArgs.Add($"--setenv={envVariable}={serverURLs!.Http}"); + } - engineArgs.Add($"--setenv=DOTNET_TEST_WEBSOCKETHOST={hostAndPort}"); - engineArgs.Add($"--setenv=DOTNET_TEST_SECUREWEBSOCKETHOST={hostAndPortSecure}"); - engineArgs.Add($"--setenv=DOTNET_TEST_HTTPHOST={hostAndPort}"); - engineArgs.Add($"--setenv=DOTNET_TEST_SECUREHTTPHOST={hostAndPortSecure}"); + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps) + { + engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}"); } + engineArgs.AddRange(PassThroughArguments); var xmlResultsFilePath = Path.Combine(_arguments.OutputDirectory, "testResults.xml"); @@ -122,10 +125,12 @@ protected override async Task InvokeInternal(ILogger logger) stdoutLog: new CallbackLog(logProcessor.Invoke), stderrLog: new CallbackLog(m => logger.LogError(m)), _arguments.Timeout); + if (cts.IsCancellationRequested) { return ExitCode.TIMED_OUT; } + if (result.ExitCode != _arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {result.ExitCode} but {_arguments.ExpectedExitCode} was expected"); diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 92a9da62b..403e97938 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -19,134 +19,140 @@ using Microsoft.Extensions.Options; using Microsoft.DotNet.XHarness.CLI.CommandArguments; -public class WebServer +namespace Microsoft.DotNet.XHarness.CLI.Commands { - internal static async Task Start(TestCommandArguments arguments, ILogger logger, Func? onConsoleConnected, CancellationToken token) + public class WebServer { - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(arguments.AppPackagePath) - .UseStartup() - .ConfigureLogging(logging => - { - logging.AddConsole().AddFilter(null, LogLevel.Warning); - }) - .ConfigureServices((ctx, services) => - { - services.AddRouting(); - services.AddSingleton(logger); - services.Configure(ctx.Configuration); - services.Configure(options => + internal static async Task Start(TestCommandArguments arguments, ILogger logger, Func? onConsoleConnected, CancellationToken token) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(arguments.AppPackagePath) + .UseStartup() + .ConfigureLogging(logging => { - options.OnConsoleConnected = onConsoleConnected; - foreach (var middlewarePath in arguments.WebServerMiddlewarePaths) + logging.AddConsole().AddFilter(null, LogLevel.Warning); + }) + .ConfigureServices((ctx, services) => + { + services.AddRouting(); + services.AddSingleton(logger); + services.Configure(ctx.Configuration); + services.Configure(options => { - var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(middlewarePath); - var middlewareType = extensionAssembly?.GetTypes().Where(type => type.Name == "GenericHandler").FirstOrDefault(); - if (middlewareType == null) + options.OnConsoleConnected = onConsoleConnected; + foreach (var (middlewarePath, middlewareTypeName) in arguments.WebServerMiddlewarePathsAndTypes) { - var message = $"Can't find GenericHandler middleware in {middlewarePath}"; - logger.LogError(message); - throw new Exception(message); + var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(middlewarePath); + var middlewareType = extensionAssembly?.GetTypes().Where(type => type.Name == middlewareTypeName).FirstOrDefault(); + if (middlewareType == null) + { + var message = $"Can't find {middlewareTypeName} middleware in {middlewarePath}"; + logger.LogError(message); + throw new Exception(message); + } + options.EchoServerMiddlewares.Add(middlewareType); } - options.EchoServerMiddlewares.Add(middlewareType); - } - }); - }) - .UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0") - .Build(); + }); + }) + .UseUrls("http://127.0.0.1:0", "https://127.0.0.1:0") + .Build(); - await host.StartAsync(token); + await host.StartAsync(token); - var ipAddress = host.ServerFeatures - .Get()? - .Addresses - .Where(a => a.StartsWith("http:")) - .FirstOrDefault(); + var ipAddress = host.ServerFeatures + .Get()? + .Addresses + .Where(a => a.StartsWith("http:")) + .FirstOrDefault(); - var ipAddressSecure = host.ServerFeatures - .Get()? - .Addresses - .Where(a => a.StartsWith("https:")) - .FirstOrDefault(); + var ipAddressSecure = host.ServerFeatures + .Get()? + .Addresses + .Where(a => a.StartsWith("https:")) + .FirstOrDefault(); - if (ipAddress == null || ipAddressSecure == null) - { - throw new InvalidOperationException("Failed to determine web server's IP address or port"); - } - return new ServerURLs(ipAddress, ipAddressSecure); - } - - internal class TestWebServerStartup - { - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly ILogger _logger; + if (ipAddress == null || ipAddressSecure == null) + { + throw new InvalidOperationException("Failed to determine web server's IP address or port"); + } - public TestWebServerStartup(IWebHostEnvironment hostingEnvironment, ILogger logger) - { - _hostingEnvironment = hostingEnvironment; - _logger = logger; + return new ServerURLs(ipAddress.Substring(ipAddress.LastIndexOf('/') + 1), + ipAddressSecure.Substring(ipAddressSecure.LastIndexOf('/') + 1)); } - public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) + class TestWebServerStartup { - var provider = new FileExtensionContentTypeProvider(); - provider.Mappings[".wasm"] = "application/wasm"; + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ILogger _logger; - foreach (var extn in new string[] { ".dll", ".pdb", ".dat", ".blat" }) + public TestWebServerStartup(IWebHostEnvironment hostingEnvironment, ILogger logger) { - provider.Mappings[extn] = "application/octet-stream"; + _hostingEnvironment = hostingEnvironment; + _logger = logger; } - app.UseStaticFiles(new StaticFileOptions + public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) { - FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), - ContentTypeProvider = provider, - ServeUnknownFileTypes = true - }); + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".wasm"] = "application/wasm"; - var options = optionsAccessor.CurrentValue; + foreach (var extn in new string[] { ".dll", ".pdb", ".dat", ".blat" }) + { + provider.Mappings[extn] = "application/octet-stream"; + } - app.UseWebSockets(); - if (options.OnConsoleConnected != null) - { - app.UseRouter(router => + app.UseStaticFiles(new StaticFileOptions { - router.MapGet("/console", async context => + FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), + ContentTypeProvider = provider, + ServeUnknownFileTypes = true + }); + + var options = optionsAccessor.CurrentValue; + + app.UseWebSockets(); + if (options.OnConsoleConnected != null) + { + app.UseRouter(router => { - if (!context.WebSockets.IsWebSocketRequest) + router.MapGet("/console", async context => { - context.Response.StatusCode = 400; - return; - } - - var socket = await context.WebSockets.AcceptWebSocketAsync(); - await options.OnConsoleConnected(socket); + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + var socket = await context.WebSockets.AcceptWebSocketAsync(); + await options.OnConsoleConnected(socket); + }); }); - }); - } - foreach (var middleware in options.EchoServerMiddlewares) - { - app.UseMiddleware(middleware); - _logger.LogInformation($"Loaded {middleware.FullName} middleware"); + } + foreach (var middleware in options.EchoServerMiddlewares) + { + app.UseMiddleware(middleware); + _logger.LogInformation($"Loaded {middleware.FullName} middleware"); + } } } - } - internal class TestWebServerOptions - { - public Func? OnConsoleConnected { get; set; } - public IList EchoServerMiddlewares { get; set; } = new List(); + class TestWebServerOptions + { + public Func? OnConsoleConnected { get; set; } + public IList EchoServerMiddlewares { get; set; } = new List(); + } } -} -public class ServerURLs -{ - public ServerURLs(string http, string https) + public record ServerURLs { - this.Http = http; - this.Https = https; + public ServerURLs(string http, string https) + { + this.Http = http; + this.Https = https; + } + + public string Http { get; init; } + public string Https { get; init; } } - public string Http { get; init; } - public string Https { get; init; } -} +} \ No newline at end of file From 087480ac296505534543867813e7591aab1b9cb7 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 10:19:10 +0200 Subject: [PATCH 05/15] fix --- .../CommandArguments/TestCommandArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs index 18bceb884..e2d01c893 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs @@ -55,7 +55,7 @@ protected override OptionSet GetCommandOptions() : "GenericHandler"; if (!File.Exists(file)) { - throw new ArgumentException($"Failed to find the middleware assembly at {v}"); + throw new ArgumentException($"Failed to find the middleware assembly at {file}"); } WebServerMiddlewarePathsAndTypes.Add((file,type)); } From d846595c94c2c3433174422e1edd72b8364be4ad Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 10:22:46 +0200 Subject: [PATCH 06/15] pretty --- src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 403e97938..6895f984f 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -129,6 +129,7 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor Date: Wed, 19 May 2021 16:05:12 +0200 Subject: [PATCH 07/15] code review feedback --- .../Commands/WASM/JS/WasmTestCommand.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index 7f805941e..14be723c1 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -78,10 +78,9 @@ protected override async Task InvokeInternal(ILogger logger) _arguments, logger, null, cts.Token); + cts.CancelAfter(_arguments.Timeout); } - cts.CancelAfter(_arguments.Timeout); - var engineArgs = new List(); if (_arguments.Engine == JavaScriptEngine.V8) From e264048a236fec6b675824556695e2230bc3732e Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 16:23:15 +0200 Subject: [PATCH 08/15] code review feedback --- .../Commands/WebServer.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 6895f984f..b19139f27 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -145,15 +145,5 @@ class TestWebServerOptions } } - public record ServerURLs - { - public ServerURLs(string http, string https) - { - this.Http = http; - this.Https = https; - } - - public string Http { get; init; } - public string Https { get; init; } - } + public record ServerURLs(string Http, string Https); } \ No newline at end of file From d50992e385b2630249452d4d9f5fb932acffcf70 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 16:37:15 +0200 Subject: [PATCH 09/15] feedback --- src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index b19139f27..9e8349555 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -64,12 +64,16 @@ internal static async Task Start(TestCommandArguments arguments, ILo .Get()? .Addresses .Where(a => a.StartsWith("http:")) + .Select(a => new Uri(a)) + .Select(uri => $"{uri.Host}:{uri.Port}") .FirstOrDefault(); var ipAddressSecure = host.ServerFeatures .Get()? .Addresses .Where(a => a.StartsWith("https:")) + .Select(a => new Uri(a)) + .Select(uri => $"{uri.Host}:{uri.Port}") .FirstOrDefault(); if (ipAddress == null || ipAddressSecure == null) @@ -77,8 +81,7 @@ internal static async Task Start(TestCommandArguments arguments, ILo throw new InvalidOperationException("Failed to determine web server's IP address or port"); } - return new ServerURLs(ipAddress.Substring(ipAddress.LastIndexOf('/') + 1), - ipAddressSecure.Substring(ipAddressSecure.LastIndexOf('/') + 1)); + return new ServerURLs(ipAddress, ipAddressSecure); } class TestWebServerStartup From f2f5a4d450116c0c20c1b792b16361da306b9b69 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 17:18:28 +0200 Subject: [PATCH 10/15] feedback --- .../WASM/Browser/WasmBrowserTestRunner.cs | 20 +++++++++---------- .../Commands/WASM/JS/WasmTestCommand.cs | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index d80b2e3a3..0f0276872 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -53,19 +53,19 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I return ExitCode.GENERAL_FAILURE; } - var cts = new CancellationTokenSource(); + var webServerCts = new CancellationTokenSource(); try { var consolePumpTcs = new TaskCompletionSource(); ServerURLs serverURLs = await WebServer.Start( _arguments, _logger, - socket => RunConsoleMessagesPump(socket, consolePumpTcs, cts.Token), - cts.Token); + socket => RunConsoleMessagesPump(socket, consolePumpTcs, webServerCts.Token), + webServerCts.Token); string testUrl = BuildUrl(serverURLs); - var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, cts.Token), cts.Token); - cts.CancelAfter(_arguments.Timeout); + var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, webServerCts.Token), webServerCts.Token); + webServerCts.CancelAfter(_arguments.Timeout); _logger.LogTrace($"Opening in browser: {testUrl}"); driver.Navigate().GoToUrl(testUrl); @@ -80,7 +80,7 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I }; var task = await Task.WhenAny(tasks).ConfigureAwait(false); - if (task == tasks[^1] || cts.IsCancellationRequested) + if (task == tasks[^1] || webServerCts.IsCancellationRequested) { if (driverService.IsRunning) { @@ -95,8 +95,8 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I } // timed out - if (!cts.IsCancellationRequested) - cts.Cancel(); + if (!webServerCts.IsCancellationRequested) + webServerCts.Cancel(); return ExitCode.TIMED_OUT; } @@ -124,9 +124,9 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I } finally { - if (!cts.IsCancellationRequested) + if (!webServerCts.IsCancellationRequested) { - cts.Cancel(); + webServerCts.Cancel(); } } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index 14be723c1..eae090a73 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -68,7 +68,7 @@ protected override async Task InvokeInternal(ILogger logger) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) engineBinary = FindEngineInPath(engineBinary + ".cmd"); - var cts = new CancellationTokenSource(); + var webServerCts = new CancellationTokenSource(); try { ServerURLs? serverURLs = null; @@ -77,8 +77,8 @@ protected override async Task InvokeInternal(ILogger logger) serverURLs = await WebServer.Start( _arguments, logger, null, - cts.Token); - cts.CancelAfter(_arguments.Timeout); + webServerCts.Token); + webServerCts.CancelAfter(_arguments.Timeout); } var engineArgs = new List(); @@ -125,7 +125,7 @@ protected override async Task InvokeInternal(ILogger logger) stderrLog: new CallbackLog(m => logger.LogError(m)), _arguments.Timeout); - if (cts.IsCancellationRequested) + if (webServerCts.IsCancellationRequested) { return ExitCode.TIMED_OUT; } @@ -148,9 +148,9 @@ protected override async Task InvokeInternal(ILogger logger) } finally { - if (!cts.IsCancellationRequested) + if (!webServerCts.IsCancellationRequested) { - cts.Cancel(); + webServerCts.Cancel(); } } } From 256bf7433fde4e5908046b55cbf4e677c95478a0 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 17:22:42 +0200 Subject: [PATCH 11/15] feedback --- .../Commands/WASM/JS/WasmTestCommand.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index eae090a73..976cfad56 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -72,7 +72,7 @@ protected override async Task InvokeInternal(ILogger logger) try { ServerURLs? serverURLs = null; - if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0 || _arguments.SetWebServerEnvironmentVariablesHttp.Count > 0 || _arguments.SetWebServerEnvironmentVariablesHttps.Count > 0) + if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0) { serverURLs = await WebServer.Start( _arguments, logger, @@ -98,14 +98,17 @@ protected override async Task InvokeInternal(ILogger logger) engineArgs.Add("--"); } - foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp) + if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0) { - engineArgs.Add($"--setenv={envVariable}={serverURLs!.Http}"); - } - - foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps) - { - engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}"); + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp) + { + engineArgs.Add($"--setenv={envVariable}={serverURLs!.Http}"); + } + + foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps) + { + engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}"); + } } engineArgs.AddRange(PassThroughArguments); From e6732515915a816f675b1d864676ced7df3e4d59 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 18:05:25 +0200 Subject: [PATCH 12/15] feedback --- .../Commands/WASM/JS/WasmTestCommand.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index 976cfad56..d3f708c72 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -128,11 +128,6 @@ protected override async Task InvokeInternal(ILogger logger) stderrLog: new CallbackLog(m => logger.LogError(m)), _arguments.Timeout); - if (webServerCts.IsCancellationRequested) - { - return ExitCode.TIMED_OUT; - } - if (result.ExitCode != _arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {result.ExitCode} but {_arguments.ExpectedExitCode} was expected"); From 3d32296cd82ce025056414edfacf81da264b2aac Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 18:10:18 +0200 Subject: [PATCH 13/15] feedback --- src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 9e8349555..d6592bd07 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -1,3 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + using System; using System.Net.WebSockets; using System.Linq; From 3ba7223ff47ef3e08d1252d91411766ffde62561 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 18:13:00 +0200 Subject: [PATCH 14/15] feedback --- .../WASM/Browser/WasmBrowserTestRunner.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index 0f0276872..d80b2e3a3 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -53,19 +53,19 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I return ExitCode.GENERAL_FAILURE; } - var webServerCts = new CancellationTokenSource(); + var cts = new CancellationTokenSource(); try { var consolePumpTcs = new TaskCompletionSource(); ServerURLs serverURLs = await WebServer.Start( _arguments, _logger, - socket => RunConsoleMessagesPump(socket, consolePumpTcs, webServerCts.Token), - webServerCts.Token); + socket => RunConsoleMessagesPump(socket, consolePumpTcs, cts.Token), + cts.Token); string testUrl = BuildUrl(serverURLs); - var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, webServerCts.Token), webServerCts.Token); - webServerCts.CancelAfter(_arguments.Timeout); + var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, cts.Token), cts.Token); + cts.CancelAfter(_arguments.Timeout); _logger.LogTrace($"Opening in browser: {testUrl}"); driver.Navigate().GoToUrl(testUrl); @@ -80,7 +80,7 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I }; var task = await Task.WhenAny(tasks).ConfigureAwait(false); - if (task == tasks[^1] || webServerCts.IsCancellationRequested) + if (task == tasks[^1] || cts.IsCancellationRequested) { if (driverService.IsRunning) { @@ -95,8 +95,8 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I } // timed out - if (!webServerCts.IsCancellationRequested) - webServerCts.Cancel(); + if (!cts.IsCancellationRequested) + cts.Cancel(); return ExitCode.TIMED_OUT; } @@ -124,9 +124,9 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I } finally { - if (!webServerCts.IsCancellationRequested) + if (!cts.IsCancellationRequested) { - webServerCts.Cancel(); + cts.Cancel(); } } } From 9dd169cd937fbaa20631bc172dbbc9b41da735ea Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 19 May 2021 18:58:08 +0200 Subject: [PATCH 15/15] feedback --- .../CommandArguments/TestCommandArguments.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs index e2d01c893..1e19404f3 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/TestCommandArguments.cs @@ -50,9 +50,13 @@ protected override OptionSet GetCommandOptions() { var split = v.Split(','); var file = split[0]; - var type = split.Length > 1 + var type = split.Length > 1 && !string.IsNullOrWhiteSpace(split[1]) ? split[1] : "GenericHandler"; + if (string.IsNullOrWhiteSpace(file)) + { + throw new ArgumentException($"Empty path to middleware assembly"); + } if (!File.Exists(file)) { throw new ArgumentException($"Failed to find the middleware assembly at {file}");