diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index 4fcbc5d32e..4980d732d8 100644 --- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Globalization; using Elastic.CommonSchema.Serilog; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Serilog; @@ -27,6 +28,11 @@ namespace Tgstation.Server.Host.Extensions /// static Type chatProviderFactoryType = typeof(ProviderFactory); + /// + /// A for an additional to use. + /// + static ServiceDescriptor additionalLoggerProvider; + /// /// Change the used as an implementation for calls to . /// @@ -36,6 +42,17 @@ namespace Tgstation.Server.Host.Extensions chatProviderFactoryType = typeof(TProviderFactory); } + /// + /// Add an additional to s that call . + /// + /// The of to add. + public static void UseAdditionalLoggerProvider() where TLoggerProvider : class, ILoggerProvider + { + if (additionalLoggerProvider != null) + throw new InvalidOperationException("Cannot have multiple additionalLoggerProviders!"); + additionalLoggerProvider = ServiceDescriptor.Singleton(); + } + /// /// Adds a implementation to the given . /// @@ -133,6 +150,9 @@ namespace Tgstation.Server.Host.Extensions if (Debugger.IsAttached) builder.AddDebug(); + + if (additionalLoggerProvider != null) + builder.Services.TryAddEnumerable(additionalLoggerProvider); }); } } diff --git a/tests/Tgstation.Server.Tests/Live/HardFailLogger.cs b/tests/Tgstation.Server.Tests/Live/HardFailLogger.cs new file mode 100644 index 0000000000..22b46a5532 --- /dev/null +++ b/tests/Tgstation.Server.Tests/Live/HardFailLogger.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Tgstation.Server.Tests.Live +{ + sealed class HardFailLogger : ILogger + { + readonly Action failureSink; + + public HardFailLogger(Action failureSink) + { + this.failureSink = failureSink ?? throw new ArgumentNullException(nameof(failureSink)); + } + + public IDisposable BeginScope(TState state) => Task.CompletedTask; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + var logMessage = formatter(state, exception); + if (logLevel == LogLevel.Error || (logLevel == LogLevel.Critical && logMessage != "DropDatabase configuration option set! Dropping any existing database...")) + failureSink(new AssertFailedException(logMessage)); + } + } +} diff --git a/tests/Tgstation.Server.Tests/Live/HardFailLoggerProvider.cs b/tests/Tgstation.Server.Tests/Live/HardFailLoggerProvider.cs new file mode 100644 index 0000000000..e72ea557db --- /dev/null +++ b/tests/Tgstation.Server.Tests/Live/HardFailLoggerProvider.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + +namespace Tgstation.Server.Tests.Live +{ + sealed class HardFailLoggerProvider : ILoggerProvider + { + public static bool BlockFails { get; set; } + + public static Task FailureSource => failureSink.Task; + + static readonly TaskCompletionSource failureSink = new TaskCompletionSource(); + + public ILogger CreateLogger(string categoryName) => new HardFailLogger(ex => + { + if (!BlockFails) + failureSink.TrySetException(ex); + }); + + public void Dispose() { } + } +} diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index e39d36650e..535d4d124c 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -698,6 +698,26 @@ namespace Tgstation.Server.Tests.Live [TestMethod] public async Task TestStandardTgsOperation() { + const int MaximumTestMinutes = 30; + using var hardCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaximumTestMinutes)); + var hardCancellationToken = hardCancellationTokenSource.Token; + + ServiceCollectionExtensions.UseAdditionalLoggerProvider(); + + var internalTask = TestTgsInternal(hardCancellationToken); + await Task.WhenAny( + internalTask, + HardFailLoggerProvider.FailureSource); + + if (!internalTask.IsCompleted) + { + hardCancellationTokenSource.Cancel(); + await Task.WhenAll(internalTask, HardFailLoggerProvider.FailureSource); + } + } + + async Task TestTgsInternal(CancellationToken hardCancellationToken) + { var discordConnectionString = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_TOKEN"); var ircConnectionString = Environment.GetEnvironmentVariable("TGS_TEST_IRC_CONNECTION_STRING"); var missingChatVarsCount = Convert.ToInt32(String.IsNullOrWhiteSpace(discordConnectionString)) @@ -734,9 +754,6 @@ namespace Tgstation.Server.Tests.Live using var server = new LiveTestingServer(null, true); - const int MaximumTestMinutes = 180; - using var hardTimeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaximumTestMinutes)); - var hardCancellationToken = hardTimeoutCancellationTokenSource.Token; using var serverCts = CancellationTokenSource.CreateLinkedTokenSource(hardCancellationToken); var cancellationToken = serverCts.Token; @@ -816,12 +833,13 @@ namespace Tgstation.Server.Tests.Live // http bind test https://github.com/tgstation/tgstation-server/issues/1065 if (new PlatformIdentifier().IsWindows) { - using var blockingSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); - blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); - blockingSocket.Bind(new IPEndPoint(IPAddress.Any, server.Url.Port)); + HardFailLoggerProvider.BlockFails = true; try { + using var blockingSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); + blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + blockingSocket.Bind(new IPEndPoint(IPAddress.Any, server.Url.Port)); // bind test run await server.Run(cancellationToken); Assert.Fail("Expected server task to end with a SocketException"); @@ -830,6 +848,10 @@ namespace Tgstation.Server.Tests.Live { Assert.AreEqual(ex.SocketErrorCode, SocketError.AddressAlreadyInUse); } + finally + { + HardFailLoggerProvider.BlockFails = false; + } } await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));