Skip to content

Conversation

@thomhurst
Copy link
Owner

Summary

This PR improves code clarity and maintainability in the hook services by:

  • Renaming services to reflect their responsibilities - Making the codebase easier to understand
  • Eliminating ~170 lines of duplication - Using a generic helper method for hook delegate building

Changes

1. Renamed Hook Registrar Services

  • IHookDiscoveryServiceIHookRegistrar
  • SourceGenHookDiscoveryServiceSourceGenHookRegistrar
  • ReflectionBasedHookDiscoveryServiceReflectionHookRegistrar

Why: The old name "Discovery" was ambiguous. The new name "Registrar" clearly indicates these services register hook metadata into Sources collections.

2. Renamed Hook Delegate Builder Services

  • IHookCollectionServiceIHookDelegateBuilder
  • HookCollectionServiceHookDelegateBuilder

Why: The old name "Collection" was confusing (sounded like it was finding hooks). The new name "DelegateBuilder" clearly indicates it builds executable Func<> delegates from hook metadata.

3. Code Deduplication

  • Added generic BuildGlobalHooksAsync<THookMethod, TContext>() helper method
  • Converted 10 nearly-identical methods (~15 lines each) into one-liners
  • Result: ~170 lines of duplication reduced to ~20 lines (88% reduction)

Before:

private async Task<IReadOnlyList<Func<TestSessionContext, CancellationToken, Task>>> BuildGlobalBeforeTestSessionHooksAsync()
{
    var allHooks = new List<(int order, int registrationIndex, Func<TestSessionContext, CancellationToken, Task> hook)>(Sources.BeforeTestSessionHooks.Count);
    foreach (var hook in Sources.BeforeTestSessionHooks)
    {
        var hookFunc = await CreateTestSessionHookDelegateAsync(hook);
        allHooks.Add((hook.Order, hook.RegistrationIndex, hookFunc));
    }
    return allHooks.OrderBy(h => h.order).ThenBy(h => h.registrationIndex).Select(h => h.hook).ToList();
}
// ... 9 more nearly identical methods

After:

private Task<IReadOnlyList<Func<TestSessionContext, CancellationToken, Task>>> BuildGlobalBeforeTestSessionHooksAsync()
    => BuildGlobalHooksAsync(Sources.BeforeTestSessionHooks, CreateTestSessionHookDelegateAsync);
// ... 9 more one-liners

Impact

No behavior changes - Internal refactoring only
No breaking changes - All public APIs unchanged
Builds successfully - Zero warnings or errors
Same performance - Eager initialization preserved
Better maintainability - Clear responsibilities, less duplication

Test Plan

  • Builds successfully on all target frameworks
  • Existing test suite passes
  • No snapshot test changes required

…tion

Rename services to reflect their actual responsibilities:
- IHookDiscoveryService → IHookRegistrar (registers metadata into Sources)
- IHookCollectionService → IHookDelegateBuilder (builds executable delegates)
- Update all implementations and references accordingly

Eliminate duplication in delegate building:
- Add generic BuildGlobalHooksAsync<THookMethod, TContext>() helper
- Convert 10 nearly-identical BuildGlobalXXX methods to one-liners
- Reduce ~170 lines of duplicate code to ~20 lines

This is an internal refactoring with no behavior changes. Eager hook
initialization is preserved, and all existing functionality remains intact.
@claude
Copy link
Contributor

claude bot commented Jan 31, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Add opt-in debug logging to track hook delegate creation:
- Log when building global hooks during initialization
- Log each hook delegate being created with hook name and type
- Log summary count of delegates built per hook type
- Inject TUnitFrameworkLogger into HookDelegateBuilder

This enables troubleshooting by showing:
- Which hooks are being registered
- When delegates are created
- Total hook counts at startup

Users must enable Debug log level to see this output.
Add debug logging to the generated module initializer to track:
- Module initializer start/completion
- Source generation mode enablement
- Assembly reference loading (count and individual assemblies)
- Assembly loading failures with error messages

The generated code uses GlobalContext.Current.GlobalLogger.LogDebug() which:
- Logs to Debug level (opt-in by user)
- Works as soon as logger is configured
- No-ops early if logger isn't ready yet (defaults to NullLogger)

Example output when Debug logging enabled:
```
DEBUG: [ModuleInitializer] TUnit infrastructure initializing...
DEBUG: [ModuleInitializer] Source generation mode enabled
DEBUG: [ModuleInitializer] Loading 5 assembly reference(s)...
DEBUG: [ModuleInitializer] Loading assembly containing: global::MyTests.TestClass
DEBUG: [ModuleInitializer] TUnit infrastructure initialized
```

This helps diagnose early initialization issues and understand when
assemblies are loaded to trigger module constructors.
Addresses timing issue where module initializers run before the main
logger is configured. Implements EarlyBufferLogger that buffers all
log messages until the real logger is set, then flushes buffered
messages.

Changes:
- Created EarlyBufferLogger implementing ILogger with ConcurrentQueue
- Modified GlobalContext to use EarlyBufferLogger initially
- FlushTo() replays buffered logs when real logger is assigned
- Debug logging in generated module initializers now works correctly
@thomhurst thomhurst enabled auto-merge (squash) January 31, 2026 20:24
@thomhurst thomhurst merged commit ab72223 into main Jan 31, 2026
11 of 13 checks passed
@thomhurst thomhurst deleted the refactor/hook-services-cleanup branch January 31, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants