201 lines
8.2 KiB
C#
201 lines
8.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json.Linq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
|
{
|
|
public class AutobahnTester : IDisposable
|
|
{
|
|
private readonly List<IApplicationDeployer> _deployers = new List<IApplicationDeployer>();
|
|
private readonly List<DeploymentResult> _deployments = new List<DeploymentResult>();
|
|
private readonly List<AutobahnExpectations> _expectations = new List<AutobahnExpectations>();
|
|
private readonly ILoggerFactory _loggerFactory;
|
|
private readonly ILogger _logger;
|
|
|
|
public AutobahnSpec Spec { get; }
|
|
|
|
public AutobahnTester(ILoggerFactory loggerFactory, AutobahnSpec baseSpec)
|
|
{
|
|
_loggerFactory = loggerFactory;
|
|
_logger = _loggerFactory.CreateLogger("AutobahnTester");
|
|
|
|
Spec = baseSpec;
|
|
}
|
|
|
|
public async Task<AutobahnResult> Run(CancellationToken cancellationToken)
|
|
{
|
|
var specFile = Path.GetTempFileName();
|
|
try
|
|
{
|
|
// Start pinging the servers to see that they're still running
|
|
var pingCts = new CancellationTokenSource();
|
|
var pinger = new Timer(state => Pinger((CancellationToken)state), pingCts.Token, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
|
|
|
Spec.WriteJson(specFile);
|
|
|
|
// Run the test (write something to the console so people know this will take a while...)
|
|
_logger.LogInformation("Now launching Autobahn Test Suite. This will take a while.");
|
|
var exitCode = await Wstest.Default.ExecAsync("-m fuzzingclient -s " + specFile, cancellationToken, _loggerFactory.CreateLogger("wstest"));
|
|
if (exitCode != 0)
|
|
{
|
|
throw new Exception("wstest failed");
|
|
}
|
|
|
|
pingCts.Cancel();
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(specFile))
|
|
{
|
|
File.Delete(specFile);
|
|
}
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Parse the output.
|
|
var outputFile = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", Spec.OutputDirectory, "index.json");
|
|
using (var reader = new StreamReader(File.OpenRead(outputFile)))
|
|
{
|
|
return AutobahnResult.FromReportJson(JObject.Parse(await reader.ReadToEndAsync()));
|
|
}
|
|
}
|
|
|
|
// Async void! It's OK here because we are running in a timer. We're just using async void to chain continuations.
|
|
// There's nobody to await this, hence async void.
|
|
private async void Pinger(CancellationToken token)
|
|
{
|
|
try
|
|
{
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
foreach (var deployment in _deployments)
|
|
{
|
|
if (token.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var resp = await deployment.HttpClient.GetAsync("/ping", token);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
_logger.LogWarning("Non-successful response when pinging {url}: {statusCode} {reasonPhrase}", deployment.ApplicationBaseUri, resp.StatusCode, resp.ReasonPhrase);
|
|
}
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// We don't want to throw when the token fires, just stop.
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while pinging servers");
|
|
}
|
|
}
|
|
|
|
public void Verify(AutobahnResult result)
|
|
{
|
|
var failures = new StringBuilder();
|
|
foreach (var serverResult in result.Servers)
|
|
{
|
|
var serverExpectation = _expectations.FirstOrDefault(e => e.Server == serverResult.Server && e.Ssl == serverResult.Ssl);
|
|
if (serverExpectation == null)
|
|
{
|
|
failures.AppendLine($"Expected no results for server: {serverResult.Name} but found results!");
|
|
}
|
|
else
|
|
{
|
|
serverExpectation.Verify(serverResult, failures);
|
|
}
|
|
}
|
|
|
|
Assert.True(failures.Length == 0, "Autobahn results did not meet expectations:" + Environment.NewLine + failures.ToString());
|
|
}
|
|
|
|
public async Task DeployTestAndAddToSpec(ServerType server, bool ssl, string environment, CancellationToken cancellationToken, Action<AutobahnExpectations> expectationConfig = null)
|
|
{
|
|
var baseUrl = ssl ? "https://localhost:0" : "http://localhost:0";
|
|
var sslNamePart = ssl ? "SSL" : "NoSSL";
|
|
var name = $"{server}|{sslNamePart}|{environment}";
|
|
var logger = _loggerFactory.CreateLogger($"AutobahnTestApp:{server}:{sslNamePart}:{environment}");
|
|
|
|
var appPath = Helpers.GetApplicationPath("AutobahnTestApp");
|
|
var configPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "Http.config");
|
|
var targetFramework =
|
|
#if NETCOREAPP2_1
|
|
"netcoreapp2.1";
|
|
#elif NETCOREAPP2_0
|
|
"netcoreapp2.0";
|
|
#else
|
|
#error Target frameworks need to be updated
|
|
#endif
|
|
var parameters = new DeploymentParameters(appPath, server, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64)
|
|
{
|
|
ApplicationBaseUriHint = baseUrl,
|
|
ApplicationType = ApplicationType.Portable,
|
|
TargetFramework = targetFramework,
|
|
EnvironmentName = environment,
|
|
SiteName = "HttpTestSite", // This is configured in the Http.config
|
|
ServerConfigTemplateContent = (server == ServerType.IISExpress) ? File.ReadAllText(configPath) : null,
|
|
};
|
|
|
|
var deployer = ApplicationDeployerFactory.Create(parameters, _loggerFactory);
|
|
var result = await deployer.DeployAsync();
|
|
_deployers.Add(deployer);
|
|
_deployments.Add(result);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
var handler = new HttpClientHandler();
|
|
if (ssl)
|
|
{
|
|
// Don't take this out of the "if(ssl)". If we set it on some platforms, it crashes
|
|
// So we avoid running SSL tests on those platforms (for now).
|
|
// See https://github.com/dotnet/corefx/issues/9728
|
|
handler.ServerCertificateCustomValidationCallback = (_, __, ___, ____) => true;
|
|
}
|
|
var client = result.CreateHttpClient(handler);
|
|
|
|
// Make sure the server works
|
|
var resp = await RetryHelper.RetryRequest(() =>
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return client.GetAsync(result.ApplicationBaseUri);
|
|
}, logger, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, result.HostShutdownToken).Token);
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Add to the current spec
|
|
var wsUrl = result.ApplicationBaseUri.Replace("https://", "wss://").Replace("http://", "ws://");
|
|
Spec.WithServer(name, wsUrl);
|
|
|
|
var expectations = new AutobahnExpectations(server, ssl, environment);
|
|
expectationConfig?.Invoke(expectations);
|
|
_expectations.Add(expectations);
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var deployer in _deployers)
|
|
{
|
|
deployer.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|