-
-
Notifications
You must be signed in to change notification settings - Fork 136
Expand file tree
/
Copy pathTestExecutionGuard.cs
More file actions
56 lines (48 loc) · 1.69 KB
/
Copy pathTestExecutionGuard.cs
File metadata and controls
56 lines (48 loc) · 1.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Collections.Concurrent;
namespace TUnit.Engine.Services.TestExecution;
/// <summary>
/// Prevents duplicate test execution using thread-safe mechanisms.
/// Single Responsibility: Execution deduplication.
/// </summary>
internal sealed class TestExecutionGuard
{
private readonly ConcurrentDictionary<string, TaskCompletionSource<bool>> _executingTests = new();
public ValueTask<bool> TryStartExecutionAsync(string testId, Func<ValueTask> executionFunc)
{
// Fast path: check if test is already executing without allocating a TCS
if (_executingTests.TryGetValue(testId, out var existingTcs))
{
return new ValueTask<bool>(WaitForExistingExecutionAsync(existingTcs));
}
var tcs = new TaskCompletionSource<bool>();
existingTcs = _executingTests.GetOrAdd(testId, tcs);
if (existingTcs != tcs)
{
return new ValueTask<bool>(WaitForExistingExecutionAsync(existingTcs));
}
return ExecuteAndCompleteAsync(testId, tcs, executionFunc);
}
private static async Task<bool> WaitForExistingExecutionAsync(TaskCompletionSource<bool> tcs)
{
await tcs.Task.ConfigureAwait(false);
return false;
}
private async ValueTask<bool> ExecuteAndCompleteAsync(string testId, TaskCompletionSource<bool> tcs, Func<ValueTask> executionFunc)
{
try
{
await executionFunc().ConfigureAwait(false);
tcs.SetResult(true);
return true;
}
catch (Exception ex)
{
tcs.SetException(ex);
throw;
}
finally
{
_executingTests.TryRemove(testId, out _);
}
}
}