From 8fd1da411cc504fc1d2b51ea9ad9242afd9788cf Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 1 May 2025 00:04:28 +0700 Subject: [PATCH 1/2] Add Akka.TestKit.Xunit3 implementation --- Akka.sln | 30 +++ Directory.Build.props | 2 + .../Akka.TestKit.Xunit3.Tests.csproj | 27 +++ .../Internals/AkkaEqualExceptionSpec.cs | 33 +++ .../XunitAssertionsSpec.cs | 81 +++++++ .../Akka.TestKit.Xunit3.csproj | 20 ++ .../Attributes/LocalFactAttribute.cs | 70 ++++++ .../Attributes/LocalTheoryAttribute.cs | 34 +++ .../Attributes/WindowsFactAttribute.cs | 71 ++++++ .../Internals/AkkaAssertEqualityComparer.cs | 100 ++++++++ .../AkkaAssertEqualityComparerAdapter.cs | 37 +++ .../Internals/AkkaEqualException.cs | 87 +++++++ .../Akka.TestKit.Xunit3/Internals/Loggers.cs | 66 ++++++ .../Properties/FriendsOf.cs | 10 + .../testkits/Akka.TestKit.Xunit3/TestKit.cs | 220 ++++++++++++++++++ .../Akka.TestKit.Xunit3/XunitAssertions.cs | 87 +++++++ 16 files changed, 975 insertions(+) create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs create mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs diff --git a/Akka.sln b/Akka.sln index 6d5dafd201e..70d879b67e7 100644 --- a/Akka.sln +++ b/Akka.sln @@ -277,6 +277,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClusterToolsExample.Node", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit2.Tests", "src\contrib\testkits\Akka.TestKit.Xunit2.Tests\Akka.TestKit.Xunit2.Tests.csproj", "{95017C99-E960-44E5-83AD-BF21461DF06F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit3", "src\contrib\testkits\Akka.TestKit.Xunit3\Akka.TestKit.Xunit3.csproj", "{D7A0BB43-509A-469A-BBC7-1123634425C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit3.Tests", "src\contrib\testkits\Akka.TestKit.Xunit3.Tests\Akka.TestKit.Xunit3.Tests.csproj", "{DE5C2854-6B2F-48BE-881A-00E18C567DEA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1304,6 +1308,30 @@ Global {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x64.Build.0 = Release|Any CPU {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.ActiveCfg = Release|Any CPU {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.Build.0 = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x64.Build.0 = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x86.Build.0 = Debug|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|Any CPU.Build.0 = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x64.ActiveCfg = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x64.Build.0 = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x86.ActiveCfg = Release|Any CPU + {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x86.Build.0 = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x64.Build.0 = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x86.Build.0 = Debug|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|Any CPU.Build.0 = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x64.ActiveCfg = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x64.Build.0 = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x86.ActiveCfg = Release|Any CPU + {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1426,6 +1454,8 @@ Global {ED00E6F4-2B5C-4F16-ADE4-45E4A73C17B8} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C} {337A85B5-4A7C-4883-8634-46E7E52A765F} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C} {95017C99-E960-44E5-83AD-BF21461DF06F} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} + {D7A0BB43-509A-469A-BBC7-1123634425C7} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} + {DE5C2854-6B2F-48BE-881A-00E18C567DEA} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164} diff --git a/Directory.Build.props b/Directory.Build.props index 138ae1714a3..318d468dac1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,6 +19,8 @@ 2.8.1 + 2.0.1 + 3.0.2 2.8.1 17.9.0 0.12.2 diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj new file mode 100644 index 00000000000..84e485f736a --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj @@ -0,0 +1,27 @@ + + + + + $(NetFrameworkTestVersion);net8.0 + Exe + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs new file mode 100644 index 00000000000..9629e49bc34 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.TestKit.Xunit3.Internals; +using Xunit; + +namespace Akka.TestKit.Xunit2.Tests.Internals; + +public static class AkkaEqualExceptionSpec +{ +#if NETFRAMEWORK + [Fact] + public static void Constructor_deserializes_message() + { + var originalException = new AkkaEqualException("Test message"); + + AkkaEqualException deserializedException; + using (var memoryStream = new System.IO.MemoryStream()) + { + var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); + formatter.Serialize(memoryStream, originalException); + memoryStream.Seek(0, System.IO.SeekOrigin.Begin); + deserializedException = (AkkaEqualException)formatter.Deserialize(memoryStream); + } + + Assert.Equal(originalException.Message, deserializedException.Message); + } +#endif +} diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs new file mode 100644 index 00000000000..098a336ee63 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Xunit; +using Xunit.Sdk; + +namespace Akka.TestKit.Xunit3.Tests; + +public class XunitAssertionsSpec +{ + private readonly XunitAssertions _assertions = new(); + + [Fact] + public void Assert_does_not_format_message_when_no_arguments_are_specified() + { + const string testMessage = "{Value} with different format placeholders {0}"; + + var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage)); + Assert.Contains(testMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage)); + Assert.Contains(testMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage)); + Assert.Contains(testMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage)); + Assert.Contains(testMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage)); + Assert.Contains(testMessage, exception.Message); + } + + [Fact] + public void Assert_formats_message_when_arguments_are_specified() + { + const string testMessage = "Meaning: {0}"; + const string expectedMessage = "Meaning: 42"; + + var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + } + + [Fact] + public void Assert_catches_format_exceptions() + { + const string testMessage = "Meaning: {0} {1}"; + const string expectedMessage = "Could not string.Format"; + + var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + + exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage, 42)); + Assert.Contains(expectedMessage, exception.Message); + } +} diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj b/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj new file mode 100644 index 00000000000..78fea4d9f72 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj @@ -0,0 +1,20 @@ + + + + $(NetStandardLibVersion);net8.0 + enable + enable + + + + + + + + + + + + true + + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs new file mode 100644 index 00000000000..aacebe25412 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Xunit; +using Xunit.v3; + +namespace Akka.TestKit.Xunit3.Attributes; + +/// +/// +/// This custom XUnit Fact attribute will skip unit tests if the environment variable +/// "XUNIT_SKIP_LOCAL_FACT" exists and is set to the string "true" +/// +/// +/// Note that the original property takes precedence over this attribute, +/// any unit tests with with its property +/// set will always be skipped, regardless of the environment variable content. +/// +/// +[XunitTestCaseDiscoverer(typeof(FactDiscoverer))] +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public class LocalFactAttribute: Attribute, IFactAttribute +{ + private const string EnvironmentVariableName = "XUNIT_SKIP_LOCAL_FACT"; + + private string? _skip; + + /// + public string? DisplayName { get; set; } + + /// + public bool Explicit { get; set; } + + /// + public Type[]? SkipExceptions { get; set; } + + /// + public Type? SkipType { get; set; } + + /// + public string? SkipUnless { get; set; } + + /// + public string? SkipWhen { get; set; } + + /// + public int Timeout { get; set; } + + /// + public string? Skip + { + get + { + var skipLocal = Environment.GetEnvironmentVariable(EnvironmentVariableName)? + .ToLowerInvariant(); + return skipLocal is "true" ? SkipLocal ?? "Local facts are being skipped" : _skip; + } + set => _skip = value; + } + + /// + /// The reason why this unit test is being skipped by the . + /// Note that the original property takes precedence over this message. + /// + public string? SkipLocal { get; set; } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs new file mode 100644 index 00000000000..e4185dafc46 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Xunit; +using Xunit.v3; + +namespace Akka.TestKit.Xunit3.Attributes +{ + /// + /// + /// This custom XUnit Fact attribute will skip unit tests if the environment variable + /// "XUNIT_SKIP_LOCAL_THEORY" exists and is set to the string "true" + /// + /// + /// Note that the original property takes precedence over this attribute, + /// any unit tests with with its property + /// set will always be skipped, regardless of the environment variable content. + /// + /// + [XunitTestCaseDiscoverer(typeof(TheoryDiscoverer))] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class LocalTheoryAttribute : LocalFactAttribute, ITheoryAttribute + { + /// + public bool DisableDiscoveryEnumeration { get; set; } + + /// + public bool SkipTestWithoutData { get; set; } + } +} diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs new file mode 100644 index 00000000000..9d0d1d7da78 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Xunit; +using Xunit.v3; + +namespace Akka.TestKit.Xunit3.Attributes +{ + /// + /// + /// This custom XUnit Fact attribute will skip unit tests if the run-time environment is not windows + /// + /// + /// Note that the original property takes precedence over this attribute, + /// any unit tests with with its property + /// set will always be skipped, regardless of the environment variable content. + /// + /// + public class WindowsFactAttribute : Attribute, IFactAttribute + { + private string? _skip; + + + /// + public string? DisplayName { get; set; } + + /// + public bool Explicit { get; set; } + + /// + public Type[]? SkipExceptions { get; set; } + + /// + public Type? SkipType { get; set; } + + /// + public string? SkipUnless { get; set; } + + /// + public string? SkipWhen { get; set; } + + /// + public int Timeout { get; set; } + + /// + public string? Skip + { + get + { + if (_skip != null) + return _skip; + + var platform = Environment.OSVersion.Platform; + var notWindows = platform is PlatformID.MacOSX or PlatformID.Unix or PlatformID.Xbox; + return notWindows ? SkipUnix ?? "Skipped under Unix platforms" : null; + } + set => _skip = value; + } + + /// + /// The reason why this unit test is being skipped by the . + /// Note that the original property takes precedence over this message. + /// + public string? SkipUnix { get; set; } + } +} + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs new file mode 100644 index 00000000000..1401599fb70 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System.Collections; + +namespace Akka.TestKit.Xunit3.Internals; + +internal static class AkkaAssertEqualityComparer +{ + public static readonly IEqualityComparer DefaultInnerComparer = new AkkaAssertEqualityComparerAdapter(new AkkaAssertEqualityComparer()); + public static readonly Type NullableType = typeof(Nullable<>); +} + +/// +/// Default implementation of used by the Akka's xUnit.net equality assertions. +/// Copy of xUnits code +/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparer.cs +/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. +/// +/// The type that is being compared. +public class AkkaAssertEqualityComparer : IEqualityComparer +{ + private readonly Func _innerComparerFactory; + private readonly bool _skipTypeCheck; + + /// + /// Initializes a new instance of the class. + /// + /// Set to true to skip type equality checks. + /// The inner comparer to be used when the compared objects are enumerable. + public AkkaAssertEqualityComparer(bool skipTypeCheck = false, IEqualityComparer? innerComparer = null) + { + _skipTypeCheck = skipTypeCheck; + + // Use a thunk to delay evaluation of DefaultInnerComparer + _innerComparerFactory = () => innerComparer ?? AkkaAssertEqualityComparer.DefaultInnerComparer; + } + + public bool Equals(T? x, T? y) + { + var type = typeof(T); + + // Null? + if(!type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(AkkaAssertEqualityComparer.NullableType))) + { + if(object.Equals(x, default(T))) + return object.Equals(y, default(T)); + + if(object.Equals(y, default(T))) + return false; + } + + switch (x) + { + case IEquatable equatable: + return equatable.Equals(y!); + case IComparable comparableGeneric: + return comparableGeneric.CompareTo(y!) == 0; + case IComparable comparable: + return comparable.CompareTo(y) == 0; + case IEnumerable enumerableX when y is IEnumerable enumerableY: + var enumeratorX = enumerableX.GetEnumerator(); + var enumeratorY = enumerableY.GetEnumerator(); + var equalityComparer = _innerComparerFactory(); + + try + { + while (true) + { + var hasNextX = enumeratorX.MoveNext(); + var hasNextY = enumeratorY.MoveNext(); + + if (!hasNextX || !hasNextY) + return (hasNextX == hasNextY); + + if (!equalityComparer.Equals(enumeratorX.Current, enumeratorY.Current)) + return false; + } + } + finally + { + (enumeratorX as IDisposable)?.Dispose(); + (enumeratorY as IDisposable)?.Dispose(); + } + } + + // Same type? + if (!_skipTypeCheck && x!.GetType() != y!.GetType()) + return false; + + // Last case, rely on Object.Equals + return object.Equals(x, y); + } + + public int GetHashCode(T obj) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs new file mode 100644 index 00000000000..a6bf7800b05 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System.Collections; + +namespace Akka.TestKit.Xunit3.Internals; + +/// +/// A class that wraps to create . +/// Copy of xUnits class: +/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparerAdapter.cs +/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. +/// +/// The type that is being compared. +internal class AkkaAssertEqualityComparerAdapter : IEqualityComparer +{ + private readonly IEqualityComparer _innerComparer; + + /// + /// Initializes a new instance of the class. + /// + /// The comparer that is being adapted. + public AkkaAssertEqualityComparerAdapter(IEqualityComparer innerComparer) + { + _innerComparer = innerComparer; + } + + /// + public new bool Equals(object? x, object? y) => _innerComparer.Equals((T?)x, (T?)y); + + /// + public int GetHashCode(object obj) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs new file mode 100644 index 00000000000..5e271fad0bb --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System.Runtime.Serialization; +using Xunit.Sdk; + +namespace Akka.TestKit.Xunit3.Internals; + +/// +/// TBD +/// +[Serializable] +public class AkkaEqualException : XunitException +{ + // Length of "Expected: " and "Actual: " + private static readonly string NewLineAndIndent = Environment.NewLine + new string(' ', 10); + + public static AkkaEqualException ForMismatchedValues( + object? expected, + object? actual, + string? format = null, + params object[] args) + { + // Strings normally come through ForMismatchedStrings, so we want to make sure any + // string value that comes through here isn't re-formatted/truncated. This is for + // two reasons: (a) to support Assert.Equal(string1, string2) to get a full + // printout of the raw string values, which is useful when debugging; and (b) to + // allow the assertion functions to pre-format the value themselves, perhaps with + // additional information (like DateTime/DateTimeOffset when providing the precision + // of the comparison). + var expectedText = expected as string ?? ArgumentFormatter.Format(expected); + var actualText = actual as string ?? ArgumentFormatter.Format(actual); + + return new AkkaEqualException( + "Assert.Equal() Failure: " + (format ?? "Values differ") + Environment.NewLine + + "Expected: " + expectedText.Replace(Environment.NewLine, NewLineAndIndent) + Environment.NewLine + + "Actual: " + actualText.Replace(Environment.NewLine, NewLineAndIndent), + args + ); + } + + /// + /// Initializes a new instance of the class. + /// + /// A template string that describes the error. + /// An optional object array that contains zero or more objects to format. + public AkkaEqualException(string format = "", params object[] args) + : base(BuildAssertionMessage(format, args)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected AkkaEqualException(SerializationInfo info, StreamingContext context) + : base(info.GetString("Message")) { } + + /// + /// Builds assertion message by applying specified arguments to the format string. + /// When no arguments are specified, format string is returned as-is. + /// + internal static string? BuildAssertionMessage(string format, object[] args) + { + if (string.IsNullOrEmpty(format)) + { + return null; + } + + if (args is not { Length: > 0 }) + { + return format; + } + + try + { + return string.Format(format, args); + } + catch (Exception) + { + return $"""[Could not string.Format("{format}", {string.Join(", ", args)})]"""; + } + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs new file mode 100644 index 00000000000..a7626b3d081 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.Actor; +using Akka.Event; +using Akka.Util; +using Xunit; + +namespace Akka.TestKit.Xunit3.Internals; + +/// +/// This class represents an actor that logs output from tests using an provider. +/// +public class TestOutputLogger : ReceiveActor +{ + private readonly ITestOutputHelper _output; + + /// + /// Initializes a new instance of the class. + /// + /// The provider used to write test output. + public TestOutputLogger(ITestOutputHelper output) + { + _output = output; + + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(e => + { + e.LoggingBus.Subscribe(Self, typeof (LogEvent)); + Sender.Tell(new LoggerInitialized()); + }); + } + + private void HandleLogEvent(LogEvent e) + { + try + { + _output.WriteLine(e.ToString()); + } + catch (FormatException ex) + { + if (e.Message is LogMessage msg) + { + var message = + $"Received a malformed formatted message. Log level: [{e.LogLevel()}], Template: [{msg.Format}], args: [{string.Join(",", msg.Unformatted())}]"; + if (e.Cause != null) + throw new AggregateException(message, ex, e.Cause); + throw new FormatException(message, ex); + } + + throw; + } + catch (InvalidOperationException ie) + { + StandardOutWriter.WriteLine($"Received InvalidOperationException: {ie} - probably because the test had completed executing."); + Context.Stop(Self); // shut ourselves down, can't do our job any longer + } + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs new file mode 100644 index 00000000000..8b60afea87c --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs @@ -0,0 +1,10 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Akka.TestKit.Xunit3.Tests")] \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs new file mode 100644 index 00000000000..89d74046259 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs @@ -0,0 +1,220 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.Actor; +using Akka.Actor.Setup; +using Akka.Configuration; +using Akka.Event; +using Akka.TestKit.Xunit3.Internals; +using Xunit; + +namespace Akka.TestKit.Xunit3; + +/// +/// This class represents an Akka.NET TestKit that uses xUnit +/// as its testing framework. +/// +public class TestKit : TestKitBase, IDisposable +{ + private class PrefixedOutput : ITestOutputHelper + { + private static readonly string Newline = Environment.NewLine; + private readonly ITestOutputHelper _output; + private readonly string _prefix; + private bool _newLine; + + public PrefixedOutput(ITestOutputHelper output, string prefix) + { + _output = output; + _prefix = prefix; + } + + public void Write(string message) + { + if(_newLine) + message = _prefix + Newline; + + var parts = message.Split([Newline], StringSplitOptions.None); + _output.Write(string.Join($"{Newline}{_prefix}", parts)); + _newLine = message.EndsWith(Newline); + } + + public void Write(string format, params object[] args) + { + if(_newLine) + format = _prefix + format; + var parts = format.Split([Newline], StringSplitOptions.None); + format = string.Join($"{Newline}{_prefix}", parts); + + _output.Write(format, args); + _newLine = format.EndsWith(Newline); + } + + public void WriteLine(string message) + { + if (_newLine) + message = _prefix + message; + _newLine = true; + _output.WriteLine(message); + } + + public void WriteLine(string format, params object[] args) + { + if (_newLine) + format = _prefix + format; + _newLine = true; + _output.WriteLine(format, args); + } + + public string Output => _output.Output; + } + + /// + /// The provider used to write test output. + /// + protected readonly ITestOutputHelper? Output; + + private bool _disposed; + private bool _disposing; + + /// + /// + /// Initializes a new instance of the class. + /// + /// + /// If no is passed in, a new system with + /// will be created. + /// + /// + /// The actor system to use for testing. The default value is . + /// The provider used to write test output. The default value is . + public TestKit(ActorSystem? system = null, ITestOutputHelper? output = null) + : base(Assertions, system) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use for configuring the ActorSystem. + /// The name of the system. The default name is "test". + /// The provider used to write test output. The default value is . + public TestKit(ActorSystemSetup config, string? actorSystemName = null, ITestOutputHelper? output = null) + : base(Assertions, config, actorSystemName) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration to use for the system. + /// The name of the system. The default name is "test". + /// The provider used to write test output. The default value is . + public TestKit(Config config, string? actorSystemName = null, ITestOutputHelper? output = null) + : base(Assertions, config, actorSystemName) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration to use for the system. + /// The provider used to write test output. The default value is . + public TestKit(string config, ITestOutputHelper? output = null) + : base(Assertions, ConfigurationFactory.ParseString(config)) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// A configuration that has just the default log settings enabled. The default settings can be found in + /// Akka.TestKit.Internal.Reference.conf. + /// + public new static Config DefaultConfig => TestKitBase.DefaultConfig; + + /// + /// A configuration that has all log settings enabled + /// + public new static Config FullDebugConfig => TestKitBase.FullDebugConfig; + + /// + /// Commonly used assertions used throughout the testkit. + /// + protected static XunitAssertions Assertions { get; } = new(); + + /// + /// This method is called when a test ends. + /// + protected virtual void AfterAll() + { + } + + /// + /// Initializes a new used to log messages. + /// + /// The actor system used to attach the logger + protected void InitializeLogger(ActorSystem system) + { + if (Output == null) + return; + + var extSystem = (ExtendedActorSystem)system; + var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(Output)), "log-test"); + logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) + .ConfigureAwait(false).GetAwaiter().GetResult(); + } + + protected void InitializeLogger(ActorSystem system, string prefix) + { + if (Output == null) + return; + + var extSystem = (ExtendedActorSystem)system; + var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger( + string.IsNullOrEmpty(prefix) ? Output : new PrefixedOutput(Output, prefix))), "log-test"); + logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) + .ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// + /// if set to true the method has been called directly or indirectly by a user's code. + /// Managed and unmanaged resources will be disposed.
if set to false the method + /// has been called by the runtime from inside the finalizer and only unmanaged resources can + /// be disposed. + /// + protected virtual void Dispose(bool disposing) + { + if (_disposing || _disposed) + return; + + _disposing = true; + try + { + AfterAll(); + } + finally + { + Shutdown(); + _disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs new file mode 100644 index 00000000000..7c34a785010 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.TestKit.Xunit3.Internals; +using Xunit; + +namespace Akka.TestKit.Xunit3; + +/// +/// This class contains several common assert patterns used throughout this testkit. +/// +public class XunitAssertions : ITestKitAssertions +{ + /// + /// Fails an assertion without checking any conditions. + /// + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void Fail(string format = "", params object[] args) + { + Assert.Fail(AkkaEqualException.BuildAssertionMessage(format, args)); + } + + /// + /// Verifies that a specified is true. + /// + /// The condition that is being verified. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void AssertTrue(bool condition, string format = "", params object[] args) + { + Assert.True(condition, AkkaEqualException.BuildAssertionMessage(format, args)); + } + + /// + /// Verifies that a specified is false. + /// + /// The condition that is being verified. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void AssertFalse(bool condition, string format = "", params object[] args) + { + Assert.False(condition, AkkaEqualException.BuildAssertionMessage(format, args)); + } + + /// + /// Verifies that the two specified values ( and + /// are equal using the built-in comparison function . + /// + /// The type that is being compared. + /// The expected value of the object + /// The actual value of the object + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + /// + /// This exception is thrown when the two specified values are not equal. + /// + public void AssertEqual(T expected, T actual, string format = "", params object[] args) + { + var comparer = new AkkaAssertEqualityComparer(); + if(!comparer.Equals(expected, actual)) + throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); + } + + /// + /// Verifies that the two specified values ( and + /// are equal using a specified comparison function . + /// + /// The type that is being compared. + /// The expected value of the object + /// The actual value of the object + /// The function used to compare the two specified values. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + /// + /// This exception is thrown when the two specified values are not equal. + /// + public void AssertEqual(T expected, T actual, Func comparer, string format = "", params object[] args) + { + if(!comparer(expected, actual)) + throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); + } +} \ No newline at end of file From eb1d59646ee5e94238456b1c92a0e1c05289c898 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 1 May 2025 00:34:46 +0700 Subject: [PATCH 2/2] Refactor Akka.TestKit.Xunit3 to Akka.TestKit.Xunit --- Akka.sln | 43 +-- .../Akka.TestKit.Xunit.Tests.csproj} | 5 +- .../Internals/AkkaEqualExceptionSpec.cs | 4 +- .../XunitAssertionsSpec.cs | 3 +- .../Akka.TestKit.Xunit.csproj | 6 +- .../Attributes/LocalFactAttribute.cs | 3 +- .../Attributes/LocalTheoryAttribute.cs | 4 +- .../Attributes/WindowsFactAttribute.cs | 4 +- .../Internals/AkkaAssertEqualityComparer.cs | 133 ++++---- .../AkkaAssertEqualityComparerAdapter.cs | 46 ++- .../Internals/AkkaEqualException.cs | 131 ++++---- .../Akka.TestKit.Xunit/Internals/Loggers.cs | 83 ++--- .../Properties/AssemblyInfo.cs | 33 -- .../Properties/FriendsOf.cs | 0 .../testkits/Akka.TestKit.Xunit/TestKit.cs | 314 ++++++++++-------- .../Akka.TestKit.Xunit/XunitAssertions.cs | 137 ++++---- .../Akka.TestKit.Xunit3.csproj | 20 -- .../Internals/AkkaAssertEqualityComparer.cs | 100 ------ .../AkkaAssertEqualityComparerAdapter.cs | 37 --- .../Internals/AkkaEqualException.cs | 87 ----- .../Akka.TestKit.Xunit3/Internals/Loggers.cs | 66 ---- .../testkits/Akka.TestKit.Xunit3/TestKit.cs | 220 ------------ .../Akka.TestKit.Xunit3/XunitAssertions.cs | 87 ----- 23 files changed, 482 insertions(+), 1084 deletions(-) rename src/contrib/testkits/{Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj => Akka.TestKit.Xunit.Tests/Akka.TestKit.Xunit.Tests.csproj} (90%) rename src/contrib/testkits/{Akka.TestKit.Xunit3.Tests => Akka.TestKit.Xunit.Tests}/Internals/AkkaEqualExceptionSpec.cs (93%) rename src/contrib/testkits/{Akka.TestKit.Xunit3.Tests => Akka.TestKit.Xunit.Tests}/XunitAssertionsSpec.cs (98%) rename src/contrib/testkits/{Akka.TestKit.Xunit3 => Akka.TestKit.Xunit}/Attributes/LocalFactAttribute.cs (97%) rename src/contrib/testkits/{Akka.TestKit.Xunit3 => Akka.TestKit.Xunit}/Attributes/LocalTheoryAttribute.cs (96%) rename src/contrib/testkits/{Akka.TestKit.Xunit3 => Akka.TestKit.Xunit}/Attributes/WindowsFactAttribute.cs (97%) delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit/Properties/AssemblyInfo.cs rename src/contrib/testkits/{Akka.TestKit.Xunit3 => Akka.TestKit.Xunit}/Properties/FriendsOf.cs (100%) delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs delete mode 100644 src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs diff --git a/Akka.sln b/Akka.sln index 70d879b67e7..4838b6b2d75 100644 --- a/Akka.sln +++ b/Akka.sln @@ -277,9 +277,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClusterToolsExample.Node", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit2.Tests", "src\contrib\testkits\Akka.TestKit.Xunit2.Tests\Akka.TestKit.Xunit2.Tests.csproj", "{95017C99-E960-44E5-83AD-BF21461DF06F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit3", "src\contrib\testkits\Akka.TestKit.Xunit3\Akka.TestKit.Xunit3.csproj", "{D7A0BB43-509A-469A-BBC7-1123634425C7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit3.Tests", "src\contrib\testkits\Akka.TestKit.Xunit3.Tests\Akka.TestKit.Xunit3.Tests.csproj", "{DE5C2854-6B2F-48BE-881A-00E18C567DEA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit.Tests", "src\contrib\testkits\Akka.TestKit.Xunit.Tests\Akka.TestKit.Xunit.Tests.csproj", "{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1308,30 +1306,18 @@ Global {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x64.Build.0 = Release|Any CPU {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.ActiveCfg = Release|Any CPU {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.Build.0 = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x64.ActiveCfg = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x64.Build.0 = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x86.ActiveCfg = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Debug|x86.Build.0 = Debug|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|Any CPU.Build.0 = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x64.ActiveCfg = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x64.Build.0 = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x86.ActiveCfg = Release|Any CPU - {D7A0BB43-509A-469A-BBC7-1123634425C7}.Release|x86.Build.0 = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x64.Build.0 = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x86.ActiveCfg = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Debug|x86.Build.0 = Debug|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|Any CPU.Build.0 = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x64.ActiveCfg = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x64.Build.0 = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x86.ActiveCfg = Release|Any CPU - {DE5C2854-6B2F-48BE-881A-00E18C567DEA}.Release|x86.Build.0 = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|x64.ActiveCfg = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|x64.Build.0 = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|x86.ActiveCfg = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Debug|x86.Build.0 = Debug|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|Any CPU.Build.0 = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x64.ActiveCfg = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x64.Build.0 = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.ActiveCfg = Release|Any CPU + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1454,8 +1440,7 @@ Global {ED00E6F4-2B5C-4F16-ADE4-45E4A73C17B8} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C} {337A85B5-4A7C-4883-8634-46E7E52A765F} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C} {95017C99-E960-44E5-83AD-BF21461DF06F} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} - {D7A0BB43-509A-469A-BBC7-1123634425C7} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} - {DE5C2854-6B2F-48BE-881A-00E18C567DEA} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} + {F80F41E6-E5C7-4C92-B1CF-42539ECFBE68} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164} diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/Akka.TestKit.Xunit.Tests.csproj similarity index 90% rename from src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj rename to src/contrib/testkits/Akka.TestKit.Xunit.Tests/Akka.TestKit.Xunit.Tests.csproj index 84e485f736a..18e1bcaf826 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Akka.TestKit.Xunit3.Tests.csproj +++ b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/Akka.TestKit.Xunit.Tests.csproj @@ -21,7 +21,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/Internals/AkkaEqualExceptionSpec.cs similarity index 93% rename from src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs rename to src/contrib/testkits/Akka.TestKit.Xunit.Tests/Internals/AkkaEqualExceptionSpec.cs index 9629e49bc34..c56b0fd665a 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/Internals/AkkaEqualExceptionSpec.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/Internals/AkkaEqualExceptionSpec.cs @@ -5,10 +5,10 @@ // //----------------------------------------------------------------------- -using Akka.TestKit.Xunit3.Internals; +using Akka.TestKit.Xunit.Internals; using Xunit; -namespace Akka.TestKit.Xunit2.Tests.Internals; +namespace Akka.TestKit.Xunit.Tests.Internals; public static class AkkaEqualExceptionSpec { diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/XunitAssertionsSpec.cs similarity index 98% rename from src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs rename to src/contrib/testkits/Akka.TestKit.Xunit.Tests/XunitAssertionsSpec.cs index 098a336ee63..7bea4f94d57 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3.Tests/XunitAssertionsSpec.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit.Tests/XunitAssertionsSpec.cs @@ -5,10 +5,11 @@ // //----------------------------------------------------------------------- +using Akka.TestKit.Xunit; using Xunit; using Xunit.Sdk; -namespace Akka.TestKit.Xunit3.Tests; +namespace Akka.TestKit.Xunit.Tests; public class XunitAssertionsSpec { diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.csproj b/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.csproj index 96315b826d3..c51c54b72ed 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.csproj +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.csproj @@ -2,14 +2,16 @@ TestKit for writing tests for Akka.NET using xUnit. - $(NetStandardLibVersion);$(NetLibVersion) + $(NetStandardLibVersion);net8.0 + enable $(AkkaPackageTags);testkit;xunit true - + + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalFactAttribute.cs similarity index 97% rename from src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs rename to src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalFactAttribute.cs index aacebe25412..97ef7802810 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalFactAttribute.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalFactAttribute.cs @@ -5,10 +5,11 @@ // //----------------------------------------------------------------------- +using System; using Xunit; using Xunit.v3; -namespace Akka.TestKit.Xunit3.Attributes; +namespace Akka.TestKit.Xunit.Attributes; /// /// diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalTheoryAttribute.cs similarity index 96% rename from src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs rename to src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalTheoryAttribute.cs index e4185dafc46..1e2a4552aeb 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/LocalTheoryAttribute.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/LocalTheoryAttribute.cs @@ -5,10 +5,10 @@ // //----------------------------------------------------------------------- -using Xunit; +using System; using Xunit.v3; -namespace Akka.TestKit.Xunit3.Attributes +namespace Akka.TestKit.Xunit.Attributes { /// /// diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/WindowsFactAttribute.cs similarity index 97% rename from src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs rename to src/contrib/testkits/Akka.TestKit.Xunit/Attributes/WindowsFactAttribute.cs index 9d0d1d7da78..e7cfb8dcc38 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Attributes/WindowsFactAttribute.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Attributes/WindowsFactAttribute.cs @@ -5,10 +5,10 @@ // //----------------------------------------------------------------------- -using Xunit; +using System; using Xunit.v3; -namespace Akka.TestKit.Xunit3.Attributes +namespace Akka.TestKit.Xunit.Attributes { /// /// diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs index c288a6be60e..c731b580e97 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs @@ -8,90 +8,95 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Reflection; -namespace Akka.TestKit.Xunit.Internals +namespace Akka.TestKit.Xunit.Internals; + +internal static class AkkaAssertEqualityComparer { + public static readonly IEqualityComparer DefaultInnerComparer = new AkkaAssertEqualityComparerAdapter(new AkkaAssertEqualityComparer()); + public static readonly Type NullableType = typeof(Nullable<>); +} + +/// +/// Default implementation of used by the Akka's xUnit.net equality assertions. +/// Copy of xUnits code +/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparer.cs +/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. +/// +/// The type that is being compared. +public class AkkaAssertEqualityComparer : IEqualityComparer +{ + private readonly Func _innerComparerFactory; + private readonly bool _skipTypeCheck; + /// - /// Default implementation of IEqualityComparer{T} used by the Akka's xUnit.net equality assertions. - /// Copy of xUnits code - /// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparer.cs - /// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. + /// Initializes a new instance of the class. /// - /// The type that is being compared. - public class AkkaAssertEqualityComparer : IEqualityComparer + /// Set to true to skip type equality checks. + /// The inner comparer to be used when the compared objects are enumerable. + public AkkaAssertEqualityComparer(bool skipTypeCheck = false, IEqualityComparer? innerComparer = null) + { + _skipTypeCheck = skipTypeCheck; + + // Use a thunk to delay evaluation of DefaultInnerComparer + _innerComparerFactory = () => innerComparer ?? AkkaAssertEqualityComparer.DefaultInnerComparer; + } + + public bool Equals(T? x, T? y) { - private static readonly IEqualityComparer DefaultInnerComparer = new AkkaAssertEqualityComparerAdapter(new AkkaAssertEqualityComparer()); - private static readonly Type NullableType = typeof(Nullable<>); - - private readonly Func _innerComparerFactory; - private readonly bool _skipTypeCheck; - - /// - /// Initializes a new instance of the class. - /// - /// Set to true to skip type equality checks. - /// The inner comparer to be used when the compared objects are enumerable. - public AkkaAssertEqualityComparer(bool skipTypeCheck = false, IEqualityComparer innerComparer = null) + var type = typeof(T); + + // Null? + if(!type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(AkkaAssertEqualityComparer.NullableType))) { - _skipTypeCheck = skipTypeCheck; + if(object.Equals(x, default(T))) + return object.Equals(y, default(T)); - // Use a thunk to delay evaluation of DefaultInnerComparer - _innerComparerFactory = () => innerComparer ?? DefaultInnerComparer; + if(object.Equals(y, default(T))) + return false; } - - public bool Equals(T x, T y) + switch (x) { - var type = typeof(T); - - // Null? - if(!type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(NullableType))) - { - if(object.Equals(x, default(T))) - return object.Equals(y, default(T)); - - if(object.Equals(y, default(T))) - return false; - } - - switch (x) - { - case IEquatable equatable: - return equatable.Equals(y); - case IComparable comparableGeneric: - return comparableGeneric.CompareTo(y) == 0; - case IComparable comparable: - return comparable.CompareTo(y) == 0; - case IEnumerable enumerableX - when y is IEnumerable enumerableY: - - var enumeratorX = enumerableX.GetEnumerator(); - var enumeratorY = enumerableY.GetEnumerator(); - var equalityComparer = _innerComparerFactory(); - + case IEquatable equatable: + return equatable.Equals(y!); + case IComparable comparableGeneric: + return comparableGeneric.CompareTo(y!) == 0; + case IComparable comparable: + return comparable.CompareTo(y) == 0; + case IEnumerable enumerableX when y is IEnumerable enumerableY: + var enumeratorX = enumerableX.GetEnumerator(); + var enumeratorY = enumerableY.GetEnumerator(); + var equalityComparer = _innerComparerFactory(); + + try + { while (true) { var hasNextX = enumeratorX.MoveNext(); var hasNextY = enumeratorY.MoveNext(); if (!hasNextX || !hasNextY) - return hasNextX == hasNextY; + return (hasNextX == hasNextY); if (!equalityComparer.Equals(enumeratorX.Current, enumeratorY.Current)) return false; } - } - - // Same type? - if (!_skipTypeCheck && x.GetType() != y.GetType()) - return false; - - // Last case, rely on Object.Equals - return object.Equals(x, y); + } + finally + { + (enumeratorX as IDisposable)?.Dispose(); + (enumeratorY as IDisposable)?.Dispose(); + } } - - public int GetHashCode(T obj) => throw new NotImplementedException(); + // Same type? + if (!_skipTypeCheck && x!.GetType() != y!.GetType()) + return false; + + // Last case, rely on Object.Equals + return object.Equals(x, y); } -} + + public int GetHashCode(T obj) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparerAdapter.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparerAdapter.cs index 6a2e7e9c736..c57524569d6 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparerAdapter.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparerAdapter.cs @@ -9,33 +9,31 @@ using System.Collections; using System.Collections.Generic; -namespace Akka.TestKit.Xunit.Internals +namespace Akka.TestKit.Xunit.Internals; + +/// +/// A class that wraps to create . +/// Copy of xUnits class: +/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparerAdapter.cs +/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. +/// +/// The type that is being compared. +internal class AkkaAssertEqualityComparerAdapter : IEqualityComparer { + private readonly IEqualityComparer _innerComparer; + /// - /// A class that wraps to create . - /// Copy of xUnits class: - /// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparerAdapter.cs - /// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. + /// Initializes a new instance of the class. /// - /// The type that is being compared. - internal class AkkaAssertEqualityComparerAdapter : IEqualityComparer + /// The comparer that is being adapted. + public AkkaAssertEqualityComparerAdapter(IEqualityComparer innerComparer) { - private readonly IEqualityComparer _innerComparer; - - /// - /// Initializes a new instance of the class. - /// - /// The comparer that is being adapted. - public AkkaAssertEqualityComparerAdapter(IEqualityComparer innerComparer) - { - _innerComparer = innerComparer; - } - - /// - public new bool Equals(object x, object y) => _innerComparer.Equals((T)x, (T)y); - - /// - public int GetHashCode(object obj) => throw new NotImplementedException(); + _innerComparer = innerComparer; } -} + /// + public new bool Equals(object? x, object? y) => _innerComparer.Equals((T?)x, (T?)y); + + /// + public int GetHashCode(object obj) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaEqualException.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaEqualException.cs index ba7372e7e7a..021c75cdc66 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaEqualException.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaEqualException.cs @@ -9,85 +9,80 @@ using System.Runtime.Serialization; using Xunit.Sdk; -namespace Akka.TestKit.Xunit.Internals +namespace Akka.TestKit.Xunit.Internals; + +/// +/// TBD +/// +[Serializable] +public class AkkaEqualException : XunitException { - /// - /// TBD - /// - public class AkkaEqualException : XunitException - { - private static readonly string NewLineAndIndent = Environment.NewLine + new string(' ', 10); // Length of "Expected: " and "Actual: " + // Length of "Expected: " and "Actual: " + private static readonly string NewLineAndIndent = Environment.NewLine + new string(' ', 10); - private readonly string _format; - private readonly object[] _args; + public static AkkaEqualException ForMismatchedValues( + object? expected, + object? actual, + string? format = null, + params object[] args) + { + // Strings normally come through ForMismatchedStrings, so we want to make sure any + // string value that comes through here isn't re-formatted/truncated. This is for + // two reasons: (a) to support Assert.Equal(string1, string2) to get a full + // printout of the raw string values, which is useful when debugging; and (b) to + // allow the assertion functions to pre-format the value themselves, perhaps with + // additional information (like DateTime/DateTimeOffset when providing the precision + // of the comparison). + var expectedText = expected as string ?? ArgumentFormatter.Format(expected); + var actualText = actual as string ?? ArgumentFormatter.Format(actual); - public static AkkaEqualException ForMismatchedValues( - object expected, - object actual, - string format = null, - params object[] args) - { - // Strings normally come through ForMismatchedStrings, so we want to make sure any - // string value that comes through here isn't re-formatted/truncated. This is for - // two reasons: (a) to support Assert.Equal(string1, string2) to get a full - // printout of the raw string values, which is useful when debugging; and (b) to - // allow the assertion functions to pre-format the value themselves, perhaps with - // additional information (like DateTime/DateTimeOffset when providing the precision - // of the comparison). - var expectedText = expected as string ?? ArgumentFormatter.Format(expected); - var actualText = actual as string ?? ArgumentFormatter.Format(actual); + return new AkkaEqualException( + "Assert.Equal() Failure: " + (format ?? "Values differ") + Environment.NewLine + + "Expected: " + expectedText.Replace(Environment.NewLine, NewLineAndIndent) + Environment.NewLine + + "Actual: " + actualText.Replace(Environment.NewLine, NewLineAndIndent), + args + ); + } - return new AkkaEqualException( - @$"Assert.Equal() Failure: {format ?? "Values differ"}{Environment.NewLine} -Expected: {expectedText.Replace(Environment.NewLine, NewLineAndIndent)}{Environment.NewLine} -Actual: {actualText.Replace(Environment.NewLine, NewLineAndIndent)}", - args - ); + /// + /// Initializes a new instance of the class. + /// + /// A template string that describes the error. + /// An optional object array that contains zero or more objects to format. + public AkkaEqualException(string format = "", params object[] args) + : base(BuildAssertionMessage(format, args)) { } - } - - /// - /// Initializes a new instance of the class. - /// - /// A template string that describes the error. - /// An optional object array that contains zero or more objects to format. - public AkkaEqualException(string format = "", params object[] args): base(null) + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected AkkaEqualException(SerializationInfo info, StreamingContext context) + : base(info.GetString("Message")) { } + + /// + /// Builds assertion message by applying specified arguments to the format string. + /// When no arguments are specified, format string is returned as-is. + /// + internal static string? BuildAssertionMessage(string format, object[] args) + { + if (string.IsNullOrEmpty(format)) { - _format = format; - _args = args; + return null; } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected AkkaEqualException(SerializationInfo info, StreamingContext context): base(null) + if (args is not { Length: > 0 }) { + return format; } - /// - /// The message that describes the error. - /// - public override string Message + try { - get - { - if(string.IsNullOrEmpty(_format)) - return base.Message; - - string message; - try - { - message = string.Format(_format, _args); - } - catch(Exception) - { - message = $@"[Could not string.Format(""{_format}"", {string.Join(", ", _args)})]"; - } - - return base.Message is not null ? $"{base.Message} {message}" : message; - } + return string.Format(format, args); + } + catch (Exception) + { + return $"""[Could not string.Format("{format}", {string.Join(", ", args)})]"""; } } -} +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs index 4ae08dbabe5..5d4130dff38 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs @@ -8,53 +8,60 @@ using System; using Akka.Actor; using Akka.Event; -using Xunit.Abstractions; +using Akka.Util; +using Xunit; -namespace Akka.TestKit.Xunit.Internals +namespace Akka.TestKit.Xunit.Internals; + +/// +/// This class represents an actor that logs output from tests using an provider. +/// +public class TestOutputLogger : ReceiveActor { + private readonly ITestOutputHelper _output; + /// - /// This class represents an actor that logs output from tests using an ITestOutputHelper provider. + /// Initializes a new instance of the class. /// - public class TestOutputLogger : ReceiveActor + /// The provider used to write test output. + public TestOutputLogger(ITestOutputHelper output) { - private readonly ITestOutputHelper _output; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to write test output. - public TestOutputLogger(ITestOutputHelper output) + _output = output; + + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(HandleLogEvent); + Receive(e => { - _output = output; - - Receive(Write); - Receive(Write); - Receive(Write); - Receive(Write); - Receive(e => - { - e.LoggingBus.Subscribe(Self, typeof (LogEvent)); - }); - } + e.LoggingBus.Subscribe(Self, typeof (LogEvent)); + Sender.Tell(new LoggerInitialized()); + }); + } - private void Write(LogEvent e) + private void HandleLogEvent(LogEvent e) + { + try { - try - { - _output.WriteLine(e.ToString()); - } - catch (FormatException ex) + _output.WriteLine(e.ToString()); + } + catch (FormatException ex) + { + if (e.Message is LogMessage msg) { - if (e.Message is LogMessage msg) - { - var message = - $"Received a malformed formatted message. Log level: [{e.LogLevel()}], Template: [{msg.Format}], args: [{string.Join(",", msg.Unformatted())}]"; - if(e.Cause != null) - throw new AggregateException(message, ex, e.Cause); - throw new FormatException(message, ex); - } - throw; + var message = + $"Received a malformed formatted message. Log level: [{e.LogLevel()}], Template: [{msg.Format}], args: [{string.Join(",", msg.Unformatted())}]"; + if (e.Cause != null) + throw new AggregateException(message, ex, e.Cause); + throw new FormatException(message, ex); } + + throw; + } + catch (InvalidOperationException ie) + { + StandardOutWriter.WriteLine($"Received InvalidOperationException: {ie} - probably because the test had completed executing."); + Context.Stop(Self); // shut ourselves down, can't do our job any longer } } -} +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Properties/AssemblyInfo.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Properties/AssemblyInfo.cs deleted file mode 100644 index aeef4172d6c..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2b7e5487-4c34-4ef5-994f-b41766d175bd")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Properties/FriendsOf.cs similarity index 100% rename from src/contrib/testkits/Akka.TestKit.Xunit3/Properties/FriendsOf.cs rename to src/contrib/testkits/Akka.TestKit.Xunit/Properties/FriendsOf.cs diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs b/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs index cb52b2215f2..26272f21045 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs @@ -11,159 +11,211 @@ using Akka.Configuration; using Akka.Event; using Akka.TestKit.Xunit.Internals; -using Xunit.Abstractions; +using Xunit; -namespace Akka.TestKit.Xunit +namespace Akka.TestKit.Xunit; + +/// +/// This class represents an Akka.NET TestKit that uses xUnit +/// as its testing framework. +/// +public class TestKit : TestKitBase, IDisposable { - /// - /// This class represents an Akka.NET TestKit that uses xUnit - /// as its testing framework. - /// - public class TestKit : TestKitBase , IDisposable + private class PrefixedOutput : ITestOutputHelper { - private bool _isDisposed; //Automatically initialized to false; - - /// - /// The provider used to write test output. - /// - protected readonly ITestOutputHelper Output; - - /// - /// - /// Initializes a new instance of the class. - /// - /// - /// If no is passed in, a new system with - /// will be created. - /// - /// - /// The actor system to use for testing. The default value is . - /// The provider used to write test output. The default value is . - public TestKit(ActorSystem system = null, ITestOutputHelper output = null) - : base(Assertions, system) - { - Output = output; - InitializeLogger(Sys); - } + private static readonly string Newline = Environment.NewLine; + private readonly ITestOutputHelper _output; + private readonly string _prefix; + private bool _newLine; - /// - /// Initializes a new instance of the class. - /// - /// The to use for configuring the ActorSystem. - /// The name of the system. The default name is "test". - /// The provider used to write test output. The default value is . - public TestKit(ActorSystemSetup config, string actorSystemName = null, ITestOutputHelper output = null) - : base(Assertions, config, actorSystemName) + public PrefixedOutput(ITestOutputHelper output, string prefix) { - Output = output; - InitializeLogger(Sys); + _output = output; + _prefix = prefix; } - /// - /// Initializes a new instance of the class. - /// - /// The configuration to use for the system. - /// The name of the system. The default name is "test". - /// The provider used to write test output. The default value is . - public TestKit(Config config, string actorSystemName = null, ITestOutputHelper output = null) - : base(Assertions, config, actorSystemName) + public void Write(string message) { - Output = output; - InitializeLogger(Sys); + if(_newLine) + message = _prefix + Newline; + + var parts = message.Split([Newline], StringSplitOptions.None); + _output.Write(string.Join($"{Newline}{_prefix}", parts)); + _newLine = message.EndsWith(Newline); } - /// - /// Initializes a new instance of the class. - /// - /// The configuration to use for the system. - /// The provider used to write test output. The default value is . - public TestKit(string config, ITestOutputHelper output = null) - : base(Assertions, ConfigurationFactory.ParseString(config)) + public void Write(string format, params object[] args) { - Output = output; - InitializeLogger(Sys); + if(_newLine) + format = _prefix + format; + var parts = format.Split([Newline], StringSplitOptions.None); + format = string.Join($"{Newline}{_prefix}", parts); + + _output.Write(format, args); + _newLine = format.EndsWith(Newline); } - /// - /// A configuration that has just the default log settings enabled. The default settings can be found in - /// Akka.TestKit.Internal.Reference.conf. - /// - public new static Config DefaultConfig => TestKitBase.DefaultConfig; - - /// - /// A configuration that has all log settings enabled - /// - public new static Config FullDebugConfig => TestKitBase.FullDebugConfig; - - /// - /// Commonly used assertions used throughout the testkit. - /// - protected static XunitAssertions Assertions { get; } = new(); - - /// - /// This method is called when a test ends. - /// - /// - /// If you override this, then make sure you either call base.AfterTest() or - /// TestKitBase.Shutdown - /// to shut down the system. Otherwise a memory leak will occur. - /// - /// - protected virtual void AfterAll() + public void WriteLine(string message) { - Shutdown(); + if (_newLine) + message = _prefix + message; + _newLine = true; + _output.WriteLine(message); } - /// - /// Initializes a new used to log messages. - /// - /// The actor system used to attach the logger - protected void InitializeLogger(ActorSystem system) + public void WriteLine(string format, params object[] args) { - if (Output != null) - { - var extSystem = (ExtendedActorSystem)system; - var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(Output)), "log-test"); - logger.Tell(new InitializeLogger(system.EventStream)); - } + if (_newLine) + format = _prefix + format; + _newLine = true; + _output.WriteLine(format, args); } + public string Output => _output.Output; + } + + /// + /// The provider used to write test output. + /// + protected readonly ITestOutputHelper? Output; + + private bool _disposed; + private bool _disposing; - public void Dispose() + /// + /// + /// Initializes a new instance of the class. + /// + /// + /// If no is passed in, a new system with + /// will be created. + /// + /// + /// The actor system to use for testing. The default value is . + /// The provider used to write test output. The default value is . + public TestKit(ActorSystem? system = null, ITestOutputHelper? output = null) + : base(Assertions, system) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use for configuring the ActorSystem. + /// The name of the system. The default name is "test". + /// The provider used to write test output. The default value is . + public TestKit(ActorSystemSetup config, string? actorSystemName = null, ITestOutputHelper? output = null) + : base(Assertions, config, actorSystemName) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration to use for the system. + /// The name of the system. The default name is "test". + /// The provider used to write test output. The default value is . + public TestKit(Config config, string? actorSystemName = null, ITestOutputHelper? output = null) + : base(Assertions, config, actorSystemName) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration to use for the system. + /// The provider used to write test output. The default value is . + public TestKit(string config, ITestOutputHelper? output = null) + : base(Assertions, ConfigurationFactory.ParseString(config)) + { + Output = output; + InitializeLogger(Sys); + } + + /// + /// A configuration that has just the default log settings enabled. The default settings can be found in + /// Akka.TestKit.Internal.Reference.conf. + /// + public new static Config DefaultConfig => TestKitBase.DefaultConfig; + + /// + /// A configuration that has all log settings enabled + /// + public new static Config FullDebugConfig => TestKitBase.FullDebugConfig; + + /// + /// Commonly used assertions used throughout the testkit. + /// + protected static XunitAssertions Assertions { get; } = new(); + + /// + /// This method is called when a test ends. + /// + protected virtual void AfterAll() + { + } + + /// + /// Initializes a new used to log messages. + /// + /// The actor system used to attach the logger + protected void InitializeLogger(ActorSystem system) + { + if (Output == null) + return; + + var extSystem = (ExtendedActorSystem)system; + var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(Output)), "log-test"); + logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) + .ConfigureAwait(false).GetAwaiter().GetResult(); + } + + protected void InitializeLogger(ActorSystem system, string prefix) + { + if (Output == null) + return; + + var extSystem = (ExtendedActorSystem)system; + var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger( + string.IsNullOrEmpty(prefix) ? Output : new PrefixedOutput(Output, prefix))), "log-test"); + logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) + .ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// + /// if set to true the method has been called directly or indirectly by a user's code. + /// Managed and unmanaged resources will be disposed.
if set to false the method + /// has been called by the runtime from inside the finalizer and only unmanaged resources can + /// be disposed. + /// + protected virtual void Dispose(bool disposing) + { + if (_disposing || _disposed) + return; + + _disposing = true; + try { - Dispose(true); - //Take this object off the finalization queue and prevent finalization code for this object - //from executing a second time. - GC.SuppressFinalize(this); + AfterAll(); } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// - /// if set to true the method has been called directly or indirectly by a user's code. - /// Managed and unmanaged resources will be disposed.
if set to false the method - /// has been called by the runtime from inside the finalizer and only unmanaged resources can - /// be disposed. - /// - protected virtual void Dispose(bool disposing) + finally { - // If disposing equals false, the method has been called by the - // runtime from inside the finalizer and you should not reference - // other objects. Only unmanaged resources can be disposed. - - try - { - //Make sure Dispose does not get called more than once, by checking the disposed field - if(!_isDisposed && disposing) - { - AfterAll(); - } - _isDisposed = true; - } - finally - { - } + Shutdown(); + _disposed = true; } } -} + + public void Dispose() + { + Dispose(true); + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/XunitAssertions.cs b/src/contrib/testkits/Akka.TestKit.Xunit/XunitAssertions.cs index 133dd89a920..f938d1f4f9e 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/XunitAssertions.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/XunitAssertions.cs @@ -9,81 +9,80 @@ using Akka.TestKit.Xunit.Internals; using Xunit; -namespace Akka.TestKit.Xunit +namespace Akka.TestKit.Xunit; + +/// +/// This class contains several common assert patterns used throughout this testkit. +/// +public class XunitAssertions : ITestKitAssertions { /// - /// This class contains several common assert patterns used throughout this testkit. + /// Fails an assertion without checking any conditions. /// - public class XunitAssertions : ITestKitAssertions + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void Fail(string format = "", params object[] args) { - /// - /// Fails an assertion without checking any conditions. - /// - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void Fail(string format = "", params object[] args) - { - Assert.Fail(string.Format(format, args)); - } + Assert.Fail(AkkaEqualException.BuildAssertionMessage(format, args)); + } - /// - /// Verifies that a specified is true. - /// - /// The condition that is being verified. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void AssertTrue(bool condition, string format = "", params object[] args) - { - Assert.True(condition, string.Format(format, args)); - } + /// + /// Verifies that a specified is true. + /// + /// The condition that is being verified. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void AssertTrue(bool condition, string format = "", params object[] args) + { + Assert.True(condition, AkkaEqualException.BuildAssertionMessage(format, args)); + } - /// - /// Verifies that a specified is false. - /// - /// The condition that is being verified. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void AssertFalse(bool condition, string format = "", params object[] args) - { - Assert.False(condition, string.Format(format, args)); - } + /// + /// Verifies that a specified is false. + /// + /// The condition that is being verified. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + public void AssertFalse(bool condition, string format = "", params object[] args) + { + Assert.False(condition, AkkaEqualException.BuildAssertionMessage(format, args)); + } - /// - /// Verifies that the two specified values ( and - /// are equal using the built-in comparison function . - /// - /// The type that is being compared. - /// The expected value of the object - /// The actual value of the object - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - /// - /// This exception is thrown when the two specified values are not equal. - /// - public void AssertEqual(T expected, T actual, string format = "", params object[] args) - { - var comparer = new AkkaAssertEqualityComparer(); - if(!comparer.Equals(expected, actual)) - throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); - } + /// + /// Verifies that the two specified values ( and + /// are equal using the built-in comparison function . + /// + /// The type that is being compared. + /// The expected value of the object + /// The actual value of the object + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + /// + /// This exception is thrown when the two specified values are not equal. + /// + public void AssertEqual(T expected, T actual, string format = "", params object[] args) + { + var comparer = new AkkaAssertEqualityComparer(); + if(!comparer.Equals(expected, actual)) + throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); + } - /// - /// Verifies that the two specified values ( and - /// are equal using a specified comparison function . - /// - /// The type that is being compared. - /// The expected value of the object - /// The actual value of the object - /// The function used to compare the two specified values. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - /// - /// This exception is thrown when the two specified values are not equal. - /// - public void AssertEqual(T expected, T actual, Func comparer, string format = "", params object[] args) - { - if(!comparer(expected, actual)) - throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); - } + /// + /// Verifies that the two specified values ( and + /// are equal using a specified comparison function . + /// + /// The type that is being compared. + /// The expected value of the object + /// The actual value of the object + /// The function used to compare the two specified values. + /// A template string to display if the assertion fails. + /// An optional object array that contains zero or more objects to format. + /// + /// This exception is thrown when the two specified values are not equal. + /// + public void AssertEqual(T expected, T actual, Func comparer, string format = "", params object[] args) + { + if(!comparer(expected, actual)) + throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); } -} +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj b/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj deleted file mode 100644 index 78fea4d9f72..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Akka.TestKit.Xunit3.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - $(NetStandardLibVersion);net8.0 - enable - enable - - - - - - - - - - - - true - - diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs deleted file mode 100644 index 1401599fb70..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparer.cs +++ /dev/null @@ -1,100 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using System.Collections; - -namespace Akka.TestKit.Xunit3.Internals; - -internal static class AkkaAssertEqualityComparer -{ - public static readonly IEqualityComparer DefaultInnerComparer = new AkkaAssertEqualityComparerAdapter(new AkkaAssertEqualityComparer()); - public static readonly Type NullableType = typeof(Nullable<>); -} - -/// -/// Default implementation of used by the Akka's xUnit.net equality assertions. -/// Copy of xUnits code -/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparer.cs -/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. -/// -/// The type that is being compared. -public class AkkaAssertEqualityComparer : IEqualityComparer -{ - private readonly Func _innerComparerFactory; - private readonly bool _skipTypeCheck; - - /// - /// Initializes a new instance of the class. - /// - /// Set to true to skip type equality checks. - /// The inner comparer to be used when the compared objects are enumerable. - public AkkaAssertEqualityComparer(bool skipTypeCheck = false, IEqualityComparer? innerComparer = null) - { - _skipTypeCheck = skipTypeCheck; - - // Use a thunk to delay evaluation of DefaultInnerComparer - _innerComparerFactory = () => innerComparer ?? AkkaAssertEqualityComparer.DefaultInnerComparer; - } - - public bool Equals(T? x, T? y) - { - var type = typeof(T); - - // Null? - if(!type.IsValueType || (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(AkkaAssertEqualityComparer.NullableType))) - { - if(object.Equals(x, default(T))) - return object.Equals(y, default(T)); - - if(object.Equals(y, default(T))) - return false; - } - - switch (x) - { - case IEquatable equatable: - return equatable.Equals(y!); - case IComparable comparableGeneric: - return comparableGeneric.CompareTo(y!) == 0; - case IComparable comparable: - return comparable.CompareTo(y) == 0; - case IEnumerable enumerableX when y is IEnumerable enumerableY: - var enumeratorX = enumerableX.GetEnumerator(); - var enumeratorY = enumerableY.GetEnumerator(); - var equalityComparer = _innerComparerFactory(); - - try - { - while (true) - { - var hasNextX = enumeratorX.MoveNext(); - var hasNextY = enumeratorY.MoveNext(); - - if (!hasNextX || !hasNextY) - return (hasNextX == hasNextY); - - if (!equalityComparer.Equals(enumeratorX.Current, enumeratorY.Current)) - return false; - } - } - finally - { - (enumeratorX as IDisposable)?.Dispose(); - (enumeratorY as IDisposable)?.Dispose(); - } - } - - // Same type? - if (!_skipTypeCheck && x!.GetType() != y!.GetType()) - return false; - - // Last case, rely on Object.Equals - return object.Equals(x, y); - } - - public int GetHashCode(T obj) => throw new NotImplementedException(); -} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs deleted file mode 100644 index a6bf7800b05..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaAssertEqualityComparerAdapter.cs +++ /dev/null @@ -1,37 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using System.Collections; - -namespace Akka.TestKit.Xunit3.Internals; - -/// -/// A class that wraps to create . -/// Copy of xUnits class: -/// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparerAdapter.cs -/// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. -/// -/// The type that is being compared. -internal class AkkaAssertEqualityComparerAdapter : IEqualityComparer -{ - private readonly IEqualityComparer _innerComparer; - - /// - /// Initializes a new instance of the class. - /// - /// The comparer that is being adapted. - public AkkaAssertEqualityComparerAdapter(IEqualityComparer innerComparer) - { - _innerComparer = innerComparer; - } - - /// - public new bool Equals(object? x, object? y) => _innerComparer.Equals((T?)x, (T?)y); - - /// - public int GetHashCode(object obj) => throw new NotImplementedException(); -} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs deleted file mode 100644 index 5e271fad0bb..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/AkkaEqualException.cs +++ /dev/null @@ -1,87 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using System.Runtime.Serialization; -using Xunit.Sdk; - -namespace Akka.TestKit.Xunit3.Internals; - -/// -/// TBD -/// -[Serializable] -public class AkkaEqualException : XunitException -{ - // Length of "Expected: " and "Actual: " - private static readonly string NewLineAndIndent = Environment.NewLine + new string(' ', 10); - - public static AkkaEqualException ForMismatchedValues( - object? expected, - object? actual, - string? format = null, - params object[] args) - { - // Strings normally come through ForMismatchedStrings, so we want to make sure any - // string value that comes through here isn't re-formatted/truncated. This is for - // two reasons: (a) to support Assert.Equal(string1, string2) to get a full - // printout of the raw string values, which is useful when debugging; and (b) to - // allow the assertion functions to pre-format the value themselves, perhaps with - // additional information (like DateTime/DateTimeOffset when providing the precision - // of the comparison). - var expectedText = expected as string ?? ArgumentFormatter.Format(expected); - var actualText = actual as string ?? ArgumentFormatter.Format(actual); - - return new AkkaEqualException( - "Assert.Equal() Failure: " + (format ?? "Values differ") + Environment.NewLine + - "Expected: " + expectedText.Replace(Environment.NewLine, NewLineAndIndent) + Environment.NewLine + - "Actual: " + actualText.Replace(Environment.NewLine, NewLineAndIndent), - args - ); - } - - /// - /// Initializes a new instance of the class. - /// - /// A template string that describes the error. - /// An optional object array that contains zero or more objects to format. - public AkkaEqualException(string format = "", params object[] args) - : base(BuildAssertionMessage(format, args)) { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected AkkaEqualException(SerializationInfo info, StreamingContext context) - : base(info.GetString("Message")) { } - - /// - /// Builds assertion message by applying specified arguments to the format string. - /// When no arguments are specified, format string is returned as-is. - /// - internal static string? BuildAssertionMessage(string format, object[] args) - { - if (string.IsNullOrEmpty(format)) - { - return null; - } - - if (args is not { Length: > 0 }) - { - return format; - } - - try - { - return string.Format(format, args); - } - catch (Exception) - { - return $"""[Could not string.Format("{format}", {string.Join(", ", args)})]"""; - } - } -} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs deleted file mode 100644 index a7626b3d081..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/Internals/Loggers.cs +++ /dev/null @@ -1,66 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using Akka.Actor; -using Akka.Event; -using Akka.Util; -using Xunit; - -namespace Akka.TestKit.Xunit3.Internals; - -/// -/// This class represents an actor that logs output from tests using an provider. -/// -public class TestOutputLogger : ReceiveActor -{ - private readonly ITestOutputHelper _output; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to write test output. - public TestOutputLogger(ITestOutputHelper output) - { - _output = output; - - Receive(HandleLogEvent); - Receive(HandleLogEvent); - Receive(HandleLogEvent); - Receive(HandleLogEvent); - Receive(e => - { - e.LoggingBus.Subscribe(Self, typeof (LogEvent)); - Sender.Tell(new LoggerInitialized()); - }); - } - - private void HandleLogEvent(LogEvent e) - { - try - { - _output.WriteLine(e.ToString()); - } - catch (FormatException ex) - { - if (e.Message is LogMessage msg) - { - var message = - $"Received a malformed formatted message. Log level: [{e.LogLevel()}], Template: [{msg.Format}], args: [{string.Join(",", msg.Unformatted())}]"; - if (e.Cause != null) - throw new AggregateException(message, ex, e.Cause); - throw new FormatException(message, ex); - } - - throw; - } - catch (InvalidOperationException ie) - { - StandardOutWriter.WriteLine($"Received InvalidOperationException: {ie} - probably because the test had completed executing."); - Context.Stop(Self); // shut ourselves down, can't do our job any longer - } - } -} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs deleted file mode 100644 index 89d74046259..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/TestKit.cs +++ /dev/null @@ -1,220 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using Akka.Actor; -using Akka.Actor.Setup; -using Akka.Configuration; -using Akka.Event; -using Akka.TestKit.Xunit3.Internals; -using Xunit; - -namespace Akka.TestKit.Xunit3; - -/// -/// This class represents an Akka.NET TestKit that uses xUnit -/// as its testing framework. -/// -public class TestKit : TestKitBase, IDisposable -{ - private class PrefixedOutput : ITestOutputHelper - { - private static readonly string Newline = Environment.NewLine; - private readonly ITestOutputHelper _output; - private readonly string _prefix; - private bool _newLine; - - public PrefixedOutput(ITestOutputHelper output, string prefix) - { - _output = output; - _prefix = prefix; - } - - public void Write(string message) - { - if(_newLine) - message = _prefix + Newline; - - var parts = message.Split([Newline], StringSplitOptions.None); - _output.Write(string.Join($"{Newline}{_prefix}", parts)); - _newLine = message.EndsWith(Newline); - } - - public void Write(string format, params object[] args) - { - if(_newLine) - format = _prefix + format; - var parts = format.Split([Newline], StringSplitOptions.None); - format = string.Join($"{Newline}{_prefix}", parts); - - _output.Write(format, args); - _newLine = format.EndsWith(Newline); - } - - public void WriteLine(string message) - { - if (_newLine) - message = _prefix + message; - _newLine = true; - _output.WriteLine(message); - } - - public void WriteLine(string format, params object[] args) - { - if (_newLine) - format = _prefix + format; - _newLine = true; - _output.WriteLine(format, args); - } - - public string Output => _output.Output; - } - - /// - /// The provider used to write test output. - /// - protected readonly ITestOutputHelper? Output; - - private bool _disposed; - private bool _disposing; - - /// - /// - /// Initializes a new instance of the class. - /// - /// - /// If no is passed in, a new system with - /// will be created. - /// - /// - /// The actor system to use for testing. The default value is . - /// The provider used to write test output. The default value is . - public TestKit(ActorSystem? system = null, ITestOutputHelper? output = null) - : base(Assertions, system) - { - Output = output; - InitializeLogger(Sys); - } - - /// - /// Initializes a new instance of the class. - /// - /// The to use for configuring the ActorSystem. - /// The name of the system. The default name is "test". - /// The provider used to write test output. The default value is . - public TestKit(ActorSystemSetup config, string? actorSystemName = null, ITestOutputHelper? output = null) - : base(Assertions, config, actorSystemName) - { - Output = output; - InitializeLogger(Sys); - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration to use for the system. - /// The name of the system. The default name is "test". - /// The provider used to write test output. The default value is . - public TestKit(Config config, string? actorSystemName = null, ITestOutputHelper? output = null) - : base(Assertions, config, actorSystemName) - { - Output = output; - InitializeLogger(Sys); - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration to use for the system. - /// The provider used to write test output. The default value is . - public TestKit(string config, ITestOutputHelper? output = null) - : base(Assertions, ConfigurationFactory.ParseString(config)) - { - Output = output; - InitializeLogger(Sys); - } - - /// - /// A configuration that has just the default log settings enabled. The default settings can be found in - /// Akka.TestKit.Internal.Reference.conf. - /// - public new static Config DefaultConfig => TestKitBase.DefaultConfig; - - /// - /// A configuration that has all log settings enabled - /// - public new static Config FullDebugConfig => TestKitBase.FullDebugConfig; - - /// - /// Commonly used assertions used throughout the testkit. - /// - protected static XunitAssertions Assertions { get; } = new(); - - /// - /// This method is called when a test ends. - /// - protected virtual void AfterAll() - { - } - - /// - /// Initializes a new used to log messages. - /// - /// The actor system used to attach the logger - protected void InitializeLogger(ActorSystem system) - { - if (Output == null) - return; - - var extSystem = (ExtendedActorSystem)system; - var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(Output)), "log-test"); - logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) - .ConfigureAwait(false).GetAwaiter().GetResult(); - } - - protected void InitializeLogger(ActorSystem system, string prefix) - { - if (Output == null) - return; - - var extSystem = (ExtendedActorSystem)system; - var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger( - string.IsNullOrEmpty(prefix) ? Output : new PrefixedOutput(Output, prefix))), "log-test"); - logger.Ask(new InitializeLogger(system.EventStream), TestKitSettings.TestKitStartupTimeout) - .ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// - /// if set to true the method has been called directly or indirectly by a user's code. - /// Managed and unmanaged resources will be disposed.
if set to false the method - /// has been called by the runtime from inside the finalizer and only unmanaged resources can - /// be disposed. - /// - protected virtual void Dispose(bool disposing) - { - if (_disposing || _disposed) - return; - - _disposing = true; - try - { - AfterAll(); - } - finally - { - Shutdown(); - _disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } -} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs b/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs deleted file mode 100644 index 7c34a785010..00000000000 --- a/src/contrib/testkits/Akka.TestKit.Xunit3/XunitAssertions.cs +++ /dev/null @@ -1,87 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2022 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -using Akka.TestKit.Xunit3.Internals; -using Xunit; - -namespace Akka.TestKit.Xunit3; - -/// -/// This class contains several common assert patterns used throughout this testkit. -/// -public class XunitAssertions : ITestKitAssertions -{ - /// - /// Fails an assertion without checking any conditions. - /// - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void Fail(string format = "", params object[] args) - { - Assert.Fail(AkkaEqualException.BuildAssertionMessage(format, args)); - } - - /// - /// Verifies that a specified is true. - /// - /// The condition that is being verified. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void AssertTrue(bool condition, string format = "", params object[] args) - { - Assert.True(condition, AkkaEqualException.BuildAssertionMessage(format, args)); - } - - /// - /// Verifies that a specified is false. - /// - /// The condition that is being verified. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - public void AssertFalse(bool condition, string format = "", params object[] args) - { - Assert.False(condition, AkkaEqualException.BuildAssertionMessage(format, args)); - } - - /// - /// Verifies that the two specified values ( and - /// are equal using the built-in comparison function . - /// - /// The type that is being compared. - /// The expected value of the object - /// The actual value of the object - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - /// - /// This exception is thrown when the two specified values are not equal. - /// - public void AssertEqual(T expected, T actual, string format = "", params object[] args) - { - var comparer = new AkkaAssertEqualityComparer(); - if(!comparer.Equals(expected, actual)) - throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); - } - - /// - /// Verifies that the two specified values ( and - /// are equal using a specified comparison function . - /// - /// The type that is being compared. - /// The expected value of the object - /// The actual value of the object - /// The function used to compare the two specified values. - /// A template string to display if the assertion fails. - /// An optional object array that contains zero or more objects to format. - /// - /// This exception is thrown when the two specified values are not equal. - /// - public void AssertEqual(T expected, T actual, Func comparer, string format = "", params object[] args) - { - if(!comparer(expected, actual)) - throw AkkaEqualException.ForMismatchedValues(expected, actual, format, args); - } -} \ No newline at end of file