diff --git a/TUnit.Engine/Scheduling/TestRunner.cs b/TUnit.Engine/Scheduling/TestRunner.cs index c47a89e936..9191170a41 100644 --- a/TUnit.Engine/Scheduling/TestRunner.cs +++ b/TUnit.Engine/Scheduling/TestRunner.cs @@ -119,7 +119,7 @@ private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, Ca } finally { - test.EndTime = DateTimeOffset.UtcNow; + test.EndTime ??= DateTimeOffset.UtcNow; } } diff --git a/TUnit.Engine/Services/TestExecution/RetryHelper.cs b/TUnit.Engine/Services/TestExecution/RetryHelper.cs index 1437dac232..75e6448ac7 100644 --- a/TUnit.Engine/Services/TestExecution/RetryHelper.cs +++ b/TUnit.Engine/Services/TestExecution/RetryHelper.cs @@ -30,6 +30,7 @@ public static async Task ExecuteWithRetry(TestContext testContext, Func ac testContext.Execution.Result = null; testContext.TestStart = null; testContext.Execution.TestEnd = null; + testContext.Timings.Clear(); continue; } diff --git a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs index 5d0a9979cf..cf216b1bd2 100644 --- a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs +++ b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs @@ -78,6 +78,7 @@ private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, Ca test.Context.Execution.Result = null; test.Context.TestStart = null; test.Context.Execution.TestEnd = null; + test.Context.Timings.Clear(); TestContext.Current = test.Context; diff --git a/TUnit.Engine/Services/TestExecution/TestStateManager.cs b/TUnit.Engine/Services/TestExecution/TestStateManager.cs index ee37fb39e0..232842109a 100644 --- a/TUnit.Engine/Services/TestExecution/TestStateManager.cs +++ b/TUnit.Engine/Services/TestExecution/TestStateManager.cs @@ -31,7 +31,7 @@ public void MarkCompleted(AbstractExecutableTest test) }; test.State = test.Result.State; - test.EndTime = now; + test.EndTime ??= now; } public void MarkFailed(AbstractExecutableTest test, Exception exception) @@ -45,7 +45,7 @@ public void MarkFailed(AbstractExecutableTest test, Exception exception) else { test.State = TestState.Failed; - test.EndTime = DateTimeOffset.UtcNow; + test.EndTime ??= DateTimeOffset.UtcNow; test.Result = new TestResult { State = TestState.Failed, diff --git a/TUnit.Engine/TestExecutor.cs b/TUnit.Engine/TestExecutor.cs index dbb80cc3f6..dcb9cf2bc4 100644 --- a/TUnit.Engine/TestExecutor.cs +++ b/TUnit.Engine/TestExecutor.cs @@ -6,6 +6,7 @@ using TUnit.Core.Exceptions; using TUnit.Core.Interfaces; using TUnit.Core.Services; +using TUnit.Engine.Helpers; using TUnit.Engine.Services; namespace TUnit.Engine; @@ -123,7 +124,8 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( executableTest.Context.RestoreExecutionContext(); - await _hookExecutor.ExecuteBeforeTestHooksAsync(executableTest, cancellationToken).ConfigureAwait(false); + await Timings.Record("BeforeTest", executableTest.Context, + () => _hookExecutor.ExecuteBeforeTestHooksAsync(executableTest, cancellationToken)).ConfigureAwait(false); // Late stage test start receivers run after instance-level hooks (default behavior) await _eventReceiverOrchestrator.InvokeTestStartEventReceiversAsync(executableTest.Context, cancellationToken, EventReceiverStage.Late).ConfigureAwait(false); @@ -131,7 +133,14 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( executableTest.Context.RestoreExecutionContext(); // Timeout is now enforced at TestCoordinator level (wrapping entire lifecycle) - await ExecuteTestAsync(executableTest, cancellationToken).ConfigureAwait(false); + try + { + await ExecuteTestAsync(executableTest, cancellationToken).ConfigureAwait(false); + } + finally + { + executableTest.Context.Execution.TestEnd ??= DateTimeOffset.UtcNow; + } executableTest.SetResult(TestState.Passed); } @@ -153,7 +162,11 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( // Early stage test end receivers run before instance-level hooks var earlyStageExceptions = await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(executableTest.Context, CancellationToken.None, EventReceiverStage.Early).ConfigureAwait(false); - var hookExceptions = await _hookExecutor.ExecuteAfterTestHooksAsync(executableTest, CancellationToken.None).ConfigureAwait(false); + IReadOnlyList hookExceptions = []; + await Timings.Record("AfterTest", executableTest.Context, (Func)(async () => + { + hookExceptions = await _hookExecutor.ExecuteAfterTestHooksAsync(executableTest, CancellationToken.None).ConfigureAwait(false); + })).ConfigureAwait(false); // Late stage test end receivers run after instance-level hooks (default behavior) var lateStageExceptions = await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(executableTest.Context, CancellationToken.None, EventReceiverStage.Late).ConfigureAwait(false);