Skip to content

Commit 04e542f

Browse files
thomhurstclaude
andauthored
+semver:minor - #2804: Global After/AfterEvery hooks not executing (#2842)
The global After and AfterEvery hooks at Test, Class, and Assembly levels were not being executed after test completion. Only TestSession and TestDiscovery hooks were running. Root cause: TestExecutor.cs was calling OnTestStartingAsync() for Before hooks but missing the corresponding OnTestCompletedAsync() call for After hooks. Changes: - Added OnTestCompletedAsync() call in TestExecutor after test execution - Wrapped in try-finally to ensure RouteTestResult always executes - Added comprehensive test coverage to verify all hook types execute This ensures proper cleanup and resource disposal in test lifecycle. Fixes #2804 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2668f56 commit 04e542f

File tree

121 files changed

+2218
-2523
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+2218
-2523
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,4 @@
9494
<PackageVersion Include="xunit.v3.assert" Version="3.0.0" />
9595
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.0.0" />
9696
</ItemGroup>
97-
</Project>
97+
</Project>

KNOWN_ISSUES.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Known Issues in TUnit
2+
3+
## NotInParallel with Multiple Constraint Keys
4+
5+
**Issue**: Tests with multiple `NotInParallel` constraint keys may run in parallel when they shouldn't.
6+
7+
**Example**:
8+
```csharp
9+
[Test, NotInParallel(["GroupD", "GroupE"])]
10+
public async Task Test1() { }
11+
12+
[Test, NotInParallel(["GroupD", "GroupF"])]
13+
public async Task Test2() { }
14+
```
15+
16+
Test1 and Test2 share "GroupD" and should not run in parallel, but they might.
17+
18+
**Root Cause**:
19+
The current implementation adds tests with multiple keys to separate queues for each key. Each queue is processed independently in parallel. This means:
20+
- GroupD queue will run Test1 and Test2 sequentially
21+
- But GroupE queue (processing Test1) and GroupF queue (processing Test2) may run concurrently
22+
- There's no cross-queue coordination to prevent tests sharing any constraint from overlapping
23+
24+
**Workaround**:
25+
- Use single constraint keys per test
26+
- Or group related tests in the same test class with a class-level `NotInParallel` attribute
27+
28+
**Fix Required**:
29+
The scheduler needs to track running tests across all queues and check for shared constraints before starting any test. This requires significant changes to the scheduling algorithm in `TestScheduler.cs` and `TestGroupingService.cs`.
30+
31+
## Assembly-Level Hooks Affecting Unrelated Tests
32+
33+
**Issue**: Assembly-level hooks (e.g., `[AfterEvery(Assembly)]`) run for ALL tests in the assembly, which can cause unexpected failures when hooks from test-specific scenarios affect other tests.
34+
35+
**Workaround**:
36+
- Avoid using assembly-level hooks in test files that intentionally throw exceptions
37+
- Or add proper filtering in the hooks to only run for specific test namespaces/classes

