From 749a2e8cfffa7b060d2358708a8e507fc9f180a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:35:19 +0000 Subject: [PATCH 1/8] Initial plan From 038d07dec4d114127be88f7a61dd5829f5c2ec74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:07:44 +0000 Subject: [PATCH 2/8] Implement hook timeout functionality with 5-minute default timeout Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../TestMetadata/TimeoutAttribute.cs | 10 +- TUnit.Core/Contexts/HookRegisteredContext.cs | 17 +++ TUnit.Core/Hooks/InstanceHookMethod.cs | 2 +- TUnit.Core/Hooks/StaticHookMethod.cs | 2 +- .../IHookRegisteredEventReceiver.cs | 12 ++ TUnit.Engine/Helpers/HookTimeoutHelper.cs | 107 ++++++++++++++++++ .../Services/HookCollectionService.cs | 52 +++++++-- 7 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 TUnit.Core/Contexts/HookRegisteredContext.cs create mode 100644 TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs create mode 100644 TUnit.Engine/Helpers/HookTimeoutHelper.cs diff --git a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs index c2ddf9d22f..0379334099 100644 --- a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs @@ -32,7 +32,7 @@ namespace TUnit.Core; /// /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] -public class TimeoutAttribute(int timeoutInMilliseconds) : TUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute +public class TimeoutAttribute(int timeoutInMilliseconds) : TUnitAttribute, ITestDiscoveryEventReceiver, IHookRegisteredEventReceiver, IScopedAttribute { /// public int Order => 0; @@ -49,4 +49,12 @@ public ValueTask OnTestDiscovered(DiscoveredTestContext context) context.TestDetails.Timeout = Timeout; return default(ValueTask); } + + /// + public ValueTask OnHookRegistered(HookRegisteredContext context) + { + // Apply timeout to the hook method + // This will be used by the hook execution infrastructure + return default(ValueTask); + } } diff --git a/TUnit.Core/Contexts/HookRegisteredContext.cs b/TUnit.Core/Contexts/HookRegisteredContext.cs new file mode 100644 index 0000000000..71fbf74d4f --- /dev/null +++ b/TUnit.Core/Contexts/HookRegisteredContext.cs @@ -0,0 +1,17 @@ +using TUnit.Core.Hooks; + +namespace TUnit.Core; + +/// +/// Context for hook registration phase +/// +public class HookRegisteredContext +{ + public StaticHookMethod HookMethod { get; } + public string HookName => HookMethod.Name; + + public HookRegisteredContext(StaticHookMethod hookMethod) + { + HookMethod = hookMethod; + } +} \ No newline at end of file diff --git a/TUnit.Core/Hooks/InstanceHookMethod.cs b/TUnit.Core/Hooks/InstanceHookMethod.cs index d212f78040..ca15c029b3 100644 --- a/TUnit.Core/Hooks/InstanceHookMethod.cs +++ b/TUnit.Core/Hooks/InstanceHookMethod.cs @@ -22,7 +22,7 @@ public record InstanceHookMethod : IExecutableHook public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - public TimeSpan? Timeout => GetAttribute()?.Timeout; + public TimeSpan? Timeout => GetAttribute()?.Timeout ?? TimeSpan.FromMinutes(5); public required IHookExecutor HookExecutor { get; init; } diff --git a/TUnit.Core/Hooks/StaticHookMethod.cs b/TUnit.Core/Hooks/StaticHookMethod.cs index d8deea6024..5f122c7ad8 100644 --- a/TUnit.Core/Hooks/StaticHookMethod.cs +++ b/TUnit.Core/Hooks/StaticHookMethod.cs @@ -32,7 +32,7 @@ public abstract record StaticHookMethod public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - public TimeSpan? Timeout => GetAttribute()?.Timeout; + public TimeSpan? Timeout => GetAttribute()?.Timeout ?? TimeSpan.FromMinutes(5); public required IHookExecutor HookExecutor { get; init; } diff --git a/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs b/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs new file mode 100644 index 0000000000..8f3ac3da53 --- /dev/null +++ b/TUnit.Core/Interfaces/IHookRegisteredEventReceiver.cs @@ -0,0 +1,12 @@ +namespace TUnit.Core.Interfaces; + +/// +/// Interface for hook registered event receivers +/// +public interface IHookRegisteredEventReceiver : IEventReceiver +{ + /// + /// Called when a hook is registered + /// + ValueTask OnHookRegistered(HookRegisteredContext context); +} \ No newline at end of file diff --git a/TUnit.Engine/Helpers/HookTimeoutHelper.cs b/TUnit.Engine/Helpers/HookTimeoutHelper.cs new file mode 100644 index 0000000000..a08027a778 --- /dev/null +++ b/TUnit.Engine/Helpers/HookTimeoutHelper.cs @@ -0,0 +1,107 @@ +using TUnit.Core.Hooks; + +namespace TUnit.Engine.Helpers; + +/// +/// Helper class for executing hooks with timeout enforcement +/// +internal static class HookTimeoutHelper +{ + /// + /// Creates a timeout-aware action wrapper for a hook + /// + public static Func CreateTimeoutHookAction( + StaticHookMethod hook, + T context, + CancellationToken cancellationToken) + { + var timeout = hook.Timeout; + if (timeout == null) + { + // No timeout specified, execute normally + return async () => await hook.ExecuteAsync(context, cancellationToken); + } + + return async () => + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeoutMs = (int)timeout.Value.TotalMilliseconds; + cts.CancelAfter(timeoutMs); + + try + { + await hook.ExecuteAsync(context, cts.Token); + } + catch (OperationCanceledException) when (cts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new System.TimeoutException($"Hook '{hook.Name}' exceeded timeout of {timeoutMs}ms"); + } + }; + } + + /// + /// Creates a timeout-aware action wrapper for a hook delegate + /// + public static Func CreateTimeoutHookAction( + Func hookDelegate, + T context, + TimeSpan? timeout, + string hookName, + CancellationToken cancellationToken) + { + if (timeout == null) + { + // No timeout specified, execute normally + return async () => await hookDelegate(context, cancellationToken); + } + + return async () => + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeoutMs = (int)timeout.Value.TotalMilliseconds; + cts.CancelAfter(timeoutMs); + + try + { + await hookDelegate(context, cts.Token); + } + catch (OperationCanceledException) when (cts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new System.TimeoutException($"Hook '{hookName}' exceeded timeout of {timeoutMs}ms"); + } + }; + } + + /// + /// Creates a timeout-aware action wrapper for a hook delegate that returns ValueTask + /// + public static Func CreateTimeoutHookAction( + Func hookDelegate, + T context, + TimeSpan? timeout, + string hookName, + CancellationToken cancellationToken) + { + if (timeout == null) + { + // No timeout specified, execute normally + return async () => await hookDelegate(context, cancellationToken); + } + + return async () => + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeoutMs = (int)timeout.Value.TotalMilliseconds; + cts.CancelAfter(timeoutMs); + + try + { + await hookDelegate(context, cts.Token); + } + catch (OperationCanceledException) when (cts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new System.TimeoutException($"Hook '{hookName}' exceeded timeout of {timeoutMs}ms"); + } + }; + } +} \ No newline at end of file diff --git a/TUnit.Engine/Services/HookCollectionService.cs b/TUnit.Engine/Services/HookCollectionService.cs index 4db0f322b7..4e35723118 100644 --- a/TUnit.Engine/Services/HookCollectionService.cs +++ b/TUnit.Engine/Services/HookCollectionService.cs @@ -2,6 +2,7 @@ using System.Reflection; using TUnit.Core; using TUnit.Core.Hooks; +using TUnit.Engine.Helpers; using TUnit.Engine.Interfaces; namespace TUnit.Engine.Services; @@ -516,7 +517,14 @@ private static Func CreateInstanceHookDele { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + (ctx, ct) => hook.ExecuteAsync(ctx, ct), + context, + hook.Timeout, + hook.Name, + cancellationToken); + + await timeoutAction(); }; } @@ -524,7 +532,12 @@ private static Func CreateStaticHookDelega { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } @@ -532,7 +545,12 @@ private static Func CreateClassHookDe { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } @@ -540,7 +558,12 @@ private static Func CreateAssembly { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } @@ -548,7 +571,12 @@ private static Func CreateTestSessi { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } @@ -556,7 +584,12 @@ private static Func CreateB { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } @@ -564,7 +597,12 @@ private static Func CreateTestDis { return async (context, cancellationToken) => { - await hook.ExecuteAsync(context, cancellationToken); + var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( + hook, + context, + cancellationToken); + + await timeoutAction(); }; } From 3788f8eb14e932c12ffe11b4b6129f21217bf600 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:15:00 +0000 Subject: [PATCH 3/8] Update public API snapshots to approve hook timeout changes Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- ...ibrary_Has_No_API_Changes.DotNet9_0.verified.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 1c409c5093..42a3fb98b7 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -781,6 +781,12 @@ namespace public int Line { get; } public int Order { get; init; } } + public class HookRegisteredContext + { + public HookRegisteredContext(. hookMethod) { } + public . HookMethod { get; } + public string HookName { get; } + } public static class HookRegistrationIndices { public static int GetNextAfterAssemblyHookIndex() { } @@ -1506,11 +1512,12 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } public Timeout { get; } + public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class Timing : <.Timing> @@ -2252,6 +2259,10 @@ namespace .Interfaces . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action); . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action); } + public interface IHookRegisteredEventReceiver : . + { + . OnHookRegistered(.HookRegisteredContext context); + } public interface IInfersType { } public interface ILastTestInAssemblyEventReceiver : . { From dc3e1868f0102be2d4fcbc76912a8dd8fd58706a Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:43:52 +0000 Subject: [PATCH 4/8] Implement proper hook timeout functionality with event receiver pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix TimeoutAttribute.OnHookRegistered to actually set timeout on context - Extend HookRegisteredContext to support both static and instance hook methods - Update hook method classes to use settable timeout property instead of direct attribute access - Add hook registration event receiver infrastructure to EventReceiverOrchestrator - Modify HookCollectionService to trigger hook registration events during delegate creation - Reorder service initialization to inject EventReceiverOrchestrator into HookCollectionService This implementation follows the established event receiver pattern used for tests, ensuring that TimeoutAttribute and other hook attributes can modify hook properties during registration rather than relying on direct attribute access. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../TestMetadata/TimeoutAttribute.cs | 3 +- TUnit.Core/Contexts/HookRegisteredContext.cs | 27 +++++++- TUnit.Core/Hooks/InstanceHookMethod.cs | 6 +- TUnit.Core/Hooks/StaticHookMethod.cs | 6 +- .../Framework/TUnitServiceProvider.cs | 4 +- .../Services/EventReceiverOrchestrator.cs | 52 +++++++++++++++ .../Services/HookCollectionService.cs | 65 ++++++++++++++++--- 7 files changed, 146 insertions(+), 17 deletions(-) diff --git a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs index 0379334099..589ae05cce 100644 --- a/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/TimeoutAttribute.cs @@ -53,8 +53,7 @@ public ValueTask OnTestDiscovered(DiscoveredTestContext context) /// public ValueTask OnHookRegistered(HookRegisteredContext context) { - // Apply timeout to the hook method - // This will be used by the hook execution infrastructure + context.Timeout = Timeout; return default(ValueTask); } } diff --git a/TUnit.Core/Contexts/HookRegisteredContext.cs b/TUnit.Core/Contexts/HookRegisteredContext.cs index 71fbf74d4f..f5025a9fb5 100644 --- a/TUnit.Core/Contexts/HookRegisteredContext.cs +++ b/TUnit.Core/Contexts/HookRegisteredContext.cs @@ -7,11 +7,32 @@ namespace TUnit.Core; /// public class HookRegisteredContext { - public StaticHookMethod HookMethod { get; } - public string HookName => HookMethod.Name; + private readonly object _hookMethod; + private readonly string _hookName; + private TimeSpan? _timeout; + + public StaticHookMethod? StaticHookMethod => _hookMethod as StaticHookMethod; + public InstanceHookMethod? InstanceHookMethod => _hookMethod as InstanceHookMethod; + public string HookName => _hookName; + + /// + /// Gets or sets the timeout for this hook + /// + public TimeSpan? Timeout + { + get => _timeout; + set => _timeout = value; + } public HookRegisteredContext(StaticHookMethod hookMethod) { - HookMethod = hookMethod; + _hookMethod = hookMethod; + _hookName = hookMethod.Name; + } + + public HookRegisteredContext(InstanceHookMethod hookMethod) + { + _hookMethod = hookMethod; + _hookName = hookMethod.Name; } } \ No newline at end of file diff --git a/TUnit.Core/Hooks/InstanceHookMethod.cs b/TUnit.Core/Hooks/InstanceHookMethod.cs index ca15c029b3..5fd43e3481 100644 --- a/TUnit.Core/Hooks/InstanceHookMethod.cs +++ b/TUnit.Core/Hooks/InstanceHookMethod.cs @@ -22,7 +22,11 @@ public record InstanceHookMethod : IExecutableHook public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - public TimeSpan? Timeout => GetAttribute()?.Timeout ?? TimeSpan.FromMinutes(5); + /// + /// Gets or sets the timeout for this hook method. This will be set during hook registration + /// by the event receiver infrastructure, falling back to the default 5-minute timeout. + /// + public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5); public required IHookExecutor HookExecutor { get; init; } diff --git a/TUnit.Core/Hooks/StaticHookMethod.cs b/TUnit.Core/Hooks/StaticHookMethod.cs index 5f122c7ad8..59979e6c3d 100644 --- a/TUnit.Core/Hooks/StaticHookMethod.cs +++ b/TUnit.Core/Hooks/StaticHookMethod.cs @@ -32,7 +32,11 @@ public abstract record StaticHookMethod public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - public TimeSpan? Timeout => GetAttribute()?.Timeout ?? TimeSpan.FromMinutes(5); + /// + /// Gets the timeout for this hook method. This will be set during hook registration + /// by the event receiver infrastructure, falling back to the default 5-minute timeout. + /// + public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5); public required IHookExecutor HookExecutor { get; init; } diff --git a/TUnit.Engine/Framework/TUnitServiceProvider.cs b/TUnit.Engine/Framework/TUnitServiceProvider.cs index 6a9eb59bd5..5a92a66d36 100644 --- a/TUnit.Engine/Framework/TUnitServiceProvider.cs +++ b/TUnit.Engine/Framework/TUnitServiceProvider.cs @@ -86,14 +86,14 @@ public TUnitServiceProvider(IExtension extension, CancellationToken = Register(new EngineCancellationToken()); - HookCollectionService = Register(new HookCollectionService()); + EventReceiverOrchestrator = Register(new EventReceiverOrchestrator(Logger)); + HookCollectionService = Register(new HookCollectionService(EventReceiverOrchestrator)); ParallelLimitLockProvider = Register(new ParallelLimitLockProvider()); ContextProvider = Register(new ContextProvider(this, TestSessionId, Filter?.ToString())); HookOrchestrator = Register(new HookOrchestrator(HookCollectionService, Logger, ContextProvider, this)); - EventReceiverOrchestrator = Register(new EventReceiverOrchestrator(Logger)); // Detect execution mode from command line or environment var useSourceGeneration = GetUseSourceGeneration(CommandLineOptions); diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index 1cf04866c8..433125f50d 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -197,6 +197,58 @@ public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext contex } } + public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredContext hookContext, CancellationToken cancellationToken) + { + // Get event receivers from the hook method's attributes + IEnumerable attributes; + + if (hookContext.StaticHookMethod != null) + { + attributes = hookContext.StaticHookMethod.Attributes; + } + else if (hookContext.InstanceHookMethod != null) + { + attributes = hookContext.InstanceHookMethod.Attributes; + } + else + { + return; // No hook method to process + } + + var eventReceivers = attributes + .OfType() + .OrderBy(r => r.Order) + .ToList(); + + // Filter scoped attributes to ensure only the highest priority one of each type is invoked + var filteredReceivers = ScopedAttributeFilter.FilterScopedAttributes(eventReceivers); + + foreach (var receiver in filteredReceivers.OrderBy(r => r.Order)) + { + try + { + await receiver.OnHookRegistered(hookContext); + } + catch (Exception ex) + { + await _logger.LogErrorAsync($"Error in hook registration event receiver: {ex.Message}"); + } + } + + // Apply the timeout from the context back to the hook method + if (hookContext.Timeout.HasValue) + { + if (hookContext.StaticHookMethod != null) + { + hookContext.StaticHookMethod.Timeout = hookContext.Timeout; + } + else if (hookContext.InstanceHookMethod != null) + { + hookContext.InstanceHookMethod.Timeout = hookContext.Timeout; + } + } + } + // First/Last event methods with fast-path checks [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/TUnit.Engine/Services/HookCollectionService.cs b/TUnit.Engine/Services/HookCollectionService.cs index 4e35723118..db18766383 100644 --- a/TUnit.Engine/Services/HookCollectionService.cs +++ b/TUnit.Engine/Services/HookCollectionService.cs @@ -9,6 +9,7 @@ namespace TUnit.Engine.Services; internal sealed class HookCollectionService : IHookCollectionService { + private readonly EventReceiverOrchestrator _eventReceiverOrchestrator; private readonly ConcurrentDictionary>> _beforeTestHooksCache = new(); private readonly ConcurrentDictionary>> _afterTestHooksCache = new(); private readonly ConcurrentDictionary>> _beforeEveryTestHooksCache = new(); @@ -19,6 +20,48 @@ internal sealed class HookCollectionService : IHookCollectionService // Cache for complete hook chains to avoid repeated lookups private readonly ConcurrentDictionary _completeHookChainCache = new(); + // Cache for processed hooks to avoid re-processing event receivers + private readonly ConcurrentDictionary _processedHooks = new(); + + public HookCollectionService(EventReceiverOrchestrator eventReceiverOrchestrator) + { + _eventReceiverOrchestrator = eventReceiverOrchestrator; + } + + private async Task ProcessHookRegistrationAsync(object hookMethod, CancellationToken cancellationToken = default) + { + // Only process each hook once + if (!_processedHooks.TryAdd(hookMethod, true)) + { + return; + } + + try + { + HookRegisteredContext context; + + if (hookMethod is StaticHookMethod staticHook) + { + context = new HookRegisteredContext(staticHook); + } + else if (hookMethod is InstanceHookMethod instanceHook) + { + context = new HookRegisteredContext(instanceHook); + } + else + { + return; // Unknown hook type + } + + await _eventReceiverOrchestrator.InvokeHookRegistrationEventReceiversAsync(context, cancellationToken); + } + catch (Exception) + { + // Ignore errors during hook registration event processing to avoid breaking hook execution + // The EventReceiverOrchestrator already logs errors internally + } + } + private sealed class CompleteHookChain { public IReadOnlyList> BeforeTestHooks { get; init; } = [ @@ -51,7 +94,7 @@ public ValueTask>> Coll { foreach (var hook in sourceHooks) { - var hookFunc = CreateInstanceHookDelegate(hook); + var hookFunc = await CreateInstanceHookDelegateAsync(hook); typeHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } } @@ -64,7 +107,7 @@ public ValueTask>> Coll { foreach (var hook in openTypeHooks) { - var hookFunc = CreateInstanceHookDelegate(hook); + var hookFunc = await CreateInstanceHookDelegateAsync(hook); typeHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } } @@ -111,7 +154,7 @@ public ValueTask>> Coll { foreach (var hook in sourceHooks) { - var hookFunc = CreateInstanceHookDelegate(hook); + var hookFunc = await CreateInstanceHookDelegateAsync(hook); typeHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } } @@ -124,7 +167,7 @@ public ValueTask>> Coll { foreach (var hook in openTypeHooks) { - var hookFunc = CreateInstanceHookDelegate(hook); + var hookFunc = await CreateInstanceHookDelegateAsync(hook); typeHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } } @@ -163,7 +206,7 @@ public ValueTask>> Coll // Collect all global BeforeEvery hooks foreach (var hook in Sources.BeforeEveryTestHooks) { - var hookFunc = CreateStaticHookDelegate(hook); + var hookFunc = await CreateStaticHookDelegateAsync(hook); allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } @@ -186,7 +229,7 @@ public ValueTask>> Coll // Collect all global AfterEvery hooks foreach (var hook in Sources.AfterEveryTestHooks) { - var hookFunc = CreateStaticHookDelegate(hook); + var hookFunc = await CreateStaticHookDelegateAsync(hook); allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc)); } @@ -513,8 +556,11 @@ public ValueTask>>(hooks); } - private static Func CreateInstanceHookDelegate(InstanceHookMethod hook) + private async Task> CreateInstanceHookDelegateAsync(InstanceHookMethod hook) { + // Process hook registration event receivers + await ProcessHookRegistrationAsync(hook); + return async (context, cancellationToken) => { var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( @@ -528,8 +574,11 @@ private static Func CreateInstanceHookDele }; } - private static Func CreateStaticHookDelegate(StaticHookMethod hook) + private async Task> CreateStaticHookDelegateAsync(StaticHookMethod hook) { + // Process hook registration event receivers + await ProcessHookRegistrationAsync(hook); + return async (context, cancellationToken) => { var timeoutAction = HookTimeoutHelper.CreateTimeoutHookAction( From f57639707d8c18756c902ae56c714dba17622ee7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:55:25 +0000 Subject: [PATCH 5/8] Refactor hook methods to share unified HookMethod base class - Created abstract HookMethod base record with all shared properties - StaticHookMethod and InstanceHookMethod now inherit from HookMethod - Simplified HookRegisteredContext to work with single HookMethod type - Reduced EventReceiverOrchestrator hook processing from 15 lines to 3 - Simplified HookCollectionService hook registration logic - Hook event receivers now only need to handle HookMethod instead of both types Co-authored-by: Tom Longhurst --- TUnit.Core/Contexts/HookRegisteredContext.cs | 18 ++------ TUnit.Core/Hooks/HookMethod.cs | 45 +++++++++++++------ TUnit.Core/Hooks/InstanceHookMethod.cs | 25 +---------- TUnit.Core/Hooks/StaticHookMethod.cs | 27 +---------- .../Services/EventReceiverOrchestrator.cs | 17 +------ .../Services/HookCollectionService.cs | 17 +------ 6 files changed, 42 insertions(+), 107 deletions(-) diff --git a/TUnit.Core/Contexts/HookRegisteredContext.cs b/TUnit.Core/Contexts/HookRegisteredContext.cs index f5025a9fb5..144aa84c94 100644 --- a/TUnit.Core/Contexts/HookRegisteredContext.cs +++ b/TUnit.Core/Contexts/HookRegisteredContext.cs @@ -7,13 +7,10 @@ namespace TUnit.Core; /// public class HookRegisteredContext { - private readonly object _hookMethod; - private readonly string _hookName; private TimeSpan? _timeout; - public StaticHookMethod? StaticHookMethod => _hookMethod as StaticHookMethod; - public InstanceHookMethod? InstanceHookMethod => _hookMethod as InstanceHookMethod; - public string HookName => _hookName; + public HookMethod HookMethod { get; } + public string HookName => HookMethod.Name; /// /// Gets or sets the timeout for this hook @@ -24,15 +21,8 @@ public TimeSpan? Timeout set => _timeout = value; } - public HookRegisteredContext(StaticHookMethod hookMethod) + public HookRegisteredContext(HookMethod hookMethod) { - _hookMethod = hookMethod; - _hookName = hookMethod.Name; - } - - public HookRegisteredContext(InstanceHookMethod hookMethod) - { - _hookMethod = hookMethod; - _hookName = hookMethod.Name; + HookMethod = hookMethod; } } \ No newline at end of file diff --git a/TUnit.Core/Hooks/HookMethod.cs b/TUnit.Core/Hooks/HookMethod.cs index 3d1b4e6003..89eb056f31 100644 --- a/TUnit.Core/Hooks/HookMethod.cs +++ b/TUnit.Core/Hooks/HookMethod.cs @@ -1,20 +1,37 @@ -namespace TUnit.Core.Hooks; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using TUnit.Core.Extensions; +using TUnit.Core.Interfaces; -public class HookMethod +namespace TUnit.Core.Hooks; + +#if !DEBUG +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +#endif +public abstract record HookMethod { - public StaticHookMethod? StaticHookMethod { get; } - public InstanceHookMethod? InstanceHookMethod { get; } + public required MethodMetadata MethodInfo { get; init; } + + [field: AllowNull, MaybeNull] + public string Name => field ??= $"{ClassType.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})"; + + public abstract Type ClassType { get; } + public virtual Assembly? Assembly => ClassType?.Assembly; + + [field: AllowNull, MaybeNull] + public IEnumerable Attributes => field ??= MethodInfo.GetCustomAttributes(); + + public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - public HookMethod(InstanceHookMethod instanceHookMethod) - { - InstanceHookMethod = instanceHookMethod; - } + /// + /// Gets the timeout for this hook method. This will be set during hook registration + /// by the event receiver infrastructure, falling back to the default 5-minute timeout. + /// + public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5); - public HookMethod(StaticHookMethod staticHookMethod) - { - StaticHookMethod = staticHookMethod; - } + public required IHookExecutor HookExecutor { get; init; } - public static implicit operator HookMethod(InstanceHookMethod instanceHookMethod) => new(instanceHookMethod); - public static implicit operator HookMethod(StaticHookMethod staticHookMethod) => new(staticHookMethod); + public required int Order { get; init; } + + public required int RegistrationIndex { get; init; } } diff --git a/TUnit.Core/Hooks/InstanceHookMethod.cs b/TUnit.Core/Hooks/InstanceHookMethod.cs index 5fd43e3481..2578d19153 100644 --- a/TUnit.Core/Hooks/InstanceHookMethod.cs +++ b/TUnit.Core/Hooks/InstanceHookMethod.cs @@ -8,31 +8,10 @@ namespace TUnit.Core.Hooks; #if !DEBUG [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] #endif -public record InstanceHookMethod : IExecutableHook +public record InstanceHookMethod : HookMethod, IExecutableHook { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - public required Type ClassType { get; init; } - public Assembly Assembly => ClassType.Assembly; - public required MethodMetadata MethodInfo { get; init; } - - [field: AllowNull, MaybeNull] - public string Name => field ??= $"{ClassType.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})"; - - [field: AllowNull, MaybeNull] public IEnumerable Attributes => field ??= MethodInfo.GetCustomAttributes(); - - public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - - /// - /// Gets or sets the timeout for this hook method. This will be set during hook registration - /// by the event receiver infrastructure, falling back to the default 5-minute timeout. - /// - public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5); - - public required IHookExecutor HookExecutor { get; init; } - - public required int Order { get; init; } - - public required int RegistrationIndex { get; init; } + public override Type ClassType { get; init; } public Func? Body { get; init; } diff --git a/TUnit.Core/Hooks/StaticHookMethod.cs b/TUnit.Core/Hooks/StaticHookMethod.cs index 59979e6c3d..311450c9e3 100644 --- a/TUnit.Core/Hooks/StaticHookMethod.cs +++ b/TUnit.Core/Hooks/StaticHookMethod.cs @@ -17,32 +17,9 @@ public abstract record StaticHookMethod : StaticHookMethod, IExecutableHook field ??= $"{MethodInfo.Class.Type.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})"; - - public Type ClassType => MethodInfo.Class.Type; - public Assembly? Assembly => ClassType?.Assembly; - - [field: AllowNull, MaybeNull] - public IEnumerable Attributes => field ??= MethodInfo.GetCustomAttributes(); - - public TAttribute? GetAttribute() where TAttribute : Attribute => Attributes.OfType().FirstOrDefault(); - - /// - /// Gets the timeout for this hook method. This will be set during hook registration - /// by the event receiver infrastructure, falling back to the default 5-minute timeout. - /// - public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5); - - public required IHookExecutor HookExecutor { get; init; } - - public required int Order { get; init; } - - public required int RegistrationIndex { get; init; } + public override Type ClassType => MethodInfo.Class.Type; public required string FilePath { get; init; } diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index 433125f50d..a600eae77d 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -200,22 +200,7 @@ public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext contex public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredContext hookContext, CancellationToken cancellationToken) { // Get event receivers from the hook method's attributes - IEnumerable attributes; - - if (hookContext.StaticHookMethod != null) - { - attributes = hookContext.StaticHookMethod.Attributes; - } - else if (hookContext.InstanceHookMethod != null) - { - attributes = hookContext.InstanceHookMethod.Attributes; - } - else - { - return; // No hook method to process - } - - var eventReceivers = attributes + var eventReceivers = hookContext.HookMethod.Attributes .OfType() .OrderBy(r => r.Order) .ToList(); diff --git a/TUnit.Engine/Services/HookCollectionService.cs b/TUnit.Engine/Services/HookCollectionService.cs index db18766383..ff8be4662c 100644 --- a/TUnit.Engine/Services/HookCollectionService.cs +++ b/TUnit.Engine/Services/HookCollectionService.cs @@ -28,7 +28,7 @@ public HookCollectionService(EventReceiverOrchestrator eventReceiverOrchestrator _eventReceiverOrchestrator = eventReceiverOrchestrator; } - private async Task ProcessHookRegistrationAsync(object hookMethod, CancellationToken cancellationToken = default) + private async Task ProcessHookRegistrationAsync(HookMethod hookMethod, CancellationToken cancellationToken = default) { // Only process each hook once if (!_processedHooks.TryAdd(hookMethod, true)) @@ -38,20 +38,7 @@ private async Task ProcessHookRegistrationAsync(object hookMethod, CancellationT try { - HookRegisteredContext context; - - if (hookMethod is StaticHookMethod staticHook) - { - context = new HookRegisteredContext(staticHook); - } - else if (hookMethod is InstanceHookMethod instanceHook) - { - context = new HookRegisteredContext(instanceHook); - } - else - { - return; // Unknown hook type - } + var context = new HookRegisteredContext(hookMethod); await _eventReceiverOrchestrator.InvokeHookRegistrationEventReceiversAsync(context, cancellationToken); } From d0c57d3f591a85ae6f2ee1adf8a441b7d97575cb Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:08:02 +0100 Subject: [PATCH 6/8] Fix hook timeout implementation for PR #2891 - Fixed InstanceHookMethod to use InitClassType property instead of trying to override ClassType with init accessor - Updated source generators to generate InitClassType instead of ClassType for InstanceHookMethod - Fixed async/await issues in HookCollectionService by restructuring methods to avoid async lambdas - Added proper DynamicallyAccessedMembers annotations for trimming compatibility - Fixed EventReceiverOrchestrator to use HookMethod property from HookRegisteredContext --- .../Writers/Hooks/TestHooksWriter.cs | 2 +- .../Generators/HookMetadataGenerator.cs | 2 +- TUnit.Core/Hooks/HookMethod.cs | 1 + TUnit.Core/Hooks/InstanceHookMethod.cs | 11 ++- TUnit.Core/Hooks/StaticHookMethod.cs | 1 + .../Services/EventReceiverOrchestrator.cs | 11 +-- .../Services/HookCollectionService.cs | 68 +++++++++++++------ 7 files changed, 64 insertions(+), 32 deletions(-) diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/Writers/Hooks/TestHooksWriter.cs b/TUnit.Core.SourceGenerator/CodeGenerators/Writers/Hooks/TestHooksWriter.cs index 56e22fe03b..166641c073 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/Writers/Hooks/TestHooksWriter.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/Writers/Hooks/TestHooksWriter.cs @@ -39,7 +39,7 @@ public static void Execute(ICodeWriter sourceBuilder, HooksDataModel model) sourceBuilder.Append("new global::TUnit.Core.Hooks.InstanceHookMethod"); sourceBuilder.Append("{"); - sourceBuilder.Append($"ClassType = typeof({model.FullyQualifiedTypeName}),"); + sourceBuilder.Append($"InitClassType = typeof({model.FullyQualifiedTypeName}),"); sourceBuilder.Append("MethodInfo = "); SourceInformationWriter.GenerateMethodInformation(sourceBuilder, model.Context.SemanticModel.Compilation, model.ClassType, model.Method, null, ','); diff --git a/TUnit.Core.SourceGenerator/Generators/HookMetadataGenerator.cs b/TUnit.Core.SourceGenerator/Generators/HookMetadataGenerator.cs index f44233da9f..2a686ce1ac 100644 --- a/TUnit.Core.SourceGenerator/Generators/HookMetadataGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/HookMetadataGenerator.cs @@ -803,7 +803,7 @@ private static void GenerateHookObject(CodeWriter writer, HookMethodMetadata hoo if (isInstance) { - writer.AppendLine($"ClassType = typeof({hook.TypeSymbol.GloballyQualified()}),"); + writer.AppendLine($"InitClassType = typeof({hook.TypeSymbol.GloballyQualified()}),"); } writer.Append("MethodInfo = "); diff --git a/TUnit.Core/Hooks/HookMethod.cs b/TUnit.Core/Hooks/HookMethod.cs index 89eb056f31..ab73dc6236 100644 --- a/TUnit.Core/Hooks/HookMethod.cs +++ b/TUnit.Core/Hooks/HookMethod.cs @@ -15,6 +15,7 @@ public abstract record HookMethod [field: AllowNull, MaybeNull] public string Name => field ??= $"{ClassType.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})"; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public abstract Type ClassType { get; } public virtual Assembly? Assembly => ClassType?.Assembly; diff --git a/TUnit.Core/Hooks/InstanceHookMethod.cs b/TUnit.Core/Hooks/InstanceHookMethod.cs index 2578d19153..59f909b5cf 100644 --- a/TUnit.Core/Hooks/InstanceHookMethod.cs +++ b/TUnit.Core/Hooks/InstanceHookMethod.cs @@ -11,7 +11,16 @@ namespace TUnit.Core.Hooks; public record InstanceHookMethod : HookMethod, IExecutableHook { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - public override Type ClassType { get; init; } + private readonly Type _classType = null!; + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public override Type ClassType => _classType; + + public required Type InitClassType + { + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + init { _classType = value; } + } public Func? Body { get; init; } diff --git a/TUnit.Core/Hooks/StaticHookMethod.cs b/TUnit.Core/Hooks/StaticHookMethod.cs index 311450c9e3..f0f2ca27f0 100644 --- a/TUnit.Core/Hooks/StaticHookMethod.cs +++ b/TUnit.Core/Hooks/StaticHookMethod.cs @@ -19,6 +19,7 @@ public abstract record StaticHookMethod : StaticHookMethod, IExecutableHook MethodInfo.Class.Type; public required string FilePath { get; init; } diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index a600eae77d..33ebb1fdd1 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -221,16 +221,9 @@ public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredC } // Apply the timeout from the context back to the hook method - if (hookContext.Timeout.HasValue) + if (hookContext.Timeout.HasValue && hookContext.HookMethod != null) { - if (hookContext.StaticHookMethod != null) - { - hookContext.StaticHookMethod.Timeout = hookContext.Timeout; - } - else if (hookContext.InstanceHookMethod != null) - { - hookContext.InstanceHookMethod.Timeout = hookContext.Timeout; - } + hookContext.HookMethod.Timeout = hookContext.Timeout; } } diff --git a/TUnit.Engine/Services/HookCollectionService.cs b/TUnit.Engine/Services/HookCollectionService.cs index ff8be4662c..719bf888a7 100644 --- a/TUnit.Engine/Services/HookCollectionService.cs +++ b/TUnit.Engine/Services/HookCollectionService.cs @@ -65,9 +65,19 @@ private sealed class CompleteHookChain ]; } - public ValueTask>> CollectBeforeTestHooksAsync(Type testClassType) + public async ValueTask>> CollectBeforeTestHooksAsync(Type testClassType) { - var hooks = _beforeTestHooksCache.GetOrAdd(testClassType, type => + if (_beforeTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) + { + return cachedHooks; + } + + var hooks = await BuildBeforeTestHooksAsync(testClassType); + _beforeTestHooksCache.TryAdd(testClassType, hooks); + return hooks; + } + + private async Task>> BuildBeforeTestHooksAsync(Type type) { var hooksByType = new List<(Type type, List<(int order, int registrationIndex, Func hook)> hooks)>(); @@ -120,14 +130,21 @@ public ValueTask>> Coll } return finalHooks; - }); - - return new ValueTask>>(hooks); } - public ValueTask>> CollectAfterTestHooksAsync(Type testClassType) + public async ValueTask>> CollectAfterTestHooksAsync(Type testClassType) { - var hooks = _afterTestHooksCache.GetOrAdd(testClassType, type => + if (_afterTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) + { + return cachedHooks; + } + + var hooks = await BuildAfterTestHooksAsync(testClassType); + _afterTestHooksCache.TryAdd(testClassType, hooks); + return hooks; + } + + private async Task>> BuildAfterTestHooksAsync(Type type) { var hooksByType = new List<(Type type, List<(int order, int registrationIndex, Func hook)> hooks)>(); @@ -179,14 +196,21 @@ public ValueTask>> Coll } return finalHooks; - }); - - return new ValueTask>>(hooks); } - public ValueTask>> CollectBeforeEveryTestHooksAsync(Type testClassType) + public async ValueTask>> CollectBeforeEveryTestHooksAsync(Type testClassType) { - var hooks = _beforeEveryTestHooksCache.GetOrAdd(testClassType, type => + if (_beforeEveryTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) + { + return cachedHooks; + } + + var hooks = await BuildBeforeEveryTestHooksAsync(testClassType); + _beforeEveryTestHooksCache.TryAdd(testClassType, hooks); + return hooks; + } + + private async Task>> BuildBeforeEveryTestHooksAsync(Type type) { var allHooks = new List<(int order, int registrationIndex, Func hook)>(); @@ -202,14 +226,21 @@ public ValueTask>> Coll .ThenBy(h => h.registrationIndex) .Select(h => h.hook) .ToList(); - }); - - return new ValueTask>>(hooks); } - public ValueTask>> CollectAfterEveryTestHooksAsync(Type testClassType) + public async ValueTask>> CollectAfterEveryTestHooksAsync(Type testClassType) { - var hooks = _afterEveryTestHooksCache.GetOrAdd(testClassType, type => + if (_afterEveryTestHooksCache.TryGetValue(testClassType, out var cachedHooks)) + { + return cachedHooks; + } + + var hooks = await BuildAfterEveryTestHooksAsync(testClassType); + _afterEveryTestHooksCache.TryAdd(testClassType, hooks); + return hooks; + } + + private async Task>> BuildAfterEveryTestHooksAsync(Type type) { var allHooks = new List<(int order, int registrationIndex, Func hook)>(); @@ -225,9 +256,6 @@ public ValueTask>> Coll .ThenBy(h => h.registrationIndex) .Select(h => h.hook) .ToList(); - }); - - return new ValueTask>>(hooks); } public ValueTask>> CollectBeforeClassHooksAsync(Type testClassType) From 4e58d8ed76af55807815b6748d4c5fdcfa3222ff Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:26:32 +0100 Subject: [PATCH 7/8] fix: Update InstanceHookMethod property from ClassType to InitClassType in test files --- .../AssemblyAfterTests.Test.verified.txt | 14 +++++++------- .../AssemblyBeforeTests.Test.verified.txt | 14 +++++++------- .../GlobalStaticAfterEachTests.Test.verified.txt | 14 +++++++------- .../GlobalStaticBeforeEachTests.Test.verified.txt | 14 +++++++------- .../HooksTests.DisposableFieldTests.verified.txt | 4 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/TUnit.Core.SourceGenerator.Tests/AssemblyAfterTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/AssemblyAfterTests.Test.verified.txt index a99996daca..702915cc67 100644 --- a/TUnit.Core.SourceGenerator.Tests/AssemblyAfterTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/AssemblyAfterTests.Test.verified.txt @@ -35,7 +35,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyBase1)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase1), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase1), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase1), @@ -77,7 +77,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyBase2)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase2), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase2), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase2), @@ -119,7 +119,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyBase3)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase3), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase3), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyBase3), @@ -161,7 +161,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), @@ -202,7 +202,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), @@ -252,7 +252,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), @@ -302,7 +302,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.AssemblyCleanupTests), diff --git a/TUnit.Core.SourceGenerator.Tests/AssemblyBeforeTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/AssemblyBeforeTests.Test.verified.txt index 608b85551b..ae811b9000 100644 --- a/TUnit.Core.SourceGenerator.Tests/AssemblyBeforeTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/AssemblyBeforeTests.Test.verified.txt @@ -35,7 +35,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase1)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase1), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase1), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase1), @@ -77,7 +77,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase2)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase2), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase2), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase2), @@ -119,7 +119,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase3)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase3), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase3), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblyBase3), @@ -161,7 +161,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), @@ -202,7 +202,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), @@ -252,7 +252,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), @@ -302,7 +302,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.AssemblySetupTests), diff --git a/TUnit.Core.SourceGenerator.Tests/GlobalStaticAfterEachTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/GlobalStaticAfterEachTests.Test.verified.txt index 77903b5bd8..409059734d 100644 --- a/TUnit.Core.SourceGenerator.Tests/GlobalStaticAfterEachTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/GlobalStaticAfterEachTests.Test.verified.txt @@ -35,7 +35,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalBase1)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase1), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase1), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalBase1), @@ -77,7 +77,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalBase2)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase2), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase2), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalBase2), @@ -119,7 +119,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalBase3)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase3), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalBase3), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalBase3), @@ -161,7 +161,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), @@ -202,7 +202,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), @@ -252,7 +252,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), @@ -302,7 +302,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), + InitClassType = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.AfterTests.GlobalCleanUpTests), diff --git a/TUnit.Core.SourceGenerator.Tests/GlobalStaticBeforeEachTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/GlobalStaticBeforeEachTests.Test.verified.txt index 91d91d1afd..31100bb930 100644 --- a/TUnit.Core.SourceGenerator.Tests/GlobalStaticBeforeEachTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/GlobalStaticBeforeEachTests.Test.verified.txt @@ -35,7 +35,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalBase1)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase1), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase1), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase1), @@ -77,7 +77,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalBase2)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase2), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase2), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase2), @@ -119,7 +119,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalBase3)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase3), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase3), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalBase3), @@ -161,7 +161,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), @@ -202,7 +202,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), @@ -252,7 +252,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), @@ -302,7 +302,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), + InitClassType = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.BeforeTests.GlobalSetUpTests), diff --git a/TUnit.Core.SourceGenerator.Tests/HooksTests.DisposableFieldTests.verified.txt b/TUnit.Core.SourceGenerator.Tests/HooksTests.DisposableFieldTests.verified.txt index a3c40e6f9f..86dcf32413 100644 --- a/TUnit.Core.SourceGenerator.Tests/HooksTests.DisposableFieldTests.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/HooksTests.DisposableFieldTests.verified.txt @@ -35,7 +35,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.BeforeTestHooks[typeof(global::TUnit.TestProject.DisposableFieldTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.DisposableFieldTests), + InitClassType = typeof(global::TUnit.TestProject.DisposableFieldTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.DisposableFieldTests), @@ -77,7 +77,7 @@ public sealed class GeneratedHookRegistry global::TUnit.Core.Sources.AfterTestHooks[typeof(global::TUnit.TestProject.DisposableFieldTests)].Add( new InstanceHookMethod { - ClassType = typeof(global::TUnit.TestProject.DisposableFieldTests), + InitClassType = typeof(global::TUnit.TestProject.DisposableFieldTests), MethodInfo = new global::TUnit.Core.MethodMetadata { Type = typeof(global::TUnit.TestProject.DisposableFieldTests), From c723ee3f204bb1beab9e32874e0d4c9f2d421f87 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:26:57 +0100 Subject: [PATCH 8/8] fix: Add HookRegisteredContext and update Timeout handling in hook methods --- ..._Has_No_API_Changes.DotNet8_0.verified.txt | 65 ++++++++++--------- ..._Has_No_API_Changes.DotNet9_0.verified.txt | 52 ++++++--------- ...ary_Has_No_API_Changes.Net4_7.verified.txt | 63 +++++++++--------- 3 files changed, 85 insertions(+), 95 deletions(-) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index abb8af8980..d999e56c9c 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -781,6 +781,13 @@ namespace public int Line { get; } public int Order { get; init; } } + public class HookRegisteredContext + { + public HookRegisteredContext(. hookMethod) { } + public . HookMethod { get; } + public string HookName { get; } + public ? Timeout { get; set; } + } public static class HookRegistrationIndices { public static int GetNextAfterAssemblyHookIndex() { } @@ -1506,11 +1513,12 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } public Timeout { get; } + public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class Timing : <.Timing> @@ -2080,14 +2088,21 @@ namespace .Hooks public BeforeTestSessionHookMethod() { } public override . ExecuteAsync(.TestSessionContext context, .CancellationToken cancellationToken) { } } - public class HookMethod + public abstract class HookMethod : <.> { - public HookMethod(. instanceHookMethod) { } - public HookMethod(. staticHookMethod) { } - public .? InstanceHookMethod { get; } - public .? StaticHookMethod { get; } - public static . op_Implicit(. instanceHookMethod) { } - public static . op_Implicit(. staticHookMethod) { } + protected HookMethod() { } + public virtual .Assembly? Assembly { get; } + public .<> Attributes { get; } + [.(..PublicMethods)] + public abstract ClassType { get; } + public required . HookExecutor { get; init; } + public required .MethodMetadata MethodInfo { get; init; } + public string Name { get; } + public required int Order { get; init; } + public required int RegistrationIndex { get; init; } + public ? Timeout { get; } + public TAttribute? GetAttribute() + where TAttribute : { } } public interface IExecutableHook { @@ -2101,23 +2116,14 @@ namespace .Hooks . Discover(string sessionId, string displayName, . hookMethod); . Push(string sessionId, string displayName, . hookMethod, <.> func); } - public class InstanceHookMethod : <.>, .<.TestContext> + public class InstanceHookMethod : ., <.>, .<.TestContext> { public InstanceHookMethod() { } - public .Assembly Assembly { get; } - public .<> Attributes { get; } public ? Body { get; init; } [.(..PublicMethods)] - public required ClassType { get; init; } - public required . HookExecutor { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } + public override ClassType { get; } + public required InitClassType { init; } public . ExecuteAsync(.TestContext context, .CancellationToken cancellationToken) { } - public TAttribute? GetAttribute() - where TAttribute : { } } public class LastTestInAssemblyAdapter : .<.AssemblyHookContext> { @@ -2140,22 +2146,13 @@ namespace .Hooks public bool Execute(.ClassHookContext context, .CancellationToken cancellationToken) { } public . ExecuteAsync(.ClassHookContext context, .CancellationToken cancellationToken) { } } - public abstract class StaticHookMethod : <.> + public abstract class StaticHookMethod : ., <.> { protected StaticHookMethod() { } - public .Assembly? Assembly { get; } - public .<> Attributes { get; } - public ClassType { get; } + [.(..PublicMethods)] + public override ClassType { get; } public required string FilePath { get; init; } - public required . HookExecutor { get; init; } public required int LineNumber { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } - public TAttribute? GetAttribute() - where TAttribute : { } } public abstract class StaticHookMethod : ., <.>, . { @@ -2252,6 +2249,10 @@ namespace .Interfaces . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action); . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action); } + public interface IHookRegisteredEventReceiver : . + { + . OnHookRegistered(.HookRegisteredContext context); + } public interface IInfersType { } public interface ILastTestInAssemblyEventReceiver : . { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 42a3fb98b7..260c5e8654 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -786,6 +786,7 @@ namespace public HookRegisteredContext(. hookMethod) { } public . HookMethod { get; } public string HookName { get; } + public ? Timeout { get; set; } } public static class HookRegistrationIndices { @@ -2087,14 +2088,21 @@ namespace .Hooks public BeforeTestSessionHookMethod() { } public override . ExecuteAsync(.TestSessionContext context, .CancellationToken cancellationToken) { } } - public class HookMethod + public abstract class HookMethod : <.> { - public HookMethod(. instanceHookMethod) { } - public HookMethod(. staticHookMethod) { } - public .? InstanceHookMethod { get; } - public .? StaticHookMethod { get; } - public static . op_Implicit(. instanceHookMethod) { } - public static . op_Implicit(. staticHookMethod) { } + protected HookMethod() { } + public virtual .Assembly? Assembly { get; } + public .<> Attributes { get; } + [.(..PublicMethods)] + public abstract ClassType { get; } + public required . HookExecutor { get; init; } + public required .MethodMetadata MethodInfo { get; init; } + public string Name { get; } + public required int Order { get; init; } + public required int RegistrationIndex { get; init; } + public ? Timeout { get; } + public TAttribute? GetAttribute() + where TAttribute : { } } public interface IExecutableHook { @@ -2108,23 +2116,14 @@ namespace .Hooks . Discover(string sessionId, string displayName, . hookMethod); . Push(string sessionId, string displayName, . hookMethod, <.> func); } - public class InstanceHookMethod : <.>, .<.TestContext> + public class InstanceHookMethod : ., <.>, .<.TestContext> { public InstanceHookMethod() { } - public .Assembly Assembly { get; } - public .<> Attributes { get; } public ? Body { get; init; } [.(..PublicMethods)] - public required ClassType { get; init; } - public required . HookExecutor { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } + public override ClassType { get; } + public required InitClassType { init; } public . ExecuteAsync(.TestContext context, .CancellationToken cancellationToken) { } - public TAttribute? GetAttribute() - where TAttribute : { } } public class LastTestInAssemblyAdapter : .<.AssemblyHookContext> { @@ -2147,22 +2146,13 @@ namespace .Hooks public bool Execute(.ClassHookContext context, .CancellationToken cancellationToken) { } public . ExecuteAsync(.ClassHookContext context, .CancellationToken cancellationToken) { } } - public abstract class StaticHookMethod : <.> + public abstract class StaticHookMethod : ., <.> { protected StaticHookMethod() { } - public .Assembly? Assembly { get; } - public .<> Attributes { get; } - public ClassType { get; } + [.(..PublicMethods)] + public override ClassType { get; } public required string FilePath { get; init; } - public required . HookExecutor { get; init; } public required int LineNumber { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } - public TAttribute? GetAttribute() - where TAttribute : { } } public abstract class StaticHookMethod : ., <.>, . { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 34da9e62fb..5763f15740 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -733,6 +733,13 @@ namespace public int Line { get; } public int Order { get; init; } } + public class HookRegisteredContext + { + public HookRegisteredContext(. hookMethod) { } + public . HookMethod { get; } + public string HookName { get; } + public ? Timeout { get; set; } + } public static class HookRegistrationIndices { public static int GetNextAfterAssemblyHookIndex() { } @@ -1428,11 +1435,12 @@ namespace public static bool IsTransient(this .TestState state) { } } [(.Assembly | .Class | .Method)] - public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., . + public class TimeoutAttribute : .TUnitAttribute, .IScopedAttribute, .IScopedAttribute<.TimeoutAttribute>, ., ., . { public TimeoutAttribute(int timeoutInMilliseconds) { } public int Order { get; } public Timeout { get; } + public . OnHookRegistered(.HookRegisteredContext context) { } public . OnTestDiscovered(.DiscoveredTestContext context) { } } public class Timing : <.Timing> @@ -1975,14 +1983,20 @@ namespace .Hooks public BeforeTestSessionHookMethod() { } public override . ExecuteAsync(.TestSessionContext context, .CancellationToken cancellationToken) { } } - public class HookMethod + public abstract class HookMethod : <.> { - public HookMethod(. instanceHookMethod) { } - public HookMethod(. staticHookMethod) { } - public .? InstanceHookMethod { get; } - public .? StaticHookMethod { get; } - public static . op_Implicit(. instanceHookMethod) { } - public static . op_Implicit(. staticHookMethod) { } + protected HookMethod() { } + public virtual .Assembly? Assembly { get; } + public .<> Attributes { get; } + public abstract ClassType { get; } + public required . HookExecutor { get; init; } + public required .MethodMetadata MethodInfo { get; init; } + public string Name { get; } + public required int Order { get; init; } + public required int RegistrationIndex { get; init; } + public ? Timeout { get; } + public TAttribute? GetAttribute() + where TAttribute : { } } public interface IExecutableHook { @@ -1996,22 +2010,13 @@ namespace .Hooks . Discover(string sessionId, string displayName, . hookMethod); . Push(string sessionId, string displayName, . hookMethod, <.> func); } - public class InstanceHookMethod : <.>, .<.TestContext> + public class InstanceHookMethod : ., <.>, .<.TestContext> { public InstanceHookMethod() { } - public .Assembly Assembly { get; } - public .<> Attributes { get; } public ? Body { get; init; } - public required ClassType { get; init; } - public required . HookExecutor { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } + public override ClassType { get; } + public required InitClassType { init; } public . ExecuteAsync(.TestContext context, .CancellationToken cancellationToken) { } - public TAttribute? GetAttribute() - where TAttribute : { } } public class LastTestInAssemblyAdapter : .<.AssemblyHookContext> { @@ -2031,22 +2036,12 @@ namespace .Hooks public bool Execute(.ClassHookContext context, .CancellationToken cancellationToken) { } public . ExecuteAsync(.ClassHookContext context, .CancellationToken cancellationToken) { } } - public abstract class StaticHookMethod : <.> + public abstract class StaticHookMethod : ., <.> { protected StaticHookMethod() { } - public .Assembly? Assembly { get; } - public .<> Attributes { get; } - public ClassType { get; } + public override ClassType { get; } public required string FilePath { get; init; } - public required . HookExecutor { get; init; } public required int LineNumber { get; init; } - public required .MethodMetadata MethodInfo { get; init; } - public string Name { get; } - public required int Order { get; init; } - public required int RegistrationIndex { get; init; } - public ? Timeout { get; } - public TAttribute? GetAttribute() - where TAttribute : { } } public abstract class StaticHookMethod : ., <.>, . { @@ -2143,6 +2138,10 @@ namespace .Interfaces . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action); . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action); } + public interface IHookRegisteredEventReceiver : . + { + . OnHookRegistered(.HookRegisteredContext context); + } public interface IInfersType { } public interface ILastTestInAssemblyEventReceiver : . {