Skip to content

Commit

Permalink
[wasm][testing] hosting webSocket echo server in xharness process (#593)
Browse files Browse the repository at this point in the history
- host multiple Kestrel middlewares
- set env variables to pass server host to unit test
  • Loading branch information
pavelsavara authored May 19, 2021
1 parent 769fd62 commit 31d6c34
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,6 +23,9 @@ internal abstract class TestCommandArguments : AppRunCommandArguments
/// Tests classes to be included in the run while all others are ignored.
/// </summary>
public IEnumerable<string> ClassMethodFilters => _classMethodFilters;
public IList<(string path, string type)> WebServerMiddlewarePathsAndTypes { get; set; } = new List<(string, string)>();
public IList<string> SetWebServerEnvironmentVariablesHttp { get; set; } = new List<string>();
public IList<string> SetWebServerEnvironmentVariablesHttps { get; set; } = new List<string>();

protected override OptionSet GetCommandOptions()
{
Expand All @@ -40,6 +45,31 @@ protected override OptionSet GetCommandOptions()
"ignored. Can be used more than once.",
v => _classMethodFilters.Add(v)
},
{ "web-server-middleware=", "<Path>,<typeName> to assembly and type which contains Kestrel middleware for local test server. Could be used multiple times to load multiple middlewares.",
v =>
{
var split = v.Split(',');
var file = split[0];
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}");
}
WebServerMiddlewarePathsAndTypes.Add((file,type));
}
},
{ "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(',')
},
};

foreach (var option in testOptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ internal class WasmTestBrowserCommandArguments : TestCommandArguments
public bool Incognito { get; set; } = true;
public bool Headless { get; set; } = true;
public bool QuitAppAtEnd { get; set; } = true;

protected override OptionSet GetTestCommandOptions() => new()
{
{ "browser=|b=", "Specifies the browser to be used. Default is Chrome",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +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;

namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm
{
Expand Down Expand Up @@ -62,12 +57,12 @@ public async Task<ExitCode> RunTestsWithWebDriver(DriverService driverService, I
try
{
var consolePumpTcs = new TaskCompletionSource<bool>();
string webServerAddr = await StartWebServer(
_arguments.AppPackagePath,
ServerURLs serverURLs = await WebServer.Start(
_arguments, _logger,
socket => RunConsoleMessagesPump(socket, consolePumpTcs, cts.Token),
cts.Token);

string testUrl = BuildUrl(webServerAddr);
string testUrl = BuildUrl(serverURLs);

var seleniumLogMessageTask = Task.Run(() => RunSeleniumLogMessagePump(driver, cts.Token), cts.Token);
cts.CancelAfter(_arguments.Timeout);
Expand Down Expand Up @@ -232,14 +227,29 @@ private void RunSeleniumLogMessagePump(IWebDriver driver, CancellationToken toke
}
}

private string BuildUrl(string webServerAddr)
private string BuildUrl(ServerURLs serverURLs)
{
var uriBuilder = new UriBuilder($"{webServerAddr}/{_arguments.HTMLFile}");
var uriBuilder = new UriBuilder($"{serverURLs.Http}/{_arguments.HTMLFile}");
var sb = new StringBuilder();

if (_arguments.DebuggerPort != null)
sb.Append($"arg=--debug");


foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp)
{
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('&');
sb.Append($"arg={HttpUtility.UrlEncode($"--setenv={envVariable}={serverURLs!.Https}")}");
}

foreach (var arg in _passThroughArguments)
{
if (sb.Length > 0)
Expand All @@ -251,37 +261,5 @@ private string BuildUrl(string webServerAddr)
uriBuilder.Query = sb.ToString();
return uriBuilder.ToString();
}

private static async Task<string> StartWebServer(string contentRoot, Func<WebSocket, Task> onConsoleConnected, CancellationToken token)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(contentRoot)
.UseStartup<WasmTestWebServerStartup>()
.ConfigureLogging(logging =>
{
logging.AddConsole().AddFilter(null, LogLevel.Warning);
})
.ConfigureServices((ctx, services) =>
{
services.AddRouting();
services.Configure<WasmTestWebServerOptions>(ctx.Configuration);
services.Configure<WasmTestWebServerOptions>(options =>
{
options.OnConsoleConnected = onConsoleConnected;
});
})
.UseUrls("http://127.0.0.1:0")
.Build();

await host.StartAsync(token);

var ipAddress = host.ServerFeatures
.Get<IServerAddressesFeature>()?
.Addresses
.FirstOrDefault();

return ipAddress ?? throw new InvalidOperationException("Failed to determine web server's IP address");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ protected override async Task<ExitCode> 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",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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");
Expand Down Expand Up @@ -67,33 +68,57 @@ protected override async Task<ExitCode> InvokeInternal(ILogger logger)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
engineBinary = FindEngineInPath(engineBinary + ".cmd");

var engineArgs = new List<string>();

if (_arguments.Engine == JavaScriptEngine.V8)
var webServerCts = new CancellationTokenSource();
try
{
// v8 needs this flag to enable WASM support
engineArgs.Add("--expose_wasm");
}
ServerURLs? serverURLs = null;
if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0)
{
serverURLs = await WebServer.Start(
_arguments, logger,
null,
webServerCts.Token);
webServerCts.CancelAfter(_arguments.Timeout);
}

engineArgs.AddRange(_arguments.EngineArgs);
engineArgs.Add(_arguments.JSFile);
var engineArgs = new List<string>();

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.Engine == JavaScriptEngine.V8)
{
// v8 needs this flag to enable WASM support
engineArgs.Add("--expose_wasm");
}

engineArgs.AddRange(_arguments.EngineArgs);
engineArgs.Add(_arguments.JSFile);

if (_arguments.Engine == JavaScriptEngine.V8 || _arguments.Engine == JavaScriptEngine.JavaScriptCore)
{
// v8/jsc want arguments to the script separated by "--", others don't
engineArgs.Add("--");
}

engineArgs.AddRange(PassThroughArguments);
if (_arguments.WebServerMiddlewarePathsAndTypes.Count > 0)
{
foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttp)
{
engineArgs.Add($"--setenv={envVariable}={serverURLs!.Http}");
}

foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps)
{
engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}");
}
}

var xmlResultsFilePath = Path.Combine(_arguments.OutputDirectory, "testResults.xml");
File.Delete(xmlResultsFilePath);
engineArgs.AddRange(PassThroughArguments);

var stdoutFilePath = Path.Combine(_arguments.OutputDirectory, "wasm-console.log");
File.Delete(stdoutFilePath);
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,
Expand All @@ -102,11 +127,11 @@ protected override async Task<ExitCode> InvokeInternal(ILogger logger)
stdoutLog: new CallbackLog(logProcessor.Invoke),
stderrLog: new CallbackLog(m => logger.LogError(m)),
_arguments.Timeout);

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
{
Expand All @@ -119,6 +144,13 @@ protected override async Task<ExitCode> InvokeInternal(ILogger logger)
logger.LogCritical($"The engine binary `{engineBinary}` was not found");
return ExitCode.APP_LAUNCH_FAILURE;
}
finally
{
if (!webServerCts.IsCancellationRequested)
{
webServerCts.Cancel();
}
}
}
}
}
Loading

0 comments on commit 31d6c34

Please sign in to comment.