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",
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
"--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,55 @@ 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);
}

engineArgs.AddRange(_arguments.EngineArgs);
engineArgs.Add(_arguments.JSFile);
cts.CancelAfter(_arguments.Timeout);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved

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<string>();

engineArgs.AddRange(PassThroughArguments);
if (_arguments.Engine == JavaScriptEngine.V8)
{
// v8 needs this flag to enable WASM support
engineArgs.Add("--expose_wasm");
}
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved

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("--");
}

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

foreach (var envVariable in _arguments.SetWebServerEnvironmentVariablesHttps)
{
engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}");
}
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved

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 +125,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;
}
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved

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 +147,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