From 3f8adb0f80a54f0bb0ca31e023dcefaae29df713 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 26 Jul 2024 10:19:42 +1000 Subject: [PATCH] fix identifier recording shared state (#1257) --- docs/mdsource/recording.source.md | 2 + docs/recording.md | 10 +-- src/Directory.Build.props | 2 +- ...cordingTests.StartIdentifier1.verified.txt | 1 - ...ecordingTests.StopIdentifier2.verified.txt | 1 - ...ngTests.StopIdentifierOverlap.verified.txt | 5 -- src/Verify.Tests/RecordingTests.cs | 15 ++--- src/Verify/Recording/Recording_Named.cs | 62 +++++-------------- 8 files changed, 27 insertions(+), 71 deletions(-) delete mode 100644 src/Verify.Tests/RecordingTests.StartIdentifier1.verified.txt delete mode 100644 src/Verify.Tests/RecordingTests.StopIdentifier2.verified.txt delete mode 100644 src/Verify.Tests/RecordingTests.StopIdentifierOverlap.verified.txt diff --git a/docs/mdsource/recording.source.md b/docs/mdsource/recording.source.md index 37924ca8f7..f543c2fde9 100644 --- a/docs/mdsource/recording.source.md +++ b/docs/mdsource/recording.source.md @@ -51,6 +51,8 @@ To avoid grouping use [Stop](#stop). Recording can be grouped by an identifier. +The identifier should be statically unique. For example a fully qualified test name or a GUID. + snippet: RecordingIdentifier Results in: diff --git a/docs/recording.md b/docs/recording.md index 5c6f0c90ad..137857f7ac 100644 --- a/docs/recording.md +++ b/docs/recording.md @@ -117,7 +117,7 @@ public Task SameKey() return Verify("TheValue"); } ``` -snippet source | anchor +snippet source | anchor Results in: @@ -143,6 +143,8 @@ To avoid grouping use [Stop](#stop). Recording can be grouped by an identifier. +The identifier should be statically unique. For example a fully qualified test name or a GUID. + ```cs @@ -186,7 +188,7 @@ public Task Case() return Verify("TheValue"); } ``` -snippet source | anchor +snippet source | anchor Results in: @@ -305,7 +307,7 @@ public Task Clear() return Verify(); } ``` -snippet source | anchor +snippet source | anchor Results in: @@ -341,7 +343,7 @@ public Task PauseResume() return Verify(); } ``` -snippet source | anchor +snippet source | anchor Results in: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e6c52e3232..6387a9dbf1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;xUnit1026;xUnit1013;CS1573;VerifyTestsProjectDir;VerifySetParameters;PolyFillTargetsForNuget - 26.1.1 + 26.1.2 enable preview 1.0.0 diff --git a/src/Verify.Tests/RecordingTests.StartIdentifier1.verified.txt b/src/Verify.Tests/RecordingTests.StartIdentifier1.verified.txt deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Verify.Tests/RecordingTests.StartIdentifier1.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Verify.Tests/RecordingTests.StopIdentifier2.verified.txt b/src/Verify.Tests/RecordingTests.StopIdentifier2.verified.txt deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Verify.Tests/RecordingTests.StopIdentifier2.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Verify.Tests/RecordingTests.StopIdentifierOverlap.verified.txt b/src/Verify.Tests/RecordingTests.StopIdentifierOverlap.verified.txt deleted file mode 100644 index d09ad9b9f3..0000000000 --- a/src/Verify.Tests/RecordingTests.StopIdentifierOverlap.verified.txt +++ /dev/null @@ -1,5 +0,0 @@ -[ - { - name: value - } -] \ No newline at end of file diff --git a/src/Verify.Tests/RecordingTests.cs b/src/Verify.Tests/RecordingTests.cs index d854cf5879..0d70109f38 100644 --- a/src/Verify.Tests/RecordingTests.cs +++ b/src/Verify.Tests/RecordingTests.cs @@ -167,18 +167,11 @@ public Task StopNotInResult() #endregion [Fact] - public void StartIdentifierThatDoesntFinish() + public Task StopIdentifier() { - Recording.Start("identifierOverlap"); - Recording.Add("identifierOverlap", "name", "value"); - } - - [Fact] - public Task StopIdentifierOverlap() - { - Recording.Start("identifierOverlap"); - Recording.Add("identifierOverlap", "name", "value"); - return Verify(Recording.Stop("identifierOverlap")); + Recording.Start("identifier"); + Recording.Add("identifier", "name", "value"); + return Verify(Recording.Stop("identifier")); } [Fact] diff --git a/src/Verify/Recording/Recording_Named.cs b/src/Verify/Recording/Recording_Named.cs index 9575bacd62..00c6232ce9 100644 --- a/src/Verify/Recording/Recording_Named.cs +++ b/src/Verify/Recording/Recording_Named.cs @@ -2,7 +2,9 @@ public static partial class Recording { - static AsyncLocal?> asyncLocalNamed = new(); + // The identifier should be statically unique. For example a fully qualified test name or a GUID. + // so it is ok for this to be shared state + static ConcurrentDictionary namedState = new(StringComparer.OrdinalIgnoreCase); public static void Add(string identifier, string name, object item) => CurrentStateNamed(identifier) @@ -10,25 +12,15 @@ public static void Add(string identifier, string name, object item) => public static void TryAdd(string identifier, string name, object item) { - var states = asyncLocalNamed.Value; - if (states != null) + if (namedState.TryGetValue(identifier, out var state)) { - if (states.TryGetValue(identifier, out var state)) - { - state.Add(name, item); - } + state.Add(name, item); } } public static bool IsRecording(string identifier) { - var states = asyncLocalNamed.Value; - if (states == null) - { - return false; - } - - if (!states.TryGetValue(identifier, out var state)) + if (!namedState.TryGetValue(identifier, out var state)) { return false; } @@ -50,14 +42,10 @@ public static bool TryStop( string identifier, [NotNullWhen(true)] out IReadOnlyCollection? recorded) { - var states = asyncLocalNamed.Value; - if (states != null) + if (namedState.TryRemove(identifier, out var state)) { - if (states.TryRemove(identifier, out var state)) - { - recorded = state.Items; - return true; - } + recorded = state.Items; + return true; } recorded = null; @@ -66,9 +54,7 @@ public static bool TryStop( static State CurrentStateNamed(string identifier, [CallerMemberName] string caller = "") { - var states = asyncLocalNamed.Value; - if (states != null && - states.TryGetValue(identifier, out var state)) + if (namedState.TryGetValue(identifier, out var state)) { return state; } @@ -78,9 +64,7 @@ static State CurrentStateNamed(string identifier, [CallerMemberName] string call public static IDisposable Start(string identifier) { - var states = asyncLocalNamed.Value ??= new(StringComparer.OrdinalIgnoreCase); - - if (!states.TryAdd(identifier, new())) + if (!namedState.TryAdd(identifier, new())) { throw new("Recording already started"); } @@ -101,13 +85,7 @@ public static void Pause(string identifier) => public static void TryPause(string identifier) { - var states = asyncLocalNamed.Value; - if (states == null) - { - return; - } - - if (states.TryGetValue(identifier, out var state)) + if (namedState.TryGetValue(identifier, out var state)) { state.Pause(); } @@ -119,13 +97,7 @@ public static void Resume(string identifier) => public static void TryResume(string identifier) { - var states = asyncLocalNamed.Value; - if (states == null) - { - return; - } - - if (states.TryGetValue(identifier, out var state)) + if (namedState.TryGetValue(identifier, out var state)) { state.Resume(); } @@ -137,13 +109,7 @@ public static void Clear(string identifier) => public static void TryClear(string identifier) { - var states = asyncLocalNamed.Value; - if (states == null) - { - return; - } - - if (states.TryGetValue(identifier, out var state)) + if (namedState.TryGetValue(identifier, out var state)) { state.Clear(); }