Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Serilog.Sinks.InMemory

In-memory sink for Serilog to use for testing with [FluentAssertions](https://fluentassertions.com/) support for easy to write assertions.
In-memory sink for Serilog to use for testing with [FluentAssertions](https://fluentassertions.com/), [AwesomeAssertions](https://github.com/AwesomeAssertions/AwesomeAssertions) or [Shouldly]() support for easy to write assertions.

## Build status

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
namespace Serilog.Sinks.InMemory.Assertions
#nullable enable

using System;
using Serilog.Events;

namespace Serilog.Sinks.InMemory.Assertions
{
public interface InMemorySinkAssertions
{
InMemorySink Subject { get; }

LogEventsAssertions HaveMessage(
Func<LogEvent, bool> predicate,
string? predicateErrorName = null,
string because = "",
params object[] becauseArgs);

LogEventsAssertions HaveMessage(
string messageTemplate,
string because = "",
Expand All @@ -10,7 +23,13 @@ LogEventsAssertions HaveMessage(
PatternLogEventsAssertions HaveMessage();

void NotHaveMessage(
string messageTemplate = null,
string? messageTemplate = null,
string because = "",
params object[] becauseArgs);

void NotHaveMessage(
Func<LogEvent, bool> predicate,
string? predicateErrorName = null,
string because = "",
params object[] becauseArgs);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Serilog.Sinks.InMemory.Assertions
{
public interface InMemorySinkAssertionsFactory
{
InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using FluentAssertions;

namespace Serilog.Sinks.InMemory.Assertions;
namespace Serilog.Sinks.InMemory.Assertions;

public interface LogEventPropertyValueAssertions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using FluentAssertions;

namespace Serilog.Sinks.InMemory.Assertions;
namespace Serilog.Sinks.InMemory.Assertions;

public interface LogEventsPropertyAssertion
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.*">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="2.12.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<ProjectReference Include="..\Serilog.Sinks.InMemory\Serilog.Sinks.InMemory.csproj" />
</ItemGroup>

</Project>
166 changes: 83 additions & 83 deletions src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
Expand All @@ -10,98 +10,97 @@ namespace Serilog.Sinks.InMemory.Assertions
{
public static class InMemorySinkAssertionExtensions
{
private static Type? _assertionsType;
private static readonly object SyncRoot = new();
public static InMemorySinkAssertionsFactory AssertionsFactory { get; }

static InMemorySinkAssertionExtensions()
{
var factoryType = GetAssertionsFactoryType() ??
throw new InvalidOperationException("Unable to load InMemorySinkAssertionsFactory");

AssertionsFactory = (InMemorySinkAssertionsFactory)Activator.CreateInstance(factoryType);
}

public static InMemorySinkAssertions Should(this InMemorySink instance)
{
if (_assertionsType == null)
{
lock (SyncRoot)
{
var assemblyLocation =
Path.GetDirectoryName(typeof(InMemorySinkAssertionExtensions).Assembly.Location);
var snapshotInstance = SnapshotOf(instance);
return AssertionsFactory.CreateInMemorySinkAssertions(snapshotInstance);
}

if (string.IsNullOrEmpty(assemblyLocation))
{
throw new Exception($"Unable to determine path to load assemblies from");
}
private static Type? GetAssertionsFactoryType()
{
var assemblyLocation =
Path.GetDirectoryName(typeof(InMemorySinkAssertionExtensions).Assembly.Location);

string? adapterName = null;
int? majorVersion = null;
Assembly? versionedAssembly = null;

// Order is important here, first check the loaded assemblies before
// looking on disk because otherwise we might load FluentAssertions from disk
// while Shouldly is already loaded into the AppDomain and that's the one we
// should be using.
// That's also a guess but hey, if you mix and match assertion frameworks you
// can deal with the fall out.
if (IsFluentAssertionsAlreadyLoadedIntoDomain(out var fluentAssertionsAssembly))
{
adapterName = "FluentAssertions";
majorVersion = fluentAssertionsAssembly.GetName().Version.Major;
}
else if (IsAwesomeAssertionsAlreadyLoadedIntoDomain(out var awesomeAssertionsAssembly))
{
adapterName = "AwesomeAssertions";
majorVersion = awesomeAssertionsAssembly.GetName().Version.Major;
}
else if (IsShouldlyAlreadyLoadedIntoDomain(out var shouldlyAssembly))
{
adapterName = "Shouldly";
majorVersion = shouldlyAssembly.GetName().Version.Major;
}
else if (IsFluentAssertionsAvailableOnDisk(assemblyLocation,
out var fluentAssertionsOnDiskAssembly))
{
adapterName = "FluentAssertions";
majorVersion = fluentAssertionsOnDiskAssembly.GetName().Version.Major;
}
else if (IsAwesomeAssertionsAvailableOnDisk(assemblyLocation,
out var awesomeAssertionsOnDiskAssembly))
{
adapterName = "AwesomeAssertions";
majorVersion = awesomeAssertionsOnDiskAssembly.GetName().Version.Major;
}
else if (IsShouldlyAvailableOnDisk(assemblyLocation, out var shouldlyOnDiskAssembly))
{
adapterName = "Shouldly";
majorVersion = shouldlyOnDiskAssembly.GetName().Version.Major;
}
if (string.IsNullOrEmpty(assemblyLocation))
{
throw new Exception($"Unable to determine path to load assemblies from");
}

if (adapterName != null && majorVersion != null)
{
var versionedLocation = Path.Combine(
assemblyLocation,
$"Serilog.Sinks.InMemory.{adapterName}{majorVersion}.dll");

if (!File.Exists(versionedLocation))
{
throw new InvalidOperationException($"Detected {adapterName} version {majorVersion} but the assertions adapter wasn't found on disk");
}

versionedAssembly = Assembly.LoadFile(versionedLocation);
}
string? adapterName = null;
int? majorVersion = null;
Assembly? versionedAssembly = null;

// Order is important here, first check the loaded assemblies before
// looking on disk because otherwise we might load FluentAssertions from disk
// while Shouldly is already loaded into the AppDomain and that's the one we
// should be using.
// That's also a guess but hey, if you mix and match assertion frameworks you
// can deal with the fall out.
if (IsFluentAssertionsAlreadyLoadedIntoDomain(out var fluentAssertionsAssembly))
{
adapterName = "FluentAssertions";
majorVersion = fluentAssertionsAssembly.GetName().Version.Major;
}
else if (IsAwesomeAssertionsAlreadyLoadedIntoDomain(out var awesomeAssertionsAssembly))
{
adapterName = "AwesomeAssertions";
majorVersion = awesomeAssertionsAssembly.GetName().Version.Major;
}
else if (IsShouldlyAlreadyLoadedIntoDomain(out var shouldlyAssembly))
{
adapterName = "Shouldly";
majorVersion = shouldlyAssembly.GetName().Version.Major;
}
else if (IsFluentAssertionsAvailableOnDisk(assemblyLocation,
out var fluentAssertionsOnDiskAssembly))
{
adapterName = "FluentAssertions";
majorVersion = fluentAssertionsOnDiskAssembly.GetName().Version.Major;
}
else if (IsAwesomeAssertionsAvailableOnDisk(assemblyLocation,
out var awesomeAssertionsOnDiskAssembly))
{
adapterName = "AwesomeAssertions";
majorVersion = awesomeAssertionsOnDiskAssembly.GetName().Version.Major;
}
else if (IsShouldlyAvailableOnDisk(assemblyLocation, out var shouldlyOnDiskAssembly))
{
adapterName = "Shouldly";
majorVersion = shouldlyOnDiskAssembly.GetName().Version.Major;
}

if (versionedAssembly != null)
{
_assertionsType = versionedAssembly
.GetTypes()
.SingleOrDefault(t => t.Name == "InMemorySinkAssertionsImpl");
}
if (adapterName != null && majorVersion != null)
{
var versionedLocation = Path.Combine(
assemblyLocation,
$"Serilog.Sinks.InMemory.{adapterName}{majorVersion}.dll");

if (!File.Exists(versionedLocation))
{
throw new InvalidOperationException($"Detected {adapterName} version {majorVersion} but the assertions adapter wasn't found on disk");
}

versionedAssembly = Assembly.LoadFile(versionedLocation);
}

if (_assertionsType == null)
if (versionedAssembly != null)
{
throw new InvalidOperationException("Unable to load InMemorySinkAssertions");
return versionedAssembly
.GetTypes()
.SingleOrDefault(t => t.Name == "InMemorySinkAssertionsFactoryImpl");
}

var snapshotInstance = SnapshotOf(instance);

return (InMemorySinkAssertions)Activator.CreateInstance(
_assertionsType, snapshotInstance);
return null;
}

private static bool IsFluentAssertionsAlreadyLoadedIntoDomain(
Expand Down Expand Up @@ -161,8 +160,9 @@ private static bool IsFluentAssertionsAvailableOnDisk(
assembly = Assembly.LoadFile(assemblyPath);

var metadataAttributes = assembly.GetCustomAttributes<AssemblyMetadataAttribute>().ToList();

if(!metadataAttributes.Any() || metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase)))

if (!metadataAttributes.Any() ||
metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase)))
{
return true;
}
Expand All @@ -176,7 +176,7 @@ private static bool IsAwesomeAssertionsAvailableOnDisk(
string assemblyLocation,
[NotNullWhen(true)] out Assembly? assembly)
{
var assemblyPath = Path.Combine(assemblyLocation, "FluentAssertions.dll");
var assemblyPath = Path.Combine(assemblyLocation, "AwesomeAssertions.dll");

if (File.Exists(assemblyPath))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Serilog.Sinks.InMemory.Assertions;

namespace Serilog.Sinks.InMemory.AwesomeAssertions8
{
public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory
{
public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance)
{
return new InMemorySinkAssertionsImpl(snapshotInstance);
}
}
}
Loading