From 2df474a01f8647fa150cb91c3fa3049a715250ef Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 30 Oct 2024 12:16:52 +1100 Subject: [PATCH] Add Combination callbacks (#1334) --- docs/combinations.md | 16 +- docs/mdsource/combinations.source.md | 2 +- src/StaticSettingsTests/BaseTest.cs | 5 +- ...ddressExceptionsDisabledTest.verified.txt} | 0 ...inationsTests.CallbackResults.verified.txt | 11 + ...mbinationsTests.CallbacksTest.verified.txt | 6 + ...bination_CustomSerialization.verified.txt} | 0 ...> CombinationsTests.Defaults.verified.txt} | 0 ...ests.ExceptionCallbackResults.verified.txt | 15 ++ ...sTests.ExceptionCallbacksTest.verified.txt | 6 + ...sTests.WithCaptureExceptions.verified.txt} | 0 src/StaticSettingsTests/CombinationsTests.cs | 233 ++++++++++++++++++ .../VerifyCombinationsTests.cs | 124 ---------- src/Verify.Tests/CombinationsTests.cs | 2 +- src/Verify/Combinations/CombinationRunner.cs | 5 +- .../Combinations/CombinationSettings.cs | 63 +++++ .../Combinations/VerifyCombinationSettings.cs | 9 - 17 files changed, 352 insertions(+), 145 deletions(-) rename src/StaticSettingsTests/{VerifyCombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt => CombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt} (100%) create mode 100644 src/StaticSettingsTests/CombinationsTests.CallbackResults.verified.txt create mode 100644 src/StaticSettingsTests/CombinationsTests.CallbacksTest.verified.txt rename src/StaticSettingsTests/{VerifyCombinationsTests.Combination_CustomSerialization.verified.txt => CombinationsTests.Combination_CustomSerialization.verified.txt} (100%) rename src/StaticSettingsTests/{VerifyCombinationsTests.Defaults.verified.txt => CombinationsTests.Defaults.verified.txt} (100%) create mode 100644 src/StaticSettingsTests/CombinationsTests.ExceptionCallbackResults.verified.txt create mode 100644 src/StaticSettingsTests/CombinationsTests.ExceptionCallbacksTest.verified.txt rename src/StaticSettingsTests/{VerifyCombinationsTests.WithCaptureExceptions.verified.txt => CombinationsTests.WithCaptureExceptions.verified.txt} (100%) create mode 100644 src/StaticSettingsTests/CombinationsTests.cs delete mode 100644 src/StaticSettingsTests/VerifyCombinationsTests.cs create mode 100644 src/Verify/Combinations/CombinationSettings.cs delete mode 100644 src/Verify/Combinations/VerifyCombinationSettings.cs diff --git a/docs/combinations.md b/docs/combinations.md index 2df87b46bf..f3154e19f0 100644 --- a/docs/combinations.md +++ b/docs/combinations.md @@ -149,9 +149,9 @@ Exception capture can be enable globally: ```cs [ModuleInitializer] public static void Initialize() => - VerifyCombinationSettings.CaptureExceptions(); + CombinationSettings.CaptureExceptions(); ``` -snippet source | anchor +snippet source | anchor If exception capture has been enabled globally, it can be disable at the method test level using `captureExceptions: false`. @@ -173,7 +173,7 @@ public Task BuildAddressExceptionsDisabledTest() cities); } ``` -snippet source | anchor +snippet source | anchor @@ -335,7 +335,7 @@ class CustomCombinationConverter : string.Join(", ", keys.Select(_ => _.Value)); } ``` -snippet source | anchor +snippet source | anchor Full control of serialization can be achieved by inheriting from `WriteOnlyJsonConverter`. @@ -354,14 +354,14 @@ static CustomCombinationConverter customConverter = new(); public static void Init() => VerifierSettings.AddExtraSettings(_ => _.Converters.Insert(0, customConverter)); ``` -snippet source | anchor +snippet source | anchor #### Result - - + + ```txt { 1, Smith St, Sydney: 1 Smith St, Sydney, @@ -374,5 +374,5 @@ public static void Init() => 10, Wallace St, Chicago: 10 Wallace St, Chicago } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/mdsource/combinations.source.md b/docs/mdsource/combinations.source.md index 4c173085c3..0e698c2939 100644 --- a/docs/mdsource/combinations.source.md +++ b/docs/mdsource/combinations.source.md @@ -86,4 +86,4 @@ snippet: CombinationSample_CustomSerializationModuleInitializer #### Result -snippet: VerifyCombinationsTests.Combination_CustomSerialization.verified.txt \ No newline at end of file +snippet: CombinationsTests.Combination_CustomSerialization.verified.txt \ No newline at end of file diff --git a/src/StaticSettingsTests/BaseTest.cs b/src/StaticSettingsTests/BaseTest.cs index d0898a1736..dec473f6d2 100644 --- a/src/StaticSettingsTests/BaseTest.cs +++ b/src/StaticSettingsTests/BaseTest.cs @@ -7,6 +7,9 @@ public abstract class BaseTest { - protected BaseTest() => + protected BaseTest() + { VerifierSettings.Reset(); + CombinationSettings.Reset(); + } } \ No newline at end of file diff --git a/src/StaticSettingsTests/VerifyCombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt b/src/StaticSettingsTests/CombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt similarity index 100% rename from src/StaticSettingsTests/VerifyCombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt rename to src/StaticSettingsTests/CombinationsTests.BuildAddressExceptionsDisabledTest.verified.txt diff --git a/src/StaticSettingsTests/CombinationsTests.CallbackResults.verified.txt b/src/StaticSettingsTests/CombinationsTests.CallbackResults.verified.txt new file mode 100644 index 0000000000..0a2450aad7 --- /dev/null +++ b/src/StaticSettingsTests/CombinationsTests.CallbackResults.verified.txt @@ -0,0 +1,11 @@ +{ + beforeKeys: [ + 10, + value2 + ], + afterKeys: [ + 10, + value2 + ], + afterResult: 10 value2 +} \ No newline at end of file diff --git a/src/StaticSettingsTests/CombinationsTests.CallbacksTest.verified.txt b/src/StaticSettingsTests/CombinationsTests.CallbacksTest.verified.txt new file mode 100644 index 0000000000..891b0071ff --- /dev/null +++ b/src/StaticSettingsTests/CombinationsTests.CallbacksTest.verified.txt @@ -0,0 +1,6 @@ +{ + 1, value1: 1 value1, + 1, value2: 1 value2, + 10, value1: 10 value1, + 10, value2: 10 value2 +} \ No newline at end of file diff --git a/src/StaticSettingsTests/VerifyCombinationsTests.Combination_CustomSerialization.verified.txt b/src/StaticSettingsTests/CombinationsTests.Combination_CustomSerialization.verified.txt similarity index 100% rename from src/StaticSettingsTests/VerifyCombinationsTests.Combination_CustomSerialization.verified.txt rename to src/StaticSettingsTests/CombinationsTests.Combination_CustomSerialization.verified.txt diff --git a/src/StaticSettingsTests/VerifyCombinationsTests.Defaults.verified.txt b/src/StaticSettingsTests/CombinationsTests.Defaults.verified.txt similarity index 100% rename from src/StaticSettingsTests/VerifyCombinationsTests.Defaults.verified.txt rename to src/StaticSettingsTests/CombinationsTests.Defaults.verified.txt diff --git a/src/StaticSettingsTests/CombinationsTests.ExceptionCallbackResults.verified.txt b/src/StaticSettingsTests/CombinationsTests.ExceptionCallbackResults.verified.txt new file mode 100644 index 0000000000..b319f95d07 --- /dev/null +++ b/src/StaticSettingsTests/CombinationsTests.ExceptionCallbackResults.verified.txt @@ -0,0 +1,15 @@ +{ + beforeKeys: [ + 10, + value2 + ], + exceptionKeys: [ + 10, + value2 + ], + exceptionResult: { + $type: Exception, + Type: Exception, + Message: Message + } +} \ No newline at end of file diff --git a/src/StaticSettingsTests/CombinationsTests.ExceptionCallbacksTest.verified.txt b/src/StaticSettingsTests/CombinationsTests.ExceptionCallbacksTest.verified.txt new file mode 100644 index 0000000000..df9e395745 --- /dev/null +++ b/src/StaticSettingsTests/CombinationsTests.ExceptionCallbacksTest.verified.txt @@ -0,0 +1,6 @@ +{ + 1, value1: Exception: Message, + 1, value2: Exception: Message, + 10, value1: Exception: Message, + 10, value2: Exception: Message +} \ No newline at end of file diff --git a/src/StaticSettingsTests/VerifyCombinationsTests.WithCaptureExceptions.verified.txt b/src/StaticSettingsTests/CombinationsTests.WithCaptureExceptions.verified.txt similarity index 100% rename from src/StaticSettingsTests/VerifyCombinationsTests.WithCaptureExceptions.verified.txt rename to src/StaticSettingsTests/CombinationsTests.WithCaptureExceptions.verified.txt diff --git a/src/StaticSettingsTests/CombinationsTests.cs b/src/StaticSettingsTests/CombinationsTests.cs new file mode 100644 index 0000000000..be6e3faa9d --- /dev/null +++ b/src/StaticSettingsTests/CombinationsTests.cs @@ -0,0 +1,233 @@ +public class CombinationsTests +{ + #region GlobalCaptureExceptions + + [ModuleInitializer] + public static void Initialize() => + CombinationSettings.CaptureExceptions(); + + #endregion + + [Fact] + public Task Defaults() + { + string[] list = ["A", "b", "C"]; + return Combination().Verify( + _ => _.ToLower(), + list); + } + + [Fact] + public async Task CallbacksTest() + { + var beforeCalled = false; + IReadOnlyList? beforeKeys = null; + object? afterResult = null; + IReadOnlyList? afterKeys = null; + IReadOnlyList? exceptionKeys = null; + Exception? exceptionResult = null; + var afterCalled = false; + var exceptionCalled = false; + int[] params1 = [1, 10]; + string[] params2 = ["value1", "value2"]; + CombinationSettings.UseCallbacks( + keys => + { + beforeKeys = keys; + beforeCalled = true; + return Task.CompletedTask; + }, + (keys, result) => + { + afterResult = result; + afterKeys = keys; + afterCalled = true; + return Task.CompletedTask; + }, + (keys, exception) => + { + exceptionResult = exception; + exceptionKeys = keys; + exceptionCalled = true; + return Task.CompletedTask; + }); + await Combination() + .Verify( + (param1, param2) => $"{param1} {param2}", + params1, + params2); + Assert.True(beforeCalled); + Assert.True(afterCalled); + Assert.False(exceptionCalled); + await Verify(new + { + beforeKeys, + afterKeys, + afterResult, + exceptionKeys, + exceptionResult + }) + .UseMethodName("CallbackResults"); + } + + [Fact] + public async Task ExceptionCallbacksTest() + { + var beforeCalled = false; + IReadOnlyList? beforeKeys = null; + object? afterResult = null; + IReadOnlyList? afterKeys = null; + IReadOnlyList? exceptionKeys = null; + Exception? exceptionResult = null; + var afterCalled = false; + var exceptionCalled = false; + int[] params1 = [1, 10]; + string[] params2 = ["value1", "value2"]; + CombinationSettings.UseCallbacks( + keys => + { + beforeKeys = keys; + beforeCalled = true; + return Task.CompletedTask; + }, + (keys, result) => + { + afterResult = result; + afterKeys = keys; + afterCalled = true; + return Task.CompletedTask; + }, + (keys, exception) => + { + exceptionResult = exception; + exceptionKeys = keys; + exceptionCalled = true; + return Task.CompletedTask; + }); + + static string Method(int i, string s) => + throw new("Message"); + + await Combination() + .Verify( + Method, + params1, + params2); + Assert.True(beforeCalled); + Assert.False(afterCalled); + Assert.True(exceptionCalled); + await Verify(new + { + beforeKeys, + afterKeys, + afterResult, + exceptionKeys, + exceptionResult + }) + .UseMethodName("ExceptionCallbackResults") + .IgnoreStackTrace(); + } + + [Fact] + public Task WithCaptureExceptions() + { + string[] a = ["A", "b", "C"]; + return Combination(captureExceptions: true) + .Verify( + a => + { + if (a == "b") + { + throw new ArgumentException("B is not allowed"); + } + + return a.ToLower(); + }, + a); + } + + [Fact] + public Task WithNoCaptureExceptions() + { + string[] a = ["A", "b", "C"]; + return Assert.ThrowsAsync( + () => Combination(captureExceptions: false) + .Verify( + a => + { + if (a == "b") + { + throw new ArgumentException("B is not allowed"); + } + + return a.ToLower(); + }, + a)); + } + + static string BuildAddress(int streetNumber, string street, string city) + { + ArgumentException.ThrowIfNullOrWhiteSpace(street); + ArgumentException.ThrowIfNullOrWhiteSpace(city); + ArgumentOutOfRangeException.ThrowIfLessThan(streetNumber, 1); + + return $"{streetNumber} {street}, {city}"; + } + + #region CombinationSample_CaptureExceptionsFalse + + [Fact] + public Task BuildAddressExceptionsDisabledTest() + { + int[] streetNumbers = [1, 10]; + string[] streets = ["Smith St", "Wallace St"]; + string[] cities = ["Sydney", "Chicago"]; + return Combination(captureExceptions: false) + .Verify( + BuildAddress, + streetNumbers, + streets, + cities); + } + + #endregion + + #region CombinationSample_CustomSerializationModuleInitializer + + static CustomCombinationConverter customConverter = new(); + + [ModuleInitializer] + public static void Init() => + VerifierSettings.AddExtraSettings(_ => _.Converters.Insert(0, customConverter)); + + #endregion + + #region CombinationSample_CustomSerialization + + [Fact] + public Task Combination_CustomSerialization() + { + int[] streetNumbers = [1, 10]; + string[] streets = ["Smith St", "Wallace St"]; + string[] cities = ["Sydney", "Chicago"]; + return Combination() + .Verify( + BuildAddress, + streetNumbers, + streets, + cities); + } + + #endregion + + #region CombinationSample_CustomSerializationConverter + + class CustomCombinationConverter : + CombinationResultsConverter + { + protected override string BuildPropertyName(IReadOnlyList keys) => + string.Join(", ", keys.Select(_ => _.Value)); + } + + #endregion +} \ No newline at end of file diff --git a/src/StaticSettingsTests/VerifyCombinationsTests.cs b/src/StaticSettingsTests/VerifyCombinationsTests.cs deleted file mode 100644 index d5937aa5c1..0000000000 --- a/src/StaticSettingsTests/VerifyCombinationsTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -public class VerifyCombinationsTests -{ - #region GlobalCaptureExceptions - - [ModuleInitializer] - public static void Initialize() => - VerifyCombinationSettings.CaptureExceptions(); - - #endregion - - [Fact] - public Task Defaults() - { - string[] list = ["A", "b", "C"]; - return Combination().Verify( - _ => _.ToLower(), - list); - } - - [Fact] - public Task WithCaptureExceptions() - { - string[] a = ["A", "b", "C"]; - return Combination(captureExceptions: true) - .Verify( - a => - { - if (a == "b") - { - throw new ArgumentException("B is not allowed"); - } - - return a.ToLower(); - }, - a); - } - - [Fact] - public Task WithNoCaptureExceptions() - { - string[] a = ["A", "b", "C"]; - return Assert.ThrowsAsync(() => - { - return Combination(captureExceptions: false) - .Verify( - a => - { - if (a == "b") - { - throw new ArgumentException("B is not allowed"); - } - - return a.ToLower(); - }, - a); - }); - } - - public static string BuildAddress(int streetNumber, string street, string city) - { - ArgumentException.ThrowIfNullOrWhiteSpace(street); - ArgumentException.ThrowIfNullOrWhiteSpace(city); - ArgumentOutOfRangeException.ThrowIfLessThan(streetNumber, 1); - - return $"{streetNumber} {street}, {city}"; - } - - #region CombinationSample_CaptureExceptionsFalse - - [Fact] - public Task BuildAddressExceptionsDisabledTest() - { - int[] streetNumbers = [1, 10]; - string[] streets = ["Smith St", "Wallace St"]; - string[] cities = ["Sydney", "Chicago"]; - return Combination(captureExceptions: false) - .Verify( - BuildAddress, - streetNumbers, - streets, - cities); - } - - #endregion - - #region CombinationSample_CustomSerializationModuleInitializer - - static CustomCombinationConverter customConverter = new(); - - [ModuleInitializer] - public static void Init() => - VerifierSettings.AddExtraSettings(_ => _.Converters.Insert(0, customConverter)); - - #endregion - - #region CombinationSample_CustomSerialization - - [Fact] - public Task Combination_CustomSerialization() - { - int[] streetNumbers = [1, 10]; - string[] streets = ["Smith St", "Wallace St"]; - string[] cities = ["Sydney", "Chicago"]; - return Combination() - .Verify( - BuildAddress, - streetNumbers, - streets, - cities); - } - - #endregion - - #region CombinationSample_CustomSerializationConverter - - class CustomCombinationConverter : - CombinationResultsConverter - { - protected override string BuildPropertyName(IReadOnlyList keys) => - string.Join(", ", keys.Select(_ => _.Value)); - } - - #endregion -} \ No newline at end of file diff --git a/src/Verify.Tests/CombinationsTests.cs b/src/Verify.Tests/CombinationsTests.cs index 65dc697e2a..da91d08ad0 100644 --- a/src/Verify.Tests/CombinationsTests.cs +++ b/src/Verify.Tests/CombinationsTests.cs @@ -2,7 +2,7 @@ public class CombinationsTests { static int[] params1 = [1, 10]; - static string[] params2 = ["Smith St", "Wallace St"]; + static string[] params2 = ["Smith St", "Wallace St"]; public static async IAsyncEnumerable AsyncEnumerableMethod(int param1, string param2) { diff --git a/src/Verify/Combinations/CombinationRunner.cs b/src/Verify/Combinations/CombinationRunner.cs index af539dcfd0..285b7b39ab 100644 --- a/src/Verify/Combinations/CombinationRunner.cs +++ b/src/Verify/Combinations/CombinationRunner.cs @@ -8,7 +8,7 @@ public CombinationRunner(bool? captureExceptions, List> lists, Type[] keyTypes) { this.keyTypes = keyTypes; - this.captureExceptions = captureExceptions ?? VerifyCombinationSettings.CaptureExceptionsEnabled; + this.captureExceptions = captureExceptions ?? CombinationSettings.CaptureExceptionsEnabled; this.lists = lists.Select(_ => _.ToArray()).ToArray(); indices = new int[lists.Count]; } @@ -21,12 +21,15 @@ async Task InnerRun(Func> var keys = BuildParameters(); try { + await CombinationSettings.RunBeforeCallbacks(keys); var value = await method(keys); + await CombinationSettings.RunAfterCallbacks(keys, value); items.Add(new(keys, value)); } catch (Exception exception) when (captureExceptions) { + await CombinationSettings.RunExceptionCallbacks(keys, exception); items.Add(new(keys, exception)); } diff --git a/src/Verify/Combinations/CombinationSettings.cs b/src/Verify/Combinations/CombinationSettings.cs new file mode 100644 index 0000000000..abbc3af872 --- /dev/null +++ b/src/Verify/Combinations/CombinationSettings.cs @@ -0,0 +1,63 @@ +namespace VerifyTests; + +public delegate Task BeforeCombination(IReadOnlyList keys); +public delegate Task AfterCombination(IReadOnlyList keys, object? result); +public delegate Task CombinationException(IReadOnlyList keys, Exception exception); + +public static class CombinationSettings +{ + public static bool CaptureExceptionsEnabled { get; private set; } + + public static void CaptureExceptions() => + CaptureExceptionsEnabled = true; + + static BeforeCombination? before; + + internal static Task RunBeforeCallbacks(IReadOnlyList keys) + { + if (before == null) + { + return Task.CompletedTask; + } + + return before(keys); + } + + static AfterCombination? after; + + internal static Task RunAfterCallbacks(IReadOnlyList keys, object? result) + { + if (after == null) + { + return Task.CompletedTask; + } + + return after(keys, result); + } + + static CombinationException? combinationException; + + internal static Task RunExceptionCallbacks(IReadOnlyList keys, Exception exception) + { + if (combinationException == null) + { + return Task.CompletedTask; + } + + return combinationException(keys, exception); + } + + public static void UseCallbacks(BeforeCombination before, AfterCombination after, CombinationException exception) + { + CombinationSettings.before += before; + CombinationSettings.after += after; + combinationException += exception; + } + + public static void Reset() + { + combinationException = null; + after = null; + before = null; + } +} \ No newline at end of file diff --git a/src/Verify/Combinations/VerifyCombinationSettings.cs b/src/Verify/Combinations/VerifyCombinationSettings.cs deleted file mode 100644 index 55db41286d..0000000000 --- a/src/Verify/Combinations/VerifyCombinationSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace VerifyTests; - -public static class VerifyCombinationSettings -{ - public static bool CaptureExceptionsEnabled { get; private set; } - - public static void CaptureExceptions() => - CaptureExceptionsEnabled = true; -} \ No newline at end of file