From d678a3e40bb3e3b7ddc47f2cac9dc505a193289d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:06:54 +0100 Subject: [PATCH 1/3] fix: resolve race condition in HookExecutionOrderTests The BeforeEvery(Test) hook in GlobalHookExecutionOrderSetup fires for every test in the assembly. When tests run concurrently, another test's BeforeEvery invocation calls Clear() on the shared list between the Before hook and the test body, causing the test to see count=2 instead of 3. Filter the BeforeEvery hook to only act on HookExecutionOrderTests. --- TUnit.Engine.Tests/HookExecutionOrderTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TUnit.Engine.Tests/HookExecutionOrderTests.cs b/TUnit.Engine.Tests/HookExecutionOrderTests.cs index 65dc6cf0019..553e32d2d86 100644 --- a/TUnit.Engine.Tests/HookExecutionOrderTests.cs +++ b/TUnit.Engine.Tests/HookExecutionOrderTests.cs @@ -8,7 +8,12 @@ public sealed class GlobalHookExecutionOrderSetup [BeforeEvery(Test)] public static void GlobalSetup(TestContext context) { - HookExecutionOrderTests._executionOrder.Clear(); // Clear before each test + if (context.Metadata.TestDetails.ClassType != typeof(HookExecutionOrderTests)) + { + return; + } + + HookExecutionOrderTests._executionOrder.Clear(); HookExecutionOrderTests._executionOrder.Add("BeforeEvery"); } } From 29313686b4305582d375babb7df55167438e5896 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:10:12 +0100 Subject: [PATCH 2/3] add [NotInParallel] to HookExecutionOrderTests Addresses review feedback: the shared static List is not thread-safe, so mark the class as non-parallel to prevent intra-class races if additional test methods are added in the future. --- TUnit.Engine.Tests/HookExecutionOrderTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TUnit.Engine.Tests/HookExecutionOrderTests.cs b/TUnit.Engine.Tests/HookExecutionOrderTests.cs index 553e32d2d86..c35f234b763 100644 --- a/TUnit.Engine.Tests/HookExecutionOrderTests.cs +++ b/TUnit.Engine.Tests/HookExecutionOrderTests.cs @@ -18,6 +18,7 @@ public static void GlobalSetup(TestContext context) } } +[NotInParallel] public class HookExecutionOrderTests { internal static readonly List _executionOrder = []; From cfb2fa80f760a166c90ba2d64c3ec070398a8111 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:12:28 +0100 Subject: [PATCH 3/3] refactor: use per-test StateBag instead of shared static state Replace the shared static List with per-test context StateBag storage. Each test gets its own isolated list, eliminating the race condition at the root rather than working around it with [NotInParallel] and type guards. The type guard in the BeforeEvery hook is still needed (it fires for all tests) but no longer protects shared mutable state. --- TUnit.Engine.Tests/HookExecutionOrderTests.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/TUnit.Engine.Tests/HookExecutionOrderTests.cs b/TUnit.Engine.Tests/HookExecutionOrderTests.cs index c35f234b763..d3d670b805e 100644 --- a/TUnit.Engine.Tests/HookExecutionOrderTests.cs +++ b/TUnit.Engine.Tests/HookExecutionOrderTests.cs @@ -13,31 +13,30 @@ public static void GlobalSetup(TestContext context) return; } - HookExecutionOrderTests._executionOrder.Clear(); - HookExecutionOrderTests._executionOrder.Add("BeforeEvery"); + var order = context.StateBag.GetOrAdd>("executionOrder", _ => []); + order.Add("BeforeEvery"); } } -[NotInParallel] public class HookExecutionOrderTests { - internal static readonly List _executionOrder = []; - [Before(Test)] public void InstanceSetup() { - _executionOrder.Add("Before"); + var order = TestContext.Current!.StateBag.GetOrAdd>("executionOrder", _ => []); + order.Add("Before"); } [Test] public void VerifyExecutionOrder() { - _executionOrder.Add("Test"); + var order = TestContext.Current!.StateBag.GetOrAdd>("executionOrder", _ => []); + order.Add("Test"); // Verify that BeforeEvery runs before Before - _executionOrder.Count.ShouldBe(3); - _executionOrder[0].ShouldBe("BeforeEvery"); - _executionOrder[1].ShouldBe("Before"); - _executionOrder[2].ShouldBe("Test"); + order.Count.ShouldBe(3); + order[0].ShouldBe("BeforeEvery"); + order[1].ShouldBe("Before"); + order[2].ShouldBe("Test"); } }