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,8 @@ 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> WebServerMiddlewarePaths { get; set; } = new List<string>();
public bool SetWebServerEnvironmentVariables { get; set; } = false;

protected override OptionSet GetCommandOptions()
{
Expand All @@ -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)
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");

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

foreach (var arg in _passThroughArguments)
{
if (sb.Length > 0)
Expand All @@ -252,36 +262,5 @@ private string BuildUrl(string webServerAddr)
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 All @@ -54,6 +55,7 @@ private static string FindEngineInPath(string engineBinary)

protected override async Task<ExitCode> InvokeInternal(ILogger logger)
{

var processManager = ProcessManagerFactory.CreateProcessManager();

var engineBinary = _arguments.Engine switch
Expand All @@ -67,33 +69,51 @@ 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");
}
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<string>();

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,
Expand All @@ -102,11 +122,14 @@ 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 +142,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