feat: intelligent test failure diagnosis and categorization#4953
feat: intelligent test failure diagnosis and categorization#4953
Conversation
Code ReviewTwo confirmed bugs were found, both rooted in the same architectural issue. Root Cause: The Categorizer Result Is Not Used for RoutingThe PR introduces TUnit/TUnit.Engine/TUnitMessageBus.cs Lines 144 to 162 in fe1839d Bug 1: AggregateException Unwrapping Mismatch
TUnit/TUnit.Engine/Services/FailureCategorizer.cs Lines 17 to 21 in fe1839d But TUnit/TUnit.Engine/TUnitMessageBus.cs Lines 149 to 153 in fe1839d Concrete failure scenario:
Bug 2: Assertion Detection MismatchThe categorizer matches exception type names containing TUnit/TUnit.Engine/Services/FailureCategorizer.cs Lines 43 to 47 in fe1839d But TUnit/TUnit.Engine/TUnitMessageBus.cs Lines 155 to 160 in fe1839d Concrete failure scenario: Exception type
Recommended FixUse the private static TestNodeStateProperty GetFailureStateProperty(TestContext testContext, Exception e, TimeSpan duration)
{
var category = FailureCategorizer.Categorize(e);
var categoryLabel = FailureCategorizer.GetLabel(category);
if (category == FailureCategory.Timeout
&& testContext.Metadata.TestDetails.Timeout != null
&& duration >= testContext.Metadata.TestDetails.Timeout.Value)
{
return new TimeoutTestNodeStateProperty($"[{categoryLabel}] Test timed out after {testContext.Metadata.TestDetails.Timeout.Value.TotalMilliseconds}ms");
}
if (category == FailureCategory.Assertion)
{
return new FailedTestNodeStateProperty(e, $"[{categoryLabel}] {e.Message}");
}
return new ErrorTestNodeStateProperty(e, $"[{categoryLabel}] {e.Message}");
}This approach is more maintainable and scalable: adding a new category in the future only requires updating |
e8c163c to
5117f1e
Compare
5117f1e to
7f10b23
Compare
Automatically categorize test failures to help users quickly understand what went wrong. Categories include: Assertion, Timeout, NullReference, Setup (Before hooks), Teardown (After hooks), Infrastructure (IO/network), and Unknown. The category label is prefixed in the failure explanation visible in test reports.
…n at top of GetFailureStateProperty The method was computing a category via FailureCategorizer but then ignoring it for routing, using separate type-checking logic against the original (potentially wrapped) exception. This caused two bugs: 1. AggregateException wrapping e.g. TaskCanceledException would be correctly categorized as Timeout by the categorizer, but the method's own `e is TaskCanceledException` check would fail since `e` was still the AggregateException. 2. The assertion check used a different string comparison (InvariantCulture) than the categorizer (Ordinal) and only checked for "Assertion" rather than also checking "Assert". Now AggregateException is unwrapped once at the top, the unwrapped exception is passed to the categorizer, and the FailureCategory enum drives all routing and is used for constructing state properties.
7f10b23 to
0c877fa
Compare
Closes #4895. Adds FailureCategorizer with 7 failure categories.