Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,15 +1038,13 @@ private async Task InvokeTestRegisteredEventReceiversAsync(TestContext context)
#if NET6_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Scoped attribute filtering uses Type.GetInterfaces and reflection")]
#endif
private async Task InvokeDiscoveryEventReceiversAsync(TestContext context)
private Task InvokeDiscoveryEventReceiversAsync(TestContext context)
{
var discoveredContext = new DiscoveredTestContext(
context.Metadata.TestDetails.TestName,
context);

{
await _eventReceiverOrchestrator.InvokeTestDiscoveryEventReceiversAsync(context, discoveredContext, CancellationToken.None);
}
return _eventReceiverOrchestrator.InvokeTestDiscoveryEventReceiversAsync(context, discoveredContext, CancellationToken.None);
}

private async Task<AbstractExecutableTest> CreateFailedTestForDataGenerationError(TestMetadata metadata, Exception exception)
Expand Down
44 changes: 22 additions & 22 deletions TUnit.Engine/Events/EventReceiverRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ private enum EventTypes
LastTestInClass = 1 << 9,
All = ~0
}

private volatile EventTypes _registeredEvents = EventTypes.None;
private readonly Dictionary<Type, object[]> _receiversByType = new();
private readonly Dictionary<Type, Array> _cachedTypedReceivers = new();
private readonly ReaderWriterLockSlim _lock = new();

/// <summary>
/// Register event receivers from a collection of objects
/// </summary>
public void RegisterReceivers(IEnumerable<object> objects)
public void RegisterReceivers(ReadOnlySpan<object> objects)
{
_lock.EnterWriteLock();
try
Expand All @@ -47,7 +47,7 @@ public void RegisterReceivers(IEnumerable<object> objects)
_lock.ExitWriteLock();
}
}

/// <summary>
/// Register a single event receiver
/// </summary>
Expand All @@ -63,7 +63,7 @@ public void RegisterReceiver(object receiver)
_lock.ExitWriteLock();
}
}

private void RegisterReceiverInternal(object receiver)
{
UpdateEventFlags(receiver);
Expand All @@ -83,7 +83,7 @@ private void RegisterReceiverInternal(object receiver)

_cachedTypedReceivers.Clear();
}

private void RegisterIfImplements<T>(object receiver) where T : class
{
if (receiver is T)
Expand All @@ -102,43 +102,43 @@ private void RegisterIfImplements<T>(object receiver) where T : class
}
}
}

/// <summary>
/// Fast check if any receivers registered for event type
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasTestStartReceivers() => (_registeredEvents & EventTypes.TestStart) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasTestEndReceivers() => (_registeredEvents & EventTypes.TestEnd) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasTestSkippedReceivers() => (_registeredEvents & EventTypes.TestSkipped) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasTestRegisteredReceivers() => (_registeredEvents & EventTypes.TestRegistered) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasFirstTestInSessionReceivers() => (_registeredEvents & EventTypes.FirstTestInSession) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasLastTestInSessionReceivers() => (_registeredEvents & EventTypes.LastTestInSession) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasFirstTestInAssemblyReceivers() => (_registeredEvents & EventTypes.FirstTestInAssembly) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasLastTestInAssemblyReceivers() => (_registeredEvents & EventTypes.LastTestInAssembly) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasFirstTestInClassReceivers() => (_registeredEvents & EventTypes.FirstTestInClass) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasLastTestInClassReceivers() => (_registeredEvents & EventTypes.LastTestInClass) != 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasAnyReceivers() => _registeredEvents != EventTypes.None;

public T[] GetReceiversOfType<T>() where T : class
{
var typeKey = typeof(T);
Expand Down Expand Up @@ -202,7 +202,7 @@ public T[] GetReceiversOfType<T>() where T : class
_lock.ExitUpgradeableReadLock();
}
}

private void UpdateEventFlags(object receiver)
{
if (receiver is ITestStartEventReceiver)
Expand Down Expand Up @@ -246,10 +246,10 @@ private void UpdateEventFlags(object receiver)
_registeredEvents |= EventTypes.LastTestInClass;
}
}


public void Dispose()
{
_lock?.Dispose();
}
}
}
42 changes: 36 additions & 6 deletions TUnit.Engine/Services/EventReceiverOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public EventReceiverOrchestrator(TUnitFrameworkLogger logger)

public void RegisterReceivers(TestContext context, CancellationToken cancellationToken)
{
List<object>? objectsToRegister = null;
var vlb = new ValueListBuilder<object>([null, null, null, null]);

foreach (var obj in context.GetEligibleEventObjects())
{
Expand All @@ -66,14 +66,14 @@ obj is IFirstTestInAssemblyEventReceiver ||
}

// Defer list allocation until actually needed
objectsToRegister ??= [];
objectsToRegister.Add(obj);
vlb.Append(obj);
}

if (objectsToRegister is { Count: > 0 })
if (vlb.Length > 0)
{
_registry.RegisterReceivers(objectsToRegister);
_registry.RegisterReceivers(vlb.AsSpan());
}
vlb.Dispose();
}


Expand Down Expand Up @@ -133,6 +133,26 @@ public ValueTask<IReadOnlyList<Exception>> InvokeTestEndEventReceiversAsync(Test
{
return new ValueTask<IReadOnlyList<Exception>>([]);
}
#if NET
if (stage.HasValue)
{
var receivers = context.GetTestEndReceivers(stage.Value);
if (receivers.Length == 0)
{
return new ValueTask<IReadOnlyList<Exception>>([]);
}
}
else
{
var earlyReceivers = context.GetTestEndReceivers(EventReceiverStage.Early);
var lateReceivers = context.GetTestEndReceivers(EventReceiverStage.Late);

if (earlyReceivers.Length == 0 && lateReceivers.Length == 0)
{
return new ValueTask<IReadOnlyList<Exception>>([]);
}
}
#endif

return InvokeTestEndEventReceiversCore(context, cancellationToken, stage);
}
Expand Down Expand Up @@ -241,11 +261,21 @@ private async ValueTask InvokeTestSkippedEventReceiversCore(TestContext context,
}
}

public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext context, DiscoveredTestContext discoveredContext, CancellationToken cancellationToken)
public Task InvokeTestDiscoveryEventReceiversAsync(TestContext context, DiscoveredTestContext discoveredContext, CancellationToken cancellationToken)
{
// Use pre-computed receivers (already filtered, sorted, and scoped-attribute filtered)
var receivers = context.GetTestDiscoveryReceivers();

if(receivers.Length == 0)
{
return Task.CompletedTask;
}

return InvokeTestDiscoveryEventReceiversCoreAsync(receivers, discoveredContext);
}

private static async Task InvokeTestDiscoveryEventReceiversCoreAsync(ITestDiscoveryEventReceiver[] receivers, DiscoveredTestContext discoveredContext)
{
foreach (var receiver in receivers)
{
await receiver.OnTestDiscovered(discoveredContext);
Expand Down
Loading