From 85b2c581fd0c75f60834b25e2f003a8cb57d4164 Mon Sep 17 00:00:00 2001
From: Martijn Bodeman <11424653+skwasjer@users.noreply.github.com>
Date: Sat, 7 Sep 2024 03:57:40 +0200
Subject: [PATCH] chore(deps): bump test dependencies (#109)
* chore(deps): replaced Serilog with own log capturing. Although we have to maintain some extra code, Serilog is not easy to maintain with the different target frameworks/libraries and its dependencies.
* chore(deps): bump test dependencies xunit, Microsoft.NET.Test.Sdk and Newtonsoft.Json
---
test/Directory.Build.targets | 4 +-
.../Fixtures/CapturingLoggerFactoryFixture.cs | 230 ++++++++++++++++++
.../Fixtures/LoggerFactoryFixture.cs | 53 ++++
.../Fixtures/MockHttpServerFixture.cs | 50 ++--
.../MockHttp.Server.Tests.csproj | 7 +-
test/MockHttp.Testing/MockHttp.Testing.csproj | 2 +-
6 files changed, 306 insertions(+), 40 deletions(-)
create mode 100644 test/MockHttp.Server.Tests/Fixtures/CapturingLoggerFactoryFixture.cs
create mode 100644 test/MockHttp.Server.Tests/Fixtures/LoggerFactoryFixture.cs
diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets
index 4c92c1dc..c95cdac5 100644
--- a/test/Directory.Build.targets
+++ b/test/Directory.Build.targets
@@ -4,7 +4,7 @@
false
false
$(NoWarn);NU1902;NU1903
- 17.8.0
+ 17.11.1
@@ -20,7 +20,7 @@
-
+
diff --git a/test/MockHttp.Server.Tests/Fixtures/CapturingLoggerFactoryFixture.cs b/test/MockHttp.Server.Tests/Fixtures/CapturingLoggerFactoryFixture.cs
new file mode 100644
index 00000000..2ff20365
--- /dev/null
+++ b/test/MockHttp.Server.Tests/Fixtures/CapturingLoggerFactoryFixture.cs
@@ -0,0 +1,230 @@
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Options;
+
+namespace MockHttp.Fixtures;
+
+public delegate void CaptureDelegate(string message);
+
+public class CapturingLoggerFactoryFixture : LoggerFactoryFixture
+{
+ private static readonly AsyncLocal LogContextLocal = new();
+
+ public CapturingLoggerFactoryFixture()
+ : base(configure => configure
+ .AddConsole(opts => opts.FormatterName = ConsoleCapture.NameKey)
+ .AddConsoleFormatter(opts => opts.IncludeScopes = true)
+ .Services.AddSingleton((CaptureDelegate)(message => LogContextLocal.Value?.Events.Add(message)))
+ )
+ {
+ }
+
+ public static LogContext CreateContext()
+ {
+ return LogContextLocal.Value = new LogContext(() => LogContextLocal.Value = null);
+ }
+
+ public sealed class LogContext(Action dispose) : IDisposable
+ {
+ public List Events { get; } = new();
+
+ public void Dispose()
+ {
+ dispose();
+ }
+ }
+
+ private class ConsoleCapture : ConsoleFormatter
+ {
+ internal const string NameKey = "console-capture";
+ private const string LogLevelPadding = ": ";
+ private static readonly string MessagePadding = new(' ', GetLogLevelString(LogLevel.Information).Length + LogLevelPadding.Length);
+ private static readonly string NewLineWithMessagePadding = Environment.NewLine + MessagePadding;
+
+ private readonly CaptureDelegate _onWrite;
+
+ private readonly ConsoleCaptureOptions _options;
+
+ public ConsoleCapture
+ (
+ CaptureDelegate onWrite,
+ IOptions options
+ ) : base(NameKey)
+ {
+ _onWrite = onWrite ?? throw new ArgumentNullException(nameof(onWrite));
+ _options = options.Value;
+ }
+
+ public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
+ {
+ var sb = new StringBuilder();
+ textWriter = new StringWriter(sb);
+
+ string? message = logEntry.Formatter(logEntry.State, logEntry.Exception);
+ if (logEntry.Exception is null && message is null)
+ {
+ return;
+ }
+
+ LogLevel logLevel = logEntry.LogLevel;
+ string logLevelString = GetLogLevelString(logLevel);
+
+ string? timestamp = null;
+ string? timestampFormat = _options.TimestampFormat;
+ if (timestampFormat is not null)
+ {
+ DateTimeOffset dateTimeOffset = GetCurrentDateTime();
+ timestamp = dateTimeOffset.ToString(timestampFormat);
+ }
+
+ if (!string.IsNullOrEmpty(timestamp))
+ {
+ textWriter.Write(timestamp);
+ }
+
+ if (!string.IsNullOrEmpty(logLevelString))
+ {
+ textWriter.Write(logLevelString);
+ }
+
+ CreateDefaultLogMessage(textWriter, in logEntry, message, scopeProvider);
+
+ _onWrite(sb.ToString());
+ }
+
+ private void CreateDefaultLogMessage(TextWriter textWriter, in LogEntry logEntry, string message, IExternalScopeProvider? scopeProvider)
+ {
+ bool singleLine = _options.SingleLine;
+ int eventId = logEntry.EventId.Id;
+ Exception? exception = logEntry.Exception;
+
+ // Example:
+ // info: ConsoleApp.Program[10]
+ // Request received
+
+ // category and event id
+ textWriter.Write(LogLevelPadding);
+ textWriter.Write(logEntry.Category);
+ textWriter.Write('[');
+
+#if NETCOREAPP
+ Span span = stackalloc char[10];
+ if (eventId.TryFormat(span, out int charsWritten))
+ {
+ textWriter.Write(span.Slice(0, charsWritten));
+ }
+ else
+#endif
+ {
+ textWriter.Write(eventId.ToString());
+ }
+
+ textWriter.Write(']');
+ if (!singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+
+ // scope information
+ WriteScopeInformation(textWriter, scopeProvider, singleLine);
+ WriteMessage(textWriter, message, singleLine);
+
+ // Example:
+ // System.InvalidOperationException
+ // at Namespace.Class.Function() in File:line X
+ if (exception is not null)
+ {
+ // exception message
+ WriteMessage(textWriter, exception.ToString(), singleLine);
+ }
+
+ if (singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+
+ private static void WriteMessage(TextWriter textWriter, string message, bool singleLine)
+ {
+ if (!string.IsNullOrEmpty(message))
+ {
+ if (singleLine)
+ {
+ textWriter.Write(' ');
+ WriteReplacing(textWriter, Environment.NewLine, " ", message);
+ }
+ else
+ {
+ textWriter.Write(MessagePadding);
+ WriteReplacing(textWriter, Environment.NewLine, NewLineWithMessagePadding, message);
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+
+ static void WriteReplacing(TextWriter writer, string oldValue, string newValue, string message)
+ {
+ string newMessage = message.Replace(oldValue, newValue);
+ writer.Write(newMessage);
+ }
+ }
+
+ private void WriteScopeInformation(TextWriter textWriter, IExternalScopeProvider? scopeProvider, bool singleLine)
+ {
+ if (_options.IncludeScopes && scopeProvider is not null)
+ {
+ bool paddingNeeded = !singleLine;
+ scopeProvider.ForEachScope((scope, state) =>
+ {
+ if (paddingNeeded)
+ {
+ paddingNeeded = false;
+ state.Write(MessagePadding);
+ state.Write("=> ");
+ }
+ else
+ {
+ state.Write(" => ");
+ }
+
+ state.Write(scope);
+ },
+ textWriter);
+
+ if (!paddingNeeded && !singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+ }
+
+ private static string GetLogLevelString(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Trace => "trce",
+ LogLevel.Debug => "dbug",
+ LogLevel.Information => "info",
+ LogLevel.Warning => "warn",
+ LogLevel.Error => "fail",
+ LogLevel.Critical => "crit",
+ _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+ };
+ }
+
+ private DateTimeOffset GetCurrentDateTime()
+ {
+ return _options.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now;
+ }
+ }
+
+ private class ConsoleCaptureOptions : ConsoleFormatterOptions
+ {
+ ///
+ /// When , the entire message gets logged in a single line.
+ ///
+ public bool SingleLine { get; set; }
+ }
+}
diff --git a/test/MockHttp.Server.Tests/Fixtures/LoggerFactoryFixture.cs b/test/MockHttp.Server.Tests/Fixtures/LoggerFactoryFixture.cs
new file mode 100644
index 00000000..5b09f61f
--- /dev/null
+++ b/test/MockHttp.Server.Tests/Fixtures/LoggerFactoryFixture.cs
@@ -0,0 +1,53 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace MockHttp.Fixtures;
+
+public abstract class LoggerFactoryFixture : IAsyncLifetime, IAsyncDisposable
+{
+ private readonly ServiceProvider _services;
+
+ protected LoggerFactoryFixture(Action? configure = null)
+ {
+ _services = new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.SetMinimumLevel(LogLevel.Trace);
+
+ builder
+ .AddDebug()
+#if NET6_0_OR_GREATER
+ .AddSimpleConsole(opts => opts.IncludeScopes = true)
+#endif
+ ;
+
+ configure?.Invoke(builder);
+ })
+ .BuildServiceProvider();
+
+ Factory = _services.GetRequiredService();
+ }
+
+ public ILoggerFactory Factory { get; }
+
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore();
+ GC.SuppressFinalize(this);
+ }
+
+ Task IAsyncLifetime.InitializeAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ Task IAsyncLifetime.DisposeAsync()
+ {
+ return DisposeAsync().AsTask();
+ }
+
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ await _services.DisposeAsync();
+ }
+}
diff --git a/test/MockHttp.Server.Tests/Fixtures/MockHttpServerFixture.cs b/test/MockHttp.Server.Tests/Fixtures/MockHttpServerFixture.cs
index 798e8ffe..5c864da3 100644
--- a/test/MockHttp.Server.Tests/Fixtures/MockHttpServerFixture.cs
+++ b/test/MockHttp.Server.Tests/Fixtures/MockHttpServerFixture.cs
@@ -1,18 +1,13 @@
using System.Net.NetworkInformation;
using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.Logging;
-using Serilog;
-using Serilog.Core;
-using Serilog.Events;
-using Serilog.Extensions.Logging;
-using Serilog.Sinks.TestCorrelator;
using Xunit.Abstractions;
namespace MockHttp.Fixtures;
public class MockHttpServerFixture : IDisposable, IAsyncLifetime
{
- private ITestCorrelatorContext? _testCorrelatorContext;
+ private readonly CapturingLoggerFactoryFixture _loggerFactoryFixture;
+ private CapturingLoggerFactoryFixture.LogContext? _loggerCtx;
public MockHttpServerFixture()
: this("http")
@@ -21,17 +16,11 @@ public MockHttpServerFixture()
protected MockHttpServerFixture(string scheme)
{
- Logger logger = new LoggerConfiguration()
- .MinimumLevel.Verbose()
- .Enrich.FromLogContext()
- .WriteTo.TestCorrelator()
- .WriteTo.Debug()
- .CreateLogger();
- LoggerFactory = new SerilogLoggerFactory(logger);
+ _loggerFactoryFixture = new CapturingLoggerFactoryFixture();
Handler = new MockHttpHandler();
Server = new MockHttpServer(
Handler,
- LoggerFactory,
+ _loggerFactoryFixture.Factory,
new Uri(
SupportsIpv6()
? $"{scheme}://[::1]:0"
@@ -42,33 +31,33 @@ protected MockHttpServerFixture(string scheme)
.Configure(builder => builder
.Use((_, next) =>
{
- _testCorrelatorContext ??= TestCorrelator.CreateContext();
+ _loggerCtx ??= CapturingLoggerFactoryFixture.CreateContext();
return next();
})
);
}
- public ILoggerFactory LoggerFactory { get; }
-
public MockHttpHandler Handler { get; }
public MockHttpServer Server { get; }
- public void Dispose()
+ public Task InitializeAsync()
{
- Server.Dispose();
- Handler.Dispose();
- GC.SuppressFinalize(this);
+ return Server.StartAsync();
}
- public Task InitializeAsync()
+ public async Task DisposeAsync()
{
- return Server.StartAsync();
+ await _loggerFactoryFixture.DisposeAsync();
+ await Server.DisposeAsync();
}
- public Task DisposeAsync()
+ public void Dispose()
{
- return Server.DisposeAsync().AsTask();
+ _loggerCtx?.Dispose();
+ Server.Dispose();
+ Handler.Dispose();
+ GC.SuppressFinalize(this);
}
private static bool SupportsIpv6()
@@ -80,21 +69,20 @@ private static bool SupportsIpv6()
// ReSharper disable once MemberCanBeMadeStatic.Global
public void LogServerTrace(ITestOutputHelper testOutputHelper)
{
- if (_testCorrelatorContext is null)
+ if (_loggerCtx is null)
{
return;
}
- var logEvents = TestCorrelator.GetLogEventsFromContextGuid(_testCorrelatorContext.Guid).ToList();
- foreach (LogEvent logEvent in logEvents)
+ foreach (string msg in _loggerCtx.Events)
{
- testOutputHelper.WriteLine(logEvent.RenderMessage());
+ testOutputHelper.WriteLine(msg);
}
}
public void Reset()
{
Handler.Reset();
- _testCorrelatorContext = null;
+ _loggerCtx = null;
}
}
diff --git a/test/MockHttp.Server.Tests/MockHttp.Server.Tests.csproj b/test/MockHttp.Server.Tests/MockHttp.Server.Tests.csproj
index f639c14f..0c2e5b68 100644
--- a/test/MockHttp.Server.Tests/MockHttp.Server.Tests.csproj
+++ b/test/MockHttp.Server.Tests/MockHttp.Server.Tests.csproj
@@ -6,8 +6,6 @@
true
MockHttp
- 8.0.0
- 3.1.0
@@ -15,10 +13,7 @@
-
-
-
-
+
diff --git a/test/MockHttp.Testing/MockHttp.Testing.csproj b/test/MockHttp.Testing/MockHttp.Testing.csproj
index ef75910a..e63981d9 100644
--- a/test/MockHttp.Testing/MockHttp.Testing.csproj
+++ b/test/MockHttp.Testing/MockHttp.Testing.csproj
@@ -10,7 +10,7 @@
-
+