Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm][testing] hosting webSocket echo server in xharness process #593

Merged
merged 15 commits into from
May 19, 2021
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,27 @@ 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
? split[1]
: "GenericHandler";
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,54 @@ 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 cts = new CancellationTokenSource();
try
{
// v8 needs this flag to enable WASM support
engineArgs.Add("--expose_wasm");
}
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);
}

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(PassThroughArguments);
engineArgs.AddRange(_arguments.EngineArgs);
engineArgs.Add(_arguments.JSFile);

var xmlResultsFilePath = Path.Combine(_arguments.OutputDirectory, "testResults.xml");
File.Delete(xmlResultsFilePath);
if (_arguments.Engine == JavaScriptEngine.V8 || _arguments.Engine == JavaScriptEngine.JavaScriptCore)
{
// v8/jsc want arguments to the script separated by "--", others don't
engineArgs.Add("--");
}

var stdoutFilePath = Path.Combine(_arguments.OutputDirectory, "wasm-console.log");
File.Delete(stdoutFilePath);
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);

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 +124,16 @@ protected override async Task<ExitCode> 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
{
Expand All @@ -119,6 +146,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 (!cts.IsCancellationRequested)
{
cts.Cancel();
}
}
}
}
}
Loading