TUnit.Core.SourceGenerator.Tests/AbstractTests.Concrete1.verified.txt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ internal sealed class ConcreteClass1_AssertClassName_TestSource_GUID : global::T
3232
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3333
{
3434
},
35-
ParameterTypes = new global::System.Type[]
36-
{
37-
},
38-
TestMethodParameterTypes = new string[]
39-
{
40-
},
4135
MethodMetadata = new global::TUnit.Core.MethodMetadata
4236
{
4337
Type = typeof(global::TUnit.TestProject.AbstractTests.AbstractBaseClass),

TUnit.Core.SourceGenerator.Tests/AbstractTests.Concrete2.verified.txt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ internal sealed class ConcreteClass2_SecondTest_TestSource_GUID : global::TUnit.
3333
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3434
{
3535
},
36-
ParameterTypes = new global::System.Type[]
37-
{
38-
},
39-
TestMethodParameterTypes = new string[]
40-
{
41-
},
4236
MethodMetadata = new global::TUnit.Core.MethodMetadata
4337
{
4438
Type = typeof(global::TUnit.TestProject.AbstractTests.ConcreteClass2),
@@ -136,12 +130,6 @@ internal sealed class ConcreteClass2_AssertClassName_TestSource_GUID : global::T
136130
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
137131
{
138132
},
139-
ParameterTypes = new global::System.Type[]
140-
{
141-
},
142-
TestMethodParameterTypes = new string[]
143-
{
144-
},
145133
MethodMetadata = new global::TUnit.Core.MethodMetadata
146134
{
147135
Type = typeof(global::TUnit.TestProject.AbstractTests.AbstractBaseClass),
@@ -237,12 +225,6 @@ internal sealed class ConcreteClass2_SecondTest_TestSource_GUID : global::TUnit.
237225
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
238226
{
239227
},
240-
ParameterTypes = new global::System.Type[]
241-
{
242-
},
243-
TestMethodParameterTypes = new string[]
244-
{
245-
},
246228
MethodMetadata = new global::TUnit.Core.MethodMetadata
247229
{
248230
Type = typeof(global::TUnit.TestProject.AbstractTests.ConcreteClass2),
@@ -339,12 +321,6 @@ internal sealed class ConcreteClass1_AssertClassName_TestSource_GUID : global::T
339321
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
340322
{
341323
},
342-
ParameterTypes = new global::System.Type[]
343-
{
344-
},
345-
TestMethodParameterTypes = new string[]
346-
{
347-
},
348324
MethodMetadata = new global::TUnit.Core.MethodMetadata
349325
{
350326
Type = typeof(global::TUnit.TestProject.AbstractTests.AbstractBaseClass),

TUnit.Core.SourceGenerator.Tests/AfterAllTests.Test.verified.txt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ internal sealed class CleanupTests_Test1_TestSource_GUID : global::TUnit.Core.In
3131
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3232
{
3333
},
34-
ParameterTypes = new global::System.Type[]
35-
{
36-
},
37-
TestMethodParameterTypes = new string[]
38-
{
39-
},
4034
MethodMetadata = new global::TUnit.Core.MethodMetadata
4135
{
4236
Type = typeof(global::TUnit.TestProject.AfterTests.CleanupTests),
@@ -130,12 +124,6 @@ internal sealed class CleanupTests_Test2_TestSource_GUID : global::TUnit.Core.In
130124
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
131125
{
132126
},
133-
ParameterTypes = new global::System.Type[]
134-
{
135-
},
136-
TestMethodParameterTypes = new string[]
137-
{
138-
},
139127
MethodMetadata = new global::TUnit.Core.MethodMetadata
140128
{
141129
Type = typeof(global::TUnit.TestProject.AfterTests.CleanupTests),

TUnit.Core.SourceGenerator.Tests/AfterTests.Test.verified.txt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ internal sealed class CleanupTests_Test1_TestSource_GUID : global::TUnit.Core.In
3131
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3232
{
3333
},
34-
ParameterTypes = new global::System.Type[]
35-
{
36-
},
37-
TestMethodParameterTypes = new string[]
38-
{
39-
},
4034
MethodMetadata = new global::TUnit.Core.MethodMetadata
4135
{
4236
Type = typeof(global::TUnit.TestProject.AfterTests.CleanupTests),
@@ -130,12 +124,6 @@ internal sealed class CleanupTests_Test2_TestSource_GUID : global::TUnit.Core.In
130124
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
131125
{
132126
},
133-
ParameterTypes = new global::System.Type[]
134-
{
135-
},
136-
TestMethodParameterTypes = new string[]
137-
{
138-
},
139127
MethodMetadata = new global::TUnit.Core.MethodMetadata
140128
{
141129
Type = typeof(global::TUnit.TestProject.AfterTests.CleanupTests),

TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ internal sealed class ArgsAsArrayTests_Params_TestSource_GUID : global::TUnit.Co
3333
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3434
{
3535
},
36-
ParameterTypes = new global::System.Type[]
37-
{
38-
typeof(string[]),
39-
},
40-
TestMethodParameterTypes = new string[]
41-
{
42-
"string[]",
43-
},
4436
MethodMetadata = new global::TUnit.Core.MethodMetadata
4537
{
4638
Type = typeof(global::TUnit.TestProject.ArgsAsArrayTests),
@@ -199,14 +191,6 @@ internal sealed class ArgsAsArrayTests_NonParams_TestSource_GUID : global::TUnit
199191
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
200192
{
201193
},
202-
ParameterTypes = new global::System.Type[]
203-
{
204-
typeof(string[]),
205-
},
206-
TestMethodParameterTypes = new string[]
207-
{
208-
"string[]",
209-
},
210194
MethodMetadata = new global::TUnit.Core.MethodMetadata
211195
{
212196
Type = typeof(global::TUnit.TestProject.ArgsAsArrayTests),
@@ -329,14 +313,6 @@ internal sealed class ArgsAsArrayTests_ParamsEnumerable_TestSource_GUID : global
329313
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
330314
{
331315
},
332-
ParameterTypes = new global::System.Type[]
333-
{
334-
typeof(global::System.Collections.Generic.IEnumerable<string>),
335-
},
336-
TestMethodParameterTypes = new string[]
337-
{
338-
"global::System.Collections.Generic.IEnumerable<string>",
339-
},
340316
MethodMetadata = new global::TUnit.Core.MethodMetadata
341317
{
342318
Type = typeof(global::TUnit.TestProject.ArgsAsArrayTests),
@@ -495,14 +471,6 @@ internal sealed class ArgsAsArrayTests_Enumerable_TestSource_GUID : global::TUni
495471
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
496472
{
497473
},
498-
ParameterTypes = new global::System.Type[]
499-
{
500-
typeof(global::System.Collections.Generic.IEnumerable<string>),
501-
},
502-
TestMethodParameterTypes = new string[]
503-
{
504-
"global::System.Collections.Generic.IEnumerable<string>",
505-
},
506474
MethodMetadata = new global::TUnit.Core.MethodMetadata
507475
{
508476
Type = typeof(global::TUnit.TestProject.ArgsAsArrayTests),
@@ -625,16 +593,6 @@ internal sealed class ArgsAsArrayTests_Following_Non_Params_TestSource_GUID : gl
625593
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
626594
{
627595
},
628-
ParameterTypes = new global::System.Type[]
629-
{
630-
typeof(int),
631-
typeof(global::System.Collections.Generic.IEnumerable<string>),
632-
},
633-
TestMethodParameterTypes = new string[]
634-
{
635-
"int",
636-
"global::System.Collections.Generic.IEnumerable<string>",
637-
},
638596
MethodMetadata = new global::TUnit.Core.MethodMetadata
639597
{
640598
Type = typeof(global::TUnit.TestProject.ArgsAsArrayTests),

TUnit.Core.SourceGenerator.Tests/ArgumentWithImplicitConverterTests.Test.verified.txt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,6 @@ internal sealed class ArgumentWithImplicitConverterTests_Explicit_TestSource_GUI
3838
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3939
{
4040
},
41-
ParameterTypes = new global::System.Type[]
42-
{
43-
typeof(global::TUnit.TestProject.ExplicitInteger),
44-
},
45-
TestMethodParameterTypes = new string[]
46-
{
47-
"global::TUnit.TestProject.ExplicitInteger",
48-
},
4941
MethodMetadata = new global::TUnit.Core.MethodMetadata
5042
{
5143
Type = typeof(global::TUnit.TestProject.ArgumentWithImplicitConverterTests),
@@ -173,14 +165,6 @@ internal sealed class ArgumentWithImplicitConverterTests_Implicit_TestSource_GUI
173165
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
174166
{
175167
},
176-
ParameterTypes = new global::System.Type[]
177-
{
178-
typeof(global::TUnit.TestProject.ImplicitInteger),
179-
},
180-
TestMethodParameterTypes = new string[]
181-
{
182-
"global::TUnit.TestProject.ImplicitInteger",
183-
},
184168
MethodMetadata = new global::TUnit.Core.MethodMetadata
185169
{
186170
Type = typeof(global::TUnit.TestProject.ArgumentWithImplicitConverterTests),

TUnit.Core.SourceGenerator.Tests/AssemblyLoaderTests.Test.DotNet8_0.verified.txt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ internal sealed class BasicTests_SynchronousTest_TestSource_GUID : global::TUnit
3131
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3232
{
3333
},
34-
ParameterTypes = new global::System.Type[]
35-
{
36-
},
37-
TestMethodParameterTypes = new string[]
38-
{
39-
},
4034
MethodMetadata = new global::TUnit.Core.MethodMetadata
4135
{
4236
Type = typeof(global::TUnit.TestProject.BasicTests),
@@ -132,12 +126,6 @@ internal sealed class BasicTests_AsynchronousTest_TestSource_GUID : global::TUni
132126
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
133127
{
134128
},
135-
ParameterTypes = new global::System.Type[]
136-
{
137-
},
138-
TestMethodParameterTypes = new string[]
139-
{
140-
},
141129
MethodMetadata = new global::TUnit.Core.MethodMetadata
142130
{
143131
Type = typeof(global::TUnit.TestProject.BasicTests),
@@ -231,12 +219,6 @@ internal sealed class BasicTests_ValueTaskAsynchronousTest_TestSource_GUID : glo
231219
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
232220
{
233221
},
234-
ParameterTypes = new global::System.Type[]
235-
{
236-
},
237-
TestMethodParameterTypes = new string[]
238-
{
239-
},
240222
MethodMetadata = new global::TUnit.Core.MethodMetadata
241223
{
242224
Type = typeof(global::TUnit.TestProject.BasicTests),

TUnit.Core.SourceGenerator.Tests/AssemblyLoaderTests.Test.DotNet9_0.verified.txt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ internal sealed class BasicTests_SynchronousTest_TestSource_GUID : global::TUnit
3131
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
3232
{
3333
},
34-
ParameterTypes = new global::System.Type[]
35-
{
36-
},
37-
TestMethodParameterTypes = new string[]
38-
{
39-
},
4034
MethodMetadata = new global::TUnit.Core.MethodMetadata
4135
{
4236
Type = typeof(global::TUnit.TestProject.BasicTests),
@@ -132,12 +126,6 @@ internal sealed class BasicTests_AsynchronousTest_TestSource_GUID : global::TUni
132126
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
133127
{
134128
},
135-
ParameterTypes = new global::System.Type[]
136-
{
137-
},
138-
TestMethodParameterTypes = new string[]
139-
{
140-
},
141129
MethodMetadata = new global::TUnit.Core.MethodMetadata
142130
{
143131
Type = typeof(global::TUnit.TestProject.BasicTests),
@@ -231,12 +219,6 @@ internal sealed class BasicTests_ValueTaskAsynchronousTest_TestSource_GUID : glo
231219
PropertyInjections = new global::TUnit.Core.PropertyInjectionData[]
232220
{
233221
},
234-
ParameterTypes = new global::System.Type[]
235-
{
236-
},
237-
TestMethodParameterTypes = new string[]
238-
{
239-
},
240222
MethodMetadata = new global::TUnit.Core.MethodMetadata
241223
{
242224
Type = typeof(global::TUnit.TestProject.BasicTests),

0 commit comments

Comments
 (0)