diff --git a/tests/TUnit.Engine.Tests/InvokableTestBase.cs b/tests/TUnit.Engine.Tests/InvokableTestBase.cs index f3da6e32554..b439d608c1e 100644 --- a/tests/TUnit.Engine.Tests/InvokableTestBase.cs +++ b/tests/TUnit.Engine.Tests/InvokableTestBase.cs @@ -99,7 +99,10 @@ private async Task RunWithAot(string filter, List> assertions, "--report-trx", "--report-trx-filename", trxFilename, "--diagnostic-verbosity", "Debug", "--diagnostic", "--diagnostic-file-prefix", $"log_{GetType().Name}_AOT_", - "--timeout", "5m", + // A whole-session budget, not a hang detector. The [EngineTest=Pass] run takes + // 3.5-5 minutes on Windows CI, so a 5m budget cancelled healthy runs mid-way + // through the sequential global [NotInParallel] tail. + "--timeout", "10m", ..runOptions.AdditionalArguments ] ) diff --git a/tests/TUnit.TestProject/Bugs/6885/Tests.cs b/tests/TUnit.TestProject/Bugs/6885/Tests.cs index 8241d2e851d..29e47403fe4 100644 --- a/tests/TUnit.TestProject/Bugs/6885/Tests.cs +++ b/tests/TUnit.TestProject/Bugs/6885/Tests.cs @@ -24,6 +24,8 @@ public class DedicatedThreadExecutorCleanUpOrderTests private static async Task AssertNoOverlapAsync() { + SlowCleanUpExecutor.MarkTestBodyRunning(); + var cleanUpRunningAtStart = SlowCleanUpExecutor.CleanUpRunning; // Longer than CleanUp(), so a previous test's late CleanUp() would reset the state mid-test. @@ -40,15 +42,28 @@ private static async Task AssertNoOverlapAsync() /// /// Sets global state in Initialize() and resets it in a slow CleanUp(). /// +/// +/// The executor also runs every Before/After(Test) hook, including the project's many global +/// [BeforeEvery]/[AfterEvery] hooks, each on its own dedicated thread. Only the test body's +/// CleanUp() is slow: sleeping after every hook made each of these [NotInParallel] tests take +/// about 16 seconds, which pushed the [EngineTest=Pass] run past its time budget. +/// public sealed class SlowCleanUpExecutor : DedicatedThreadExecutor { private static volatile bool s_cleanUpRunning; private static volatile string s_state = "reset"; + // The test body runs on the executor's dedicated thread (its continuations are pumped there), + // so a thread-static flag tells CleanUp() whether this thread ran a test body or a hook. + [ThreadStatic] + private static bool t_ranTestBody; + public static bool CleanUpRunning => s_cleanUpRunning; public static string State => s_state; + public static void MarkTestBodyRunning() => t_ranTestBody = true; + protected override void Initialize() { s_state = "configured"; @@ -56,6 +71,11 @@ protected override void Initialize() protected override void CleanUp() { + if (!t_ranTestBody) + { + return; + } + s_cleanUpRunning = true; Thread.Sleep(300); s_state = "reset";