Skip to content

Commit 4152d4c

Browse files
committed
fix(core): preserve cancellation across retries
1 parent f95a1ea commit 4152d4c

2 files changed

Lines changed: 64 additions & 14 deletions

File tree

src/TUnit.Core/TestContext.Execution.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,27 @@ internal void InitializeTestCancellation(CancellationToken cancellationToken)
167167
{
168168
lock (Lock)
169169
{
170+
// Cancel() targets the whole execution, so retries inherit an accepted request.
171+
var preserveCancellationRequest = CurrentRetryAttempt > 0 && _testCancellationRequested;
172+
170173
if (_testCancellationTokenSource is { } previousTestCancellationTokenSource)
171174
{
172175
(_retiredTestCancellationTokenSources ??= []).Add(previousTestCancellationTokenSource);
173176
}
174177

175-
_testCancellationTokenSource = cancellationToken.CanBeCanceled
178+
var testCancellationTokenSource = cancellationToken.CanBeCanceled
176179
? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
177180
: new CancellationTokenSource();
181+
182+
if (preserveCancellationRequest)
183+
{
184+
testCancellationTokenSource.Cancel();
185+
}
186+
187+
_testCancellationTokenSource = testCancellationTokenSource;
178188
_acceptingTestCancellation = true;
179-
_testCancellationRequested = false;
180-
_baseCancellationToken = _testCancellationTokenSource.Token;
189+
_testCancellationRequested = preserveCancellationRequest;
190+
_baseCancellationToken = testCancellationTokenSource.Token;
181191
RebuildLinkedCancellationTokenSource();
182192
}
183193
}

tests/TUnit.UnitTests/TestContextCancellationTests.cs

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@ public class TestContextCancellationTests
77
[Test]
88
public async Task CleanupDefersSourceDisposalWhileAcceptedCancellationIsInProgress()
99
{
10-
var currentContext = TestContext.Current!;
11-
var context = new TestContext(
12-
nameof(CleanupDefersSourceDisposalWhileAcceptedCancellationIsInProgress),
13-
currentContext.ServiceProvider,
14-
currentContext.ClassContext,
15-
new TestBuilderContext
16-
{
17-
TestMetadata = currentContext.TestDetails.MethodMetadata
18-
},
19-
CancellationToken.None);
20-
10+
var context = CreateContext();
2111
context.InitializeTestCancellation(CancellationToken.None);
2212

2313
var cancellationToken = context.TestCancellationToken;
@@ -47,4 +37,54 @@ public async Task CleanupDefersSourceDisposalWhileAcceptedCancellationIsInProgre
4737
context.RemoveFromRegistry();
4838
}
4939
}
40+
41+
[Test]
42+
public async Task RetrySourcePreservesAcceptedCancellation()
43+
{
44+
var context = CreateContext();
45+
context.InitializeTestCancellation(CancellationToken.None);
46+
47+
var callbackStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
48+
using var releaseCallback = new ManualResetEventSlim();
49+
using var registration = context.TestCancellationToken.Register(() =>
50+
{
51+
callbackStarted.TrySetResult();
52+
releaseCallback.Wait(TimeSpan.FromSeconds(10));
53+
});
54+
55+
var cancellationTask = Task.Run(context.Cancel);
56+
57+
try
58+
{
59+
await callbackStarted.Task.WaitAsync(TimeSpan.FromSeconds(10));
60+
61+
context.CurrentRetryAttempt = 1;
62+
context.InitializeTestCancellation(CancellationToken.None);
63+
64+
await Assert.That(context.IsTestCancellationRequested).IsTrue();
65+
await Assert.That(context.TestCancellationToken.IsCancellationRequested).IsTrue();
66+
}
67+
finally
68+
{
69+
releaseCallback.Set();
70+
await cancellationTask;
71+
context.DisposeLinkedCancellationTokenSources();
72+
context.RemoveFromRegistry();
73+
}
74+
}
75+
76+
private static TestContext CreateContext()
77+
{
78+
var currentContext = TestContext.Current!;
79+
80+
return new TestContext(
81+
nameof(TestContextCancellationTests),
82+
currentContext.ServiceProvider,
83+
currentContext.ClassContext,
84+
new TestBuilderContext
85+
{
86+
TestMetadata = currentContext.TestDetails.MethodMetadata
87+
},
88+
CancellationToken.None);
89+
}
5090
}

0 commit comments

Comments
 (0)