Increase ServerFixture startup timeout (#1409)

This commit is contained in:
Andrew Stanton-Nurse 2018-02-06 08:41:16 -08:00 committed by GitHub
parent a4d49bfbf0
commit a0c47c0c66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 88 deletions

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Logging.Testing;
namespace Microsoft.AspNetCore.SignalR.Tests
{
// WriteContext, but with a timestamp...
internal class LogRecord
{
public DateTime Timestamp { get; }
public WriteContext Write { get; }
public LogRecord(DateTime timestamp, WriteContext write)
{
Timestamp = timestamp;
Write = write;
}
}
}

View File

@ -2,15 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
namespace Microsoft.AspNetCore.SignalR.Tests namespace Microsoft.AspNetCore.SignalR.Tests
@ -23,7 +26,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests
private IWebHost _host; private IWebHost _host;
private IApplicationLifetime _lifetime; private IApplicationLifetime _lifetime;
private readonly IDisposable _logToken; private readonly IDisposable _logToken;
private AsyncForwardingLoggerProvider _asyncLoggerProvider;
private readonly LogSinkProvider _logSinkProvider;
public string WebSocketsUrl => Url.Replace("http", "ws"); public string WebSocketsUrl => Url.Replace("http", "ws");
@ -31,27 +35,24 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public ServerFixture() public ServerFixture()
{ {
_asyncLoggerProvider = new AsyncForwardingLoggerProvider(); _logSinkProvider = new LogSinkProvider();
var testLog = AssemblyTestLog.ForAssembly(typeof(TStartup).Assembly); var testLog = AssemblyTestLog.ForAssembly(typeof(TStartup).Assembly);
_logToken = testLog.StartTestLog(null, $"{nameof(ServerFixture<TStartup>)}_{typeof(TStartup).Name}", out _loggerFactory, "ServerFixture"); _logToken = testLog.StartTestLog(null, $"{nameof(ServerFixture<TStartup>)}_{typeof(TStartup).Name}", out _loggerFactory, "ServerFixture");
_loggerFactory.AddProvider(_asyncLoggerProvider);
_logger = _loggerFactory.CreateLogger<ServerFixture<TStartup>>(); _logger = _loggerFactory.CreateLogger<ServerFixture<TStartup>>();
StartServer();
}
private void StartServer()
{
// We're using 127.0.0.1 instead of localhost to ensure that we use IPV4 across different OSes // We're using 127.0.0.1 instead of localhost to ensure that we use IPV4 across different OSes
Url = "http://127.0.0.1:" + GetNextPort(); var url = "http://127.0.0.1:0";
StartServer(Url);
}
public void SetTestLoggerFactory(ILoggerFactory loggerFactory)
{
_asyncLoggerProvider.SetLoggerFactory(loggerFactory);
}
private void StartServer(string url)
{
_host = new WebHostBuilder() _host = new WebHostBuilder()
.ConfigureLogging(builder => builder.AddProvider(new ForwardingLoggerProvider(_loggerFactory))) .ConfigureLogging(builder => builder
.AddProvider(_logSinkProvider)
.AddProvider(new ForwardingLoggerProvider(_loggerFactory)))
.UseStartup(typeof(TStartup)) .UseStartup(typeof(TStartup))
.UseKestrel() .UseKestrel()
.UseUrls(url) .UseUrls(url)
@ -61,17 +62,24 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var t = Task.Run(() => _host.Start()); var t = Task.Run(() => _host.Start());
_logger.LogInformation("Starting test server..."); _logger.LogInformation("Starting test server...");
_lifetime = _host.Services.GetRequiredService<IApplicationLifetime>(); _lifetime = _host.Services.GetRequiredService<IApplicationLifetime>();
if (!_lifetime.ApplicationStarted.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
// This only happens once per fixture, so we can afford to wait a little bit on it.
if (!_lifetime.ApplicationStarted.WaitHandle.WaitOne(TimeSpan.FromSeconds(20)))
{ {
// t probably faulted // t probably faulted
if (t.IsFaulted) if (t.IsFaulted)
{ {
throw t.Exception.InnerException; throw t.Exception.InnerException;
} }
throw new TimeoutException("Timed out waiting for application to start.");
var logs = _logSinkProvider.GetLogs();
throw new TimeoutException($"Timed out waiting for application to start.{Environment.NewLine}Startup Logs:{Environment.NewLine}{RenderLogs(logs)}");
} }
_logger.LogInformation("Test Server started"); _logger.LogInformation("Test Server started");
// Get the URL from the server
Url = _host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.Single();
_lifetime.ApplicationStopped.Register(() => _lifetime.ApplicationStopped.Register(() =>
{ {
_logger.LogInformation("Test server shut down"); _logger.LogInformation("Test server shut down");
@ -79,6 +87,24 @@ namespace Microsoft.AspNetCore.SignalR.Tests
}); });
} }
private string RenderLogs(IList<LogRecord> logs)
{
var builder = new StringBuilder();
foreach (var log in logs)
{
builder.AppendLine($"{log.Timestamp:O} {log.Write.LoggerName} {log.Write.LogLevel}: {log.Write.Formatter(log.Write.State, log.Write.Exception)}");
if (log.Write.Exception != null)
{
var message = log.Write.Exception.ToString();
foreach (var line in message.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
{
builder.AppendLine($"| {line}");
}
}
}
return builder.ToString();
}
public void Dispose() public void Dispose()
{ {
_logger.LogInformation("Shutting down test server"); _logger.LogInformation("Shutting down test server");
@ -86,62 +112,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests
_loggerFactory.Dispose(); _loggerFactory.Dispose();
} }
private class AsyncForwardingLoggerProvider : ILoggerProvider
{
private AsyncLocal<ILoggerFactory> _localLogger = new AsyncLocal<ILoggerFactory>();
public ILogger CreateLogger(string categoryName)
{
return new AsyncLocalForwardingLogger(categoryName, _localLogger);
}
public void Dispose()
{
}
public void SetLoggerFactory(ILoggerFactory loggerFactory)
{
_localLogger.Value = loggerFactory;
}
private class AsyncLocalForwardingLogger : ILogger
{
private string _categoryName;
private AsyncLocal<ILoggerFactory> _localLoggerFactory;
public AsyncLocalForwardingLogger(string categoryName, AsyncLocal<ILoggerFactory> localLoggerFactory)
{
_categoryName = categoryName;
_localLoggerFactory = localLoggerFactory;
}
public IDisposable BeginScope<TState>(TState state)
{
return GetLocalLogger().BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return GetLocalLogger().IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
GetLocalLogger().Log(logLevel, eventId, state, exception, formatter);
}
private ILogger GetLocalLogger()
{
var factory = _localLoggerFactory.Value;
if (factory == null)
{
return NullLogger.Instance;
}
return factory.CreateLogger(_categoryName);
}
}
}
private class ForwardingLoggerProvider : ILoggerProvider private class ForwardingLoggerProvider : ILoggerProvider
{ {
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
@ -161,18 +131,63 @@ namespace Microsoft.AspNetCore.SignalR.Tests
} }
} }
// Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508 // TestSink doesn't seem to be thread-safe :(.
private static int GetNextPort() private class LogSinkProvider : ILoggerProvider
{ {
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) private ConcurrentQueue<LogRecord> _logs = new ConcurrentQueue<LogRecord>();
public ILogger CreateLogger(string categoryName)
{ {
// Let the OS assign the next available port. Unless we cycle through all ports return new LogSinkLogger(categoryName, this);
// on a test run, the OS will always increment the port number when making these calls. }
// This prevents races in parallel test runs where a test is already bound to
// a given port, and a new test is able to bind to the same port due to port public void Dispose()
// reuse being enabled by default by the OS. {
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); }
return ((IPEndPoint)socket.LocalEndPoint).Port;
public IList<LogRecord> GetLogs() => _logs.ToList();
public void Log<TState>(string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var record = new LogRecord(
DateTime.Now,
new WriteContext()
{
LoggerName = categoryName,
LogLevel = logLevel,
EventId = eventId,
State = state,
Exception = exception,
Formatter = (o, e) => formatter((TState)o, e),
});
_logs.Enqueue(record);
}
private class LogSinkLogger : ILogger
{
private string _categoryName;
private LogSinkProvider _logSinkProvider;
public LogSinkLogger(string categoryName, LogSinkProvider logSinkProvider)
{
_categoryName = categoryName;
_logSinkProvider = logSinkProvider;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_logSinkProvider.Log(_categoryName, logLevel, eventId, state, exception, formatter);
}
} }
} }
} }

View File

@ -326,8 +326,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{ {
using (StartLog(out var loggerFactory, testName: $"ConnectionCanSendAndReceiveMessages_{transportType.ToString()}")) using (StartLog(out var loggerFactory, testName: $"ConnectionCanSendAndReceiveMessages_{transportType.ToString()}"))
{ {
_serverFixture.SetTestLoggerFactory(loggerFactory);
var logger = loggerFactory.CreateLogger<EndToEndTests>(); var logger = loggerFactory.CreateLogger<EndToEndTests>();
var url = _serverFixture.Url + "/uncreatable"; var url = _serverFixture.Url + "/uncreatable";

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;