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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions docs/recording.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public Task Usage()
return Verify("TheValue");
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L84-L94' title='Snippet source file'>snippet source</a> | <a href='#snippet-Recording' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L111-L121' title='Snippet source file'>snippet source</a> | <a href='#snippet-Recording' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -61,7 +61,7 @@ public Task TryAdd()
return Verify("TheValue");
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L120-L132' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingTryAdd' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L147-L159' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingTryAdd' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -85,7 +85,7 @@ public Task RecordingScoped()
return Verify();
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L143-L158' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingScoped' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L170-L185' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingScoped' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -117,7 +117,7 @@ public Task SameKey()
return Verify("TheValue");
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L342-L353' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingSameKey' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L369-L380' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingSameKey' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -156,7 +156,7 @@ public Task Identifier()
return Verify(Recording.Stop("identifier"));
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L160-L170' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingIdentifier' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L187-L197' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingIdentifier' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -188,7 +188,7 @@ public Task Case()
return Verify("TheValue");
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L364-L375' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingIgnoreCase' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L391-L402' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingIgnoreCase' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -223,7 +223,7 @@ public Task Stop()
return Verify(appends.Where(_ => _.Name != "name1"));
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L202-L214' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingStop' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L229-L241' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingStop' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -255,7 +255,7 @@ public Task StopNotInResult()
return Verify("other data");
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L216-L228' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingStopNotInResult' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L243-L255' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingStopNotInResult' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -284,7 +284,7 @@ public void IsRecording()
Assert.True(Recording.IsRecording());
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L172-L182' title='Snippet source file'>snippet source</a> | <a href='#snippet-IsRecording' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L199-L209' title='Snippet source file'>snippet source</a> | <a href='#snippet-IsRecording' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

This can be helpful if the cost of capturing data, to add to recording, is high.
Expand All @@ -307,7 +307,7 @@ public Task Clear()
return Verify();
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L262-L274' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingClear' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L289-L301' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingClear' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down Expand Up @@ -343,7 +343,7 @@ public Task PauseResume()
return Verify();
}
```
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L286-L301' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingPauseResume' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/RecordingTests.cs#L313-L328' title='Snippet source file'>snippet source</a> | <a href='#snippet-RecordingPauseResume' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Results in:
Expand Down
27 changes: 27 additions & 0 deletions src/Verify.Tests/RecordingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,33 @@ public async Task StoppedInChildContextIsNotRecording()
Assert.False(Recording.IsRecording());
}

// The engine consumes the recording in a child context, so the null does not flow back
// and the test's own context still holds the stopped state. Starting again after that
// is the supported pattern, not a double start.
[Fact]
public async Task StartAfterConsumedInChildContext()
{
Recording.Start();
Recording.Add("name", "value1");
await Task.Run(() => Recording.TryStop(out _));

Recording.Start();
Recording.Add("name", "value2");

Assert.True(Recording.TryStop(out var recorded));
Assert.Equal(["value2"], recorded.Select(_ => _.Data));
}

[Fact]
public void StartWhileRecordingStillThrows()
{
using (Recording.Start())
{
var exception = Assert.Throws<Exception>(Recording.Start);
Assert.Equal("Recording already started", exception.Message);
}
}

[Fact]
public void DisposeAfterStopDoesNotThrow()
{
Expand Down
8 changes: 5 additions & 3 deletions src/Verify/Recording/Recording.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ public static bool TryStop([NotNullWhen(true)] out IReadOnlyCollection<ToAppend>
// Snapshot the items, then stop the shared State so the stop is observable
// through the caller's reference.
recorded = value.Items.ToList();
value.Clear();
value.Pause();
value.Stop();
asyncLocal.Value = null;
return true;
}
Expand Down Expand Up @@ -125,7 +124,10 @@ public static IDisposable Start()
{
var value = asyncLocal.Value;

if (value != null)
// A stopped state is one the caller's context is still holding after the recording
// was consumed, for example by a Verify. That is finished, so starting again is
// valid: only a live recording is a double start.
if (value is {Stopped: false})
{
throw new("Recording already started");
}
Expand Down
14 changes: 14 additions & 0 deletions src/Verify/Recording/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ public void Add(string name, object item)
items.Enqueue(append);
}

/// <summary>
/// Set once the recording has been consumed. Nulling the AsyncLocal does not flow back
/// to the caller when the engine stops a recording from inside the verification, so the
/// stop has to be observable through the caller's own reference to this state.
/// </summary>
public bool Stopped { get; private set; }

public void Stop()
{
Clear();
Pause();
Stopped = true;
}

public void Pause() =>
Paused = true;

Expand Down
Loading