Skip to content

Commit f576397

Browse files
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 <thomhurst@users.noreply.github.com>
1 parent dc3e186 commit f576397

File tree

6 files changed

+42
-107
lines changed

6 files changed

+42
-107
lines changed

TUnit.Core/Contexts/HookRegisteredContext.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ namespace TUnit.Core;
77
/// </summary>
88
public class HookRegisteredContext
99
{
10-
private readonly object _hookMethod;
11-
private readonly string _hookName;
1210
private TimeSpan? _timeout;
1311

14-
public StaticHookMethod? StaticHookMethod => _hookMethod as StaticHookMethod;
15-
public InstanceHookMethod? InstanceHookMethod => _hookMethod as InstanceHookMethod;
16-
public string HookName => _hookName;
12+
public HookMethod HookMethod { get; }
13+
public string HookName => HookMethod.Name;
1714

1815
/// <summary>
1916
/// Gets or sets the timeout for this hook
@@ -24,15 +21,8 @@ public TimeSpan? Timeout
2421
set => _timeout = value;
2522
}
2623

27-
public HookRegisteredContext(StaticHookMethod hookMethod)
24+
public HookRegisteredContext(HookMethod hookMethod)
2825
{
29-
_hookMethod = hookMethod;
30-
_hookName = hookMethod.Name;
31-
}
32-
33-
public HookRegisteredContext(InstanceHookMethod hookMethod)
34-
{
35-
_hookMethod = hookMethod;
36-
_hookName = hookMethod.Name;
26+
HookMethod = hookMethod;
3727
}
3828
}

TUnit.Core/Hooks/HookMethod.cs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
1-
namespace TUnit.Core.Hooks;
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Reflection;
3+
using TUnit.Core.Extensions;
4+
using TUnit.Core.Interfaces;
25

3-
public class HookMethod
6+
namespace TUnit.Core.Hooks;
7+
8+
#if !DEBUG
9+
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
10+
#endif
11+
public abstract record HookMethod
412
{
5-
public StaticHookMethod? StaticHookMethod { get; }
6-
public InstanceHookMethod? InstanceHookMethod { get; }
13+
public required MethodMetadata MethodInfo { get; init; }
14+
15+
[field: AllowNull, MaybeNull]
16+
public string Name => field ??= $"{ClassType.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})";
17+
18+
public abstract Type ClassType { get; }
19+
public virtual Assembly? Assembly => ClassType?.Assembly;
20+
21+
[field: AllowNull, MaybeNull]
22+
public IEnumerable<Attribute> Attributes => field ??= MethodInfo.GetCustomAttributes();
23+
24+
public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute => Attributes.OfType<TAttribute>().FirstOrDefault();
725

8-
public HookMethod(InstanceHookMethod instanceHookMethod)
9-
{
10-
InstanceHookMethod = instanceHookMethod;
11-
}
26+
/// <summary>
27+
/// Gets the timeout for this hook method. This will be set during hook registration
28+
/// by the event receiver infrastructure, falling back to the default 5-minute timeout.
29+
/// </summary>
30+
public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5);
1231

13-
public HookMethod(StaticHookMethod staticHookMethod)
14-
{
15-
StaticHookMethod = staticHookMethod;
16-
}
32+
public required IHookExecutor HookExecutor { get; init; }
1733

18-
public static implicit operator HookMethod(InstanceHookMethod instanceHookMethod) => new(instanceHookMethod);
19-
public static implicit operator HookMethod(StaticHookMethod staticHookMethod) => new(staticHookMethod);
34+
public required int Order { get; init; }
35+
36+
public required int RegistrationIndex { get; init; }
2037
}

TUnit.Core/Hooks/InstanceHookMethod.cs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,10 @@ namespace TUnit.Core.Hooks;
88
#if !DEBUG
99
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
1010
#endif
11-
public record InstanceHookMethod : IExecutableHook<TestContext>
11+
public record InstanceHookMethod : HookMethod, IExecutableHook<TestContext>
1212
{
1313
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
14-
public required Type ClassType { get; init; }
15-
public Assembly Assembly => ClassType.Assembly;
16-
public required MethodMetadata MethodInfo { get; init; }
17-
18-
[field: AllowNull, MaybeNull]
19-
public string Name => field ??= $"{ClassType.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})";
20-
21-
[field: AllowNull, MaybeNull] public IEnumerable<Attribute> Attributes => field ??= MethodInfo.GetCustomAttributes();
22-
23-
public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute => Attributes.OfType<TAttribute>().FirstOrDefault();
24-
25-
/// <summary>
26-
/// Gets or sets the timeout for this hook method. This will be set during hook registration
27-
/// by the event receiver infrastructure, falling back to the default 5-minute timeout.
28-
/// </summary>
29-
public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5);
30-
31-
public required IHookExecutor HookExecutor { get; init; }
32-
33-
public required int Order { get; init; }
34-
35-
public required int RegistrationIndex { get; init; }
14+
public override Type ClassType { get; init; }
3615

3716
public Func<object, TestContext, CancellationToken, ValueTask>? Body { get; init; }
3817

TUnit.Core/Hooks/StaticHookMethod.cs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,9 @@ public abstract record StaticHookMethod<T> : StaticHookMethod, IExecutableHook<T
1717
#if !DEBUG
1818
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
1919
#endif
20-
public abstract record StaticHookMethod
20+
public abstract record StaticHookMethod : HookMethod
2121
{
22-
public required MethodMetadata MethodInfo { get; init; }
23-
24-
[field: AllowNull, MaybeNull]
25-
public string Name => field ??= $"{MethodInfo.Class.Type.Name}.{MethodInfo.Name}({string.Join(", ", MethodInfo.Parameters.Select(x => x.Name))})";
26-
27-
public Type ClassType => MethodInfo.Class.Type;
28-
public Assembly? Assembly => ClassType?.Assembly;
29-
30-
[field: AllowNull, MaybeNull]
31-
public IEnumerable<Attribute> Attributes => field ??= MethodInfo.GetCustomAttributes();
32-
33-
public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute => Attributes.OfType<TAttribute>().FirstOrDefault();
34-
35-
/// <summary>
36-
/// Gets the timeout for this hook method. This will be set during hook registration
37-
/// by the event receiver infrastructure, falling back to the default 5-minute timeout.
38-
/// </summary>
39-
public TimeSpan? Timeout { get; internal set; } = TimeSpan.FromMinutes(5);
40-
41-
public required IHookExecutor HookExecutor { get; init; }
42-
43-
public required int Order { get; init; }
44-
45-
public required int RegistrationIndex { get; init; }
22+
public override Type ClassType => MethodInfo.Class.Type;
4623

4724
public required string FilePath { get; init; }
4825

TUnit.Engine/Services/EventReceiverOrchestrator.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -200,22 +200,7 @@ public async ValueTask InvokeTestDiscoveryEventReceiversAsync(TestContext contex
200200
public async ValueTask InvokeHookRegistrationEventReceiversAsync(HookRegisteredContext hookContext, CancellationToken cancellationToken)
201201
{
202202
// Get event receivers from the hook method's attributes
203-
IEnumerable<Attribute> attributes;
204-
205-
if (hookContext.StaticHookMethod != null)
206-
{
207-
attributes = hookContext.StaticHookMethod.Attributes;
208-
}
209-
else if (hookContext.InstanceHookMethod != null)
210-
{
211-
attributes = hookContext.InstanceHookMethod.Attributes;
212-
}
213-
else
214-
{
215-
return; // No hook method to process
216-
}
217-
218-
var eventReceivers = attributes
203+
var eventReceivers = hookContext.HookMethod.Attributes
219204
.OfType<IHookRegisteredEventReceiver>()
220205
.OrderBy(r => r.Order)
221206
.ToList();

TUnit.Engine/Services/HookCollectionService.cs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public HookCollectionService(EventReceiverOrchestrator eventReceiverOrchestrator
2828
_eventReceiverOrchestrator = eventReceiverOrchestrator;
2929
}
3030

31-
private async Task ProcessHookRegistrationAsync(object hookMethod, CancellationToken cancellationToken = default)
31+
private async Task ProcessHookRegistrationAsync(HookMethod hookMethod, CancellationToken cancellationToken = default)
3232
{
3333
// Only process each hook once
3434
if (!_processedHooks.TryAdd(hookMethod, true))
@@ -38,20 +38,7 @@ private async Task ProcessHookRegistrationAsync(object hookMethod, CancellationT
3838

3939
try
4040
{
41-
HookRegisteredContext context;
42-
43-
if (hookMethod is StaticHookMethod staticHook)
44-
{
45-
context = new HookRegisteredContext(staticHook);
46-
}
47-
else if (hookMethod is InstanceHookMethod instanceHook)
48-
{
49-
context = new HookRegisteredContext(instanceHook);
50-
}
51-
else
52-
{
53-
return; // Unknown hook type
54-
}
41+
var context = new HookRegisteredContext(hookMethod);
5542

5643
await _eventReceiverOrchestrator.InvokeHookRegistrationEventReceiversAsync(context, cancellationToken);
5744
}

0 commit comments

Comments
 (0)