-
-
Notifications
You must be signed in to change notification settings - Fork 136
Fix timeout cancellation diagnostics #6715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
thomhurst
merged 5 commits into
main
from
fix/6688-preserve-timeout-cancellation-details
Sep 4, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3eac2ba
fix: preserve timeout cancellation details
thomhurst 9c2f65f
fix: address timeout review feedback
thomhurst efcad91
refactor: drop unused timeout constructor
thomhurst 99dc836
fix: preserve custom task cancellation details
thomhurst 0d5e5ba
fix: report custom task cancellations
thomhurst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| using Shouldly; | ||
| using TUnit.Engine.Tests.Enums; | ||
|
|
||
| namespace TUnit.Engine.Tests; | ||
|
|
||
| public class Issue6688Tests(TestMode testMode) : InvokableTestBase(testMode) | ||
| { | ||
| [Test] | ||
| public async Task Timeout_Preserves_Custom_Cancellation_Message() | ||
| { | ||
| await RunTestsWithFilter( | ||
| "/*/*/TimeoutCancellationExceptionTests/Custom_Cancellation_Message", | ||
| [ | ||
| result => result.ResultSummary.Outcome.ShouldBe("Failed"), | ||
| result => result.ResultSummary.Counters.Timeout.ShouldBe(1), | ||
| result => result.Results.Single().Output?.ErrorInfo?.Message.ShouldContain("Failed due to XYZ"), | ||
| ], | ||
| new RunOptions().WithArgument("--detailed-stacktrace")); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Timeout_Preserves_Custom_Non_Cancellation_Exception_Message() | ||
| { | ||
| await RunTestsWithFilter( | ||
| "/*/*/TimeoutCancellationExceptionTests/Custom_Non_Cancellation_Exception_Message", | ||
| [ | ||
| result => result.ResultSummary.Outcome.ShouldBe("Failed"), | ||
| result => result.ResultSummary.Counters.Timeout.ShouldBe(1), | ||
| result => result.Results.Single().Output?.ErrorInfo?.Message.ShouldContain("Custom non-cancellation diagnostic"), | ||
| ], | ||
| new RunOptions().WithArgument("--detailed-stacktrace")); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Timeout_Preserves_Custom_Task_Cancellation_Message() | ||
| { | ||
| await RunTestsWithFilter( | ||
| "/*/*/TimeoutCancellationExceptionTests/Custom_Task_Cancellation_Message", | ||
| [ | ||
| result => result.ResultSummary.Outcome.ShouldBe("Failed"), | ||
| result => result.ResultSummary.Counters.Timeout.ShouldBe(1), | ||
| result => result.Results.Single().Output?.ErrorInfo?.Message.ShouldContain("Custom task cancellation diagnostic"), | ||
| ], | ||
| new RunOptions().WithArgument("--detailed-stacktrace")); | ||
| } | ||
| } |
58 changes: 58 additions & 0 deletions
58
tests/TUnit.TestProject/Bugs/_6688/TimeoutCancellationExceptionTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| using TUnit.TestProject.Attributes; | ||
|
|
||
| namespace TUnit.TestProject.Bugs._6688; | ||
|
|
||
| public class TimeoutCancellationExceptionTests | ||
| { | ||
| [Test] | ||
| [Timeout(50)] | ||
| [EngineTest(ExpectedResult.Failure)] | ||
| public async Task Custom_Cancellation_Message(CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException ex) | ||
| { | ||
| // Simulate Aspire gathering resource diagnostics after cancellation. | ||
| await Task.Delay(50); | ||
| throw new OperationCanceledException("Failed due to XYZ", ex.CancellationToken); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Timeout(50)] | ||
| [EngineTest(ExpectedResult.Failure)] | ||
| public async Task Custom_Non_Cancellation_Exception_Message(CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| await Task.Delay(50); | ||
| throw new InvalidOperationException("Custom non-cancellation diagnostic"); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| [Timeout(50)] | ||
| [EngineTest(ExpectedResult.Failure)] | ||
| public async Task Custom_Task_Cancellation_Message(CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException ex) | ||
| { | ||
| await Task.Delay(50); | ||
| throw new TaskCanceledException( | ||
| "Custom task cancellation diagnostic", | ||
| new InvalidOperationException("Inner diagnostic"), | ||
| ex.CancellationToken); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| using TUnit.Engine.Helpers; | ||
|
|
||
| namespace TUnit.UnitTests; | ||
|
|
||
| public class TimeoutHelperTests | ||
| { | ||
| [Test] | ||
| public async Task Timeout_Preserves_Exception_Thrown_During_Cancellation() | ||
| { | ||
| const string cancellationMessage = "Failed due to XYZ"; | ||
|
|
||
| var exception = await Assert.That(async () => | ||
| await TimeoutHelper.ExecuteWithTimeoutAsync( | ||
| async cancellationToken => | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException ex) | ||
| { | ||
| // Keep execution incomplete long enough for timeout detection to win before | ||
| // cancellation diagnostics finish, matching the Aspire failure in #6688. | ||
| await Task.Delay(50); | ||
| throw new OperationCanceledException(cancellationMessage, ex.CancellationToken); | ||
| } | ||
| }, | ||
| TimeSpan.FromMilliseconds(50), | ||
| CancellationToken.None)) | ||
| .ThrowsExactly<TimeoutException>(); | ||
|
|
||
| await Assert.That(exception!.Message).Contains(cancellationMessage); | ||
| await Assert.That(exception.InnerException).IsTypeOf<OperationCanceledException>(); | ||
| await Assert.That(exception.InnerException!.Message).IsEqualTo(cancellationMessage); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Timeout_Does_Not_Preserve_Routine_Operation_Cancellation() | ||
| { | ||
| var exception = await Assert.That(async () => | ||
| await TimeoutHelper.ExecuteWithTimeoutAsync( | ||
| async cancellationToken => | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| } | ||
| }, | ||
| TimeSpan.FromMilliseconds(50), | ||
| CancellationToken.None)) | ||
| .ThrowsExactly<TimeoutException>(); | ||
|
|
||
| await Assert.That(exception!.InnerException).IsNull(); | ||
| await Assert.That(exception.Message).DoesNotContain(nameof(OperationCanceledException)); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Timeout_Preserves_Custom_Task_Cancellation() | ||
| { | ||
| const string cancellationMessage = "Custom task cancellation diagnostic"; | ||
| var diagnosticException = new InvalidOperationException("Inner diagnostic"); | ||
|
|
||
| var exception = await Assert.That(async () => | ||
| await TimeoutHelper.ExecuteWithTimeoutAsync( | ||
| async cancellationToken => | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| await Task.Delay(50); | ||
| throw new TaskCanceledException(cancellationMessage, diagnosticException, cancellationToken); | ||
| } | ||
| }, | ||
| TimeSpan.FromMilliseconds(50), | ||
| CancellationToken.None)) | ||
| .ThrowsExactly<TimeoutException>(); | ||
|
|
||
| var taskCanceledException = await Assert.That(exception!.InnerException).IsTypeOf<TaskCanceledException>(); | ||
| await Assert.That(taskCanceledException!.Message).IsEqualTo(cancellationMessage); | ||
| await Assert.That(taskCanceledException.InnerException).IsSameReferenceAs(diagnosticException); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.