refactor: Replace TestCorrelator with Observable sink

This replacement is necessary to support parallelized unit tests.
TestCorrelator as well as even the InMemory sink rely on static objects,
which makes multithreaded tests impossible.
json-serializing-nullable-fields-issue
Robert Dailey 9 months ago
parent 9d72a01c73
commit 372fd804fe

@ -55,8 +55,8 @@
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit.Analyzers" Version="3.6.1" /> <PackageVersion Include="NUnit.Analyzers" Version="3.6.1" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Serilog.Sinks.Observable" Version="2.0.2" />
<PackageVersion Include="Serilog.Sinks.NUnit" Version="1.0.3" /> <PackageVersion Include="Serilog.Sinks.NUnit" Version="1.0.3" />
<PackageVersion Include="Serilog.Sinks.TestCorrelator" Version="3.2.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.47.0" /> <PackageVersion Include="Spectre.Console.Testing" Version="0.47.0" />
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="19.2.51" /> <PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="19.2.51" />
</ItemGroup> </ItemGroup>
@ -65,4 +65,4 @@
<PackageVersion Include="System.Net.Http" Version="4.3.4" /> <PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -22,8 +22,8 @@
<PackageReference Include="NUnit" PrivateAssets="All" /> <PackageReference Include="NUnit" PrivateAssets="All" />
<PackageReference Include="NUnit.Analyzers" PrivateAssets="All" /> <PackageReference Include="NUnit.Analyzers" PrivateAssets="All" />
<PackageReference Include="NUnit3TestAdapter" PrivateAssets="All" /> <PackageReference Include="NUnit3TestAdapter" PrivateAssets="All" />
<PackageReference Include="Serilog.Sinks.Observable" PrivateAssets="All" />
<PackageReference Include="Serilog.Sinks.NUnit" PrivateAssets="All" /> <PackageReference Include="Serilog.Sinks.NUnit" PrivateAssets="All" />
<PackageReference Include="Serilog.Sinks.TestCorrelator" PrivateAssets="All" />
<PackageReference Include="Spectre.Console.Testing" PrivateAssets="All" /> <PackageReference Include="Spectre.Console.Testing" PrivateAssets="All" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Extensions" PrivateAssets="All" /> <PackageReference Include="TestableIO.System.IO.Abstractions.Extensions" PrivateAssets="All" />
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" PrivateAssets="All" /> <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" PrivateAssets="All" />

@ -1,7 +1,6 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using System.IO.Abstractions.Extensions; using System.IO.Abstractions.Extensions;
using Serilog.Events; using Recyclarr.TestLibrary;
using Serilog.Sinks.TestCorrelator;
namespace Recyclarr.Common.Tests; namespace Recyclarr.Common.Tests;
@ -12,34 +11,23 @@ public class JsonUtilsTest
[Test] [Test]
public void Log_files_that_do_not_exist() public void Log_files_that_do_not_exist()
{ {
using var logContext = TestCorrelator.CreateContext();
var fs = new MockFileSystem(); var fs = new MockFileSystem();
var log = new LoggerConfiguration() var log = new TestableLogger();
.MinimumLevel.Is(LogEventLevel.Debug)
.WriteTo.TestCorrelator()
.CreateLogger();
var path = fs.CurrentDirectory().SubDirectory("doesnt_exist"); var path = fs.CurrentDirectory().SubDirectory("doesnt_exist");
var result = JsonUtils.GetJsonFilesInDirectories(new[] {path}, log); var result = JsonUtils.GetJsonFilesInDirectories(new[] {path}, log);
result.Should().BeEmpty(); result.Should().BeEmpty();
TestCorrelator.GetLogEventsFromContextGuid(logContext.Guid) log.Messages.Should().ContainSingle()
.Should().ContainSingle() .Which.Should().Match("*doesnt_exist*");
.Which.RenderMessage()
.Should().Match("*doesnt_exist*");
} }
[Test] [Test]
public void Log_files_that_only_exist() public void Log_files_that_only_exist()
{ {
var fs = new MockFileSystem(); var fs = new MockFileSystem();
var log = new LoggerConfiguration() var log = new TestableLogger();
.MinimumLevel.Is(LogEventLevel.Debug)
.WriteTo.TestCorrelator()
.CreateLogger();
using var logContext = TestCorrelator.CreateContext();
var path = fs.CurrentDirectory().SubDirectory("exists").File("test.json"); var path = fs.CurrentDirectory().SubDirectory("exists").File("test.json");
fs.AddFile(path.FullName, new MockFileData("")); fs.AddFile(path.FullName, new MockFileData(""));
@ -50,21 +38,14 @@ public class JsonUtilsTest
.Which.FullName .Which.FullName
.Should().Be(path.FullName); .Should().Be(path.FullName);
TestCorrelator.GetLogEventsFromContextGuid(logContext.Guid) log.Messages.Should().BeEmpty();
.Should().BeEmpty();
} }
[Test] [Test]
public void Log_files_that_both_exist_and_do_not_exist() public void Log_files_that_both_exist_and_do_not_exist()
{ {
var fs = new MockFileSystem(); var fs = new MockFileSystem();
var log = new LoggerConfiguration() var log = new TestableLogger();
.MinimumLevel.Is(LogEventLevel.Debug)
.WriteTo.TestCorrelator()
.CreateLogger();
using var logContext = TestCorrelator.CreateContext();
var paths = new[] var paths = new[]
{ {
fs.CurrentDirectory().SubDirectory("does_not_exist"), fs.CurrentDirectory().SubDirectory("does_not_exist"),
@ -82,10 +63,8 @@ public class JsonUtilsTest
.Which.FullName .Which.FullName
.Should().Be(existingFile); .Should().Be(existingFile);
TestCorrelator.GetLogEventsFromContextGuid(logContext.Guid) log.Messages.Should().ContainSingle()
.Should().ContainSingle() .Which.Should().Match("*does_not_exist*");
.Which.RenderMessage()
.Should().Match("*does_not_exist*");
} }
[Test] [Test]

@ -3,6 +3,7 @@
<ProjectReference Include="..\..\Recyclarr.Common\Recyclarr.Common.csproj" /> <ProjectReference Include="..\..\Recyclarr.Common\Recyclarr.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Serilog.Sinks.Console" /> <PackageReference Include="Serilog.Sinks.Console" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -1,23 +1,22 @@
using JetBrains.Annotations;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.TestCorrelator;
namespace Recyclarr.TestLibrary; namespace Recyclarr.TestLibrary;
public sealed class TestableLogger : ILogger, IDisposable [UsedImplicitly]
public sealed class TestableLogger : ILogger
{ {
private readonly Logger _log; private readonly Logger _log;
private ITestCorrelatorContext _logContext; private readonly List<string> _messages = new();
public TestableLogger() public TestableLogger()
{ {
_log = new LoggerConfiguration() _log = new LoggerConfiguration()
.MinimumLevel.Is(LogEventLevel.Verbose) .MinimumLevel.Is(LogEventLevel.Verbose)
.WriteTo.TestCorrelator() .WriteTo.Observers(o => o.Subscribe(x => _messages.Add(x.RenderMessage())))
.WriteTo.Console() .WriteTo.Console()
.CreateLogger(); .CreateLogger();
_logContext = TestCorrelator.CreateContext();
} }
public void Write(LogEvent logEvent) public void Write(LogEvent logEvent)
@ -25,21 +24,5 @@ public sealed class TestableLogger : ILogger, IDisposable
_log.Write(logEvent); _log.Write(logEvent);
} }
public void Dispose() public IEnumerable<string> Messages => _messages;
{
_logContext.Dispose();
_log.Dispose();
}
public void ResetCapturedLogs()
{
_logContext.Dispose();
_logContext = TestCorrelator.CreateContext();
}
public IEnumerable<string> GetRenderedMessages()
{
return TestCorrelator.GetLogEventsFromContextGuid(_logContext.Guid)
.Select(x => x.RenderMessage());
}
} }

@ -3,11 +3,11 @@ using System.IO.Abstractions.Extensions;
using Autofac; using Autofac;
using Autofac.Features.ResolveAnything; using Autofac.Features.ResolveAnything;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.TestLibrary;
using Recyclarr.TestLibrary.Autofac; using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.ApiServices.System; using Recyclarr.TrashLib.ApiServices.System;
using Recyclarr.TrashLib.Repo.VersionControl; using Recyclarr.TrashLib.Repo.VersionControl;
using Recyclarr.TrashLib.Startup; using Recyclarr.TrashLib.Startup;
using Serilog.Events;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Testing; using Spectre.Console.Testing;
@ -24,7 +24,6 @@ public abstract class TrashLibIntegrationFixture : IDisposable
}); });
Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr")); Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr"));
Logger = CreateLogger();
_container = new Lazy<IContainer>(() => _container = new Lazy<IContainer>(() =>
{ {
@ -63,15 +62,6 @@ public abstract class TrashLibIntegrationFixture : IDisposable
// not in the TrashLibAutofacModule. // not in the TrashLibAutofacModule.
} }
private static ILogger CreateLogger()
{
return new LoggerConfiguration()
.MinimumLevel.Is(LogEventLevel.Verbose)
.WriteTo.TestCorrelator()
.WriteTo.Console()
.CreateLogger();
}
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
private readonly Lazy<IContainer> _container; private readonly Lazy<IContainer> _container;
@ -80,7 +70,7 @@ public abstract class TrashLibIntegrationFixture : IDisposable
protected MockFileSystem Fs { get; } protected MockFileSystem Fs { get; }
protected TestConsole Console { get; } = new(); protected TestConsole Console { get; } = new();
protected IAppPaths Paths { get; } protected IAppPaths Paths { get; }
protected ILogger Logger { get; } protected TestableLogger Logger { get; } = new();
// ReSharper restore MemberCanBePrivate.Global // ReSharper restore MemberCanBePrivate.Global

@ -2,7 +2,6 @@ using System.IO.Abstractions;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Parsing; using Recyclarr.TrashLib.Config.Parsing;
using Recyclarr.TrashLib.TestLibrary; using Recyclarr.TrashLib.TestLibrary;
using Serilog.Sinks.TestCorrelator;
namespace Recyclarr.TrashLib.Tests.Config.Parsing; namespace Recyclarr.TrashLib.Tests.Config.Parsing;
@ -59,7 +58,6 @@ public class ConfigurationLoaderSecretsTest : TrashLibIntegrationFixture
[Test] [Test]
public void Throw_when_referencing_invalid_secret() public void Throw_when_referencing_invalid_secret()
{ {
using var logContext = TestCorrelator.CreateContext();
var configLoader = Resolve<ConfigurationLoader>(); var configLoader = Resolve<ConfigurationLoader>();
const string testYml = const string testYml =

@ -10,7 +10,6 @@ using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Parsing; using Recyclarr.TrashLib.Config.Parsing;
using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.TestLibrary; using Recyclarr.TrashLib.TestLibrary;
using Serilog.Sinks.TestCorrelator;
namespace Recyclarr.TrashLib.Tests.Config.Parsing; namespace Recyclarr.TrashLib.Tests.Config.Parsing;
@ -129,8 +128,6 @@ public class ConfigurationLoaderTest : TrashLibIntegrationFixture
[Test] [Test]
public void No_log_when_file_not_empty_but_has_no_desired_sections() public void No_log_when_file_not_empty_but_has_no_desired_sections()
{ {
using var logContext = TestCorrelator.CreateContext();
var sut = Resolve<ConfigurationLoader>(); var sut = Resolve<ConfigurationLoader>();
const string testYml = const string testYml =
""" """
@ -142,8 +139,6 @@ public class ConfigurationLoaderTest : TrashLibIntegrationFixture
sut.Load(testYml).GetConfigsOfType(SupportedServices.Sonarr); sut.Load(testYml).GetConfigsOfType(SupportedServices.Sonarr);
TestCorrelator.GetLogEventsFromContextGuid(logContext.Guid) Logger.Messages.Should().NotContain("Configuration is empty");
.Select(x => x.RenderMessage())
.Should().NotContain("Configuration is empty");
} }
} }

Loading…
Cancel
Save