diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index 6b83f8456e..216d2169fd 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -23,8 +23,8 @@ internal sealed class EventReceiverOrchestrator : IDisposable private ThreadSafeDictionary _firstTestInSessionTasks = new(); // Track remaining test counts for "last" events - private readonly ThreadSafeDictionary _assemblyTestCounts = new(); - private readonly ThreadSafeDictionary _classTestCounts = new(); + private readonly ConcurrentDictionary _assemblyTestCounts = new(); + private readonly ConcurrentDictionary _classTestCounts = new(); private int _sessionTestCount; // Track which objects have already been initialized to avoid duplicates @@ -79,15 +79,15 @@ obj is IFirstTestInAssemblyEventReceiver || // Fast-path checks with inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) + public ValueTask InvokeTestStartEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) { // Fast path - no allocation if no receivers if (!_registry.HasTestStartReceivers()) { - return; + return ValueTask.CompletedTask; } - await InvokeTestStartEventReceiversCore(context, cancellationToken, stage); + return InvokeTestStartEventReceiversCore(context, cancellationToken, stage); } private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage) @@ -127,17 +127,17 @@ private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, C } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask> InvokeTestEndEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) + public ValueTask> InvokeTestEndEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) { if (!_registry.HasTestEndReceivers()) { - return []; + return new ValueTask>([]); } - return await InvokeTestEndEventReceiversCore(context, cancellationToken, stage); + return InvokeTestEndEventReceiversCore(context, cancellationToken, stage); } - private async ValueTask> InvokeTestEndEventReceiversCore(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage) + private async ValueTask> InvokeTestEndEventReceiversCore(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage) { // Defer exception list allocation until actually needed List? exceptions = null; @@ -211,18 +211,18 @@ private async ValueTask> InvokeTestEndEventReceiversCore(TestCon } #endif - return exceptions ?? []; + return exceptions == null ? Array.Empty() : exceptions; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeTestSkippedEventReceiversAsync(TestContext context, CancellationToken cancellationToken) + public ValueTask InvokeTestSkippedEventReceiversAsync(TestContext context, CancellationToken cancellationToken) { if (!_registry.HasTestSkippedReceivers()) { - return; + return ValueTask.CompletedTask; } - await InvokeTestSkippedEventReceiversCore(context, cancellationToken); + return InvokeTestSkippedEventReceiversCore(context, cancellationToken); } private async ValueTask InvokeTestSkippedEventReceiversCore(TestContext context, CancellationToken cancellationToken) @@ -272,7 +272,6 @@ public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredC } } - // First/Last event methods with fast-path checks [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask InvokeFirstTestInSessionEventReceiversAsync( @@ -365,20 +364,22 @@ private async Task InvokeFirstTestInClassEventReceiversCoreAsync( // Last event methods [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeLastTestInSessionEventReceiversAsync( + public ValueTask InvokeLastTestInSessionEventReceiversAsync( TestContext context, TestSessionContext sessionContext, CancellationToken cancellationToken) { if (!_registry.HasLastTestInSessionReceivers()) { - return; + return ValueTask.CompletedTask; } if (Interlocked.Decrement(ref _sessionTestCount) == 0) { - await InvokeLastTestInSessionEventReceiversCore(context, sessionContext, cancellationToken); + return InvokeLastTestInSessionEventReceiversCore(context, sessionContext, cancellationToken); } + + return ValueTask.CompletedTask; } private async ValueTask InvokeLastTestInSessionEventReceiversCore( @@ -402,14 +403,14 @@ private async ValueTask InvokeLastTestInSessionEventReceiversCore( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeLastTestInAssemblyEventReceiversAsync( + public ValueTask InvokeLastTestInAssemblyEventReceiversAsync( TestContext context, AssemblyHookContext assemblyContext, CancellationToken cancellationToken) { if (!_registry.HasLastTestInAssemblyReceivers()) { - return; + return ValueTask.CompletedTask; } var assemblyName = assemblyContext.Assembly.GetName().FullName ?? ""; @@ -418,8 +419,10 @@ public async ValueTask InvokeLastTestInAssemblyEventReceiversAsync( if (assemblyCount == 0) { - await InvokeLastTestInAssemblyEventReceiversCore(context, assemblyContext, cancellationToken); + return InvokeLastTestInAssemblyEventReceiversCore(context, assemblyContext, cancellationToken); } + + return ValueTask.CompletedTask; } private async ValueTask InvokeLastTestInAssemblyEventReceiversCore( @@ -443,14 +446,14 @@ private async ValueTask InvokeLastTestInAssemblyEventReceiversCore( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeLastTestInClassEventReceiversAsync( + public ValueTask InvokeLastTestInClassEventReceiversAsync( TestContext context, ClassHookContext classContext, CancellationToken cancellationToken) { if (!_registry.HasLastTestInClassReceivers()) { - return; + return ValueTask.CompletedTask; } var classType = classContext.ClassType; @@ -459,8 +462,10 @@ public async ValueTask InvokeLastTestInClassEventReceiversAsync( if (classCount == 0) { - await InvokeLastTestInClassEventReceiversCore(context, classContext, cancellationToken); + return InvokeLastTestInClassEventReceiversCore(context, classContext, cancellationToken); } + + return ValueTask.CompletedTask; } private async ValueTask InvokeLastTestInClassEventReceiversCore( diff --git a/TUnit.Engine/Services/HookExecutor.cs b/TUnit.Engine/Services/HookExecutor.cs index e971037544..7f31691970 100644 --- a/TUnit.Engine/Services/HookExecutor.cs +++ b/TUnit.Engine/Services/HookExecutor.cs @@ -292,7 +292,7 @@ public async ValueTask ExecuteBeforeTestHooksAsync(AbstractExecutableTest test, } } - public async ValueTask> ExecuteAfterTestHooksAsync(AbstractExecutableTest test, CancellationToken cancellationToken) + public async ValueTask> ExecuteAfterTestHooksAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { // Defer exception list allocation until actually needed List? exceptions = null; @@ -338,7 +338,7 @@ public async ValueTask> ExecuteAfterTestHooksAsync(AbstractExecu } } - return exceptions ?? []; + return exceptions == null ? Array.Empty() : exceptions; } public async ValueTask ExecuteBeforeTestDiscoveryHooksAsync(CancellationToken cancellationToken)