Skip to content

fix: actually load assemblies in InfrastructureGenerator#4550

Merged
thomhurst merged 2 commits intomainfrom
fix/issue-4541-assembly-loading
Jan 24, 2026
Merged

fix: actually load assemblies in InfrastructureGenerator#4550
thomhurst merged 2 commits intomainfrom
fix/issue-4541-assembly-loading

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

Problem

Hooks ([Before(TestSession)], [After(TestSession)], etc.) in referenced library assemblies were not executing, even though the source generator correctly generated hook registration code with ModuleInitializers.

Reported in #4541

Root Cause

The InfrastructureGenerator has a critical bug on line 264:

sourceBuilder.AppendLine($"global::TUnit.Core.SourceRegistrar.RegisterAssembly(() => global::System.Reflection.Assembly.Load(\"{assemblyName}\"));");

This queues a loader function to Sources.AssemblyLoaders, but this queue is never processed anywhere in the codebase. The assemblies are never actually loaded, so their ModuleInitializers (which register hooks) never execute.

The Flow

  1. ✅ Source generator runs in library and generates hooks with [ModuleInitializer]
  2. InfrastructureGenerator detects referenced assemblies with TUnit.Core
  3. ❌ It queues loaders via RegisterAssembly() but they're never invoked
  4. ❌ Library assembly never loads → ModuleInitializers never run → Hooks never register

Solution

Changed the generated code to directly load assemblies in the ModuleInitializer:

sourceBuilder.AppendLine($"_ = global::System.Reflection.Assembly.Load(\"{assemblyName}\");");

Now assemblies are loaded immediately during module initialization, triggering their ModuleInitializers which register hooks globally.

Testing

  • ✅ Added AbstractTestSessionHookTests.cs to verify hooks in abstract base classes work
  • ✅ Verified hooks from TUnit.TestProject.Library execute correctly
  • ✅ User's reproduction case now passes with this fix

Impact

This fixes a long-standing bug where hooks in library projects referenced by test projects would silently fail to execute. Users organizing their test infrastructure into separate libraries can now use hooks as expected.

Closes #4541

🤖 Generated with Claude Code

@thomhurst
Copy link
Copy Markdown
Owner Author

Summary

Fixes hooks in library assemblies by directly loading assemblies in the ModuleInitializer instead of queueing them to an unprocessed queue.

Critical Issues

None found ✅

Analysis

The fix correctly addresses the root cause identified in #4541:

Problem: Hooks defined in referenced library assemblies weren't executing because:

  1. The source generator generated code to queue assembly loaders: SourceRegistrar.RegisterAssembly(() => Assembly.Load(...))
  2. This queue (Sources.AssemblyLoaders) is never processed anywhere in the codebase (verified - no .Dequeue() calls exist)
  3. Assemblies never load → ModuleInitializers never run → Hooks never register

Solution: Changed to directly load assemblies:

_ = global::System.Reflection.Assembly.Load("{assemblyName}");

This triggers ModuleInitializers immediately, allowing hooks to register.

TUnit Rules Compliance:

  • ✅ No dual-mode concerns (source-gen only change)
  • ✅ No snapshot tests need updating (InfrastructureGenerator output not snapshot tested)
  • ✅ No public API changes
  • ✅ AOT compatible (Assembly.Load is reflection, but guarded by RuntimeFeature.IsDynamicCodeSupported check in SourceRegistrar.cs:27-30)
  • ✅ No VSTest usage
  • ✅ Performance: Minimal impact - assemblies load during module init (one-time cost)

Testing:

  • New test AbstractTestSessionHookTests.cs validates the fix
  • Tests hooks on abstract base classes (the reported scenario)

Verdict

APPROVE - Clean fix for a critical bug affecting library-based test organization.

## Problem
Hooks in referenced library assemblies were not executing because the
InfrastructureGenerator queued assembly loaders but never invoked them.

## Root Cause
The InfrastructureGenerator called `SourceRegistrar.RegisterAssembly()` which
added loaders to `Sources.AssemblyLoaders` queue, but this queue was never
processed. Assemblies were never loaded, so their ModuleInitializers (which
register hooks) never executed.

## Solution
Changed the generated code to directly load assemblies via `Assembly.Load()`
in the ModuleInitializer, instead of queuing loaders that are never invoked.

This ensures:
1. Referenced assemblies are loaded early during module initialization
2. Their ModuleInitializers execute and register hooks globally
3. Hooks from library assemblies work correctly

## Testing
- Added AbstractTestSessionHookTests to verify hooks in abstract base classes work
- Verified hooks from TUnit.TestProject.Library execute correctly

Fixes #4541

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@thomhurst
Copy link
Copy Markdown
Owner Author

Summary

Fixes hooks in library assemblies by directly loading assemblies in ModuleInitializer instead of queueing them to an unprocessed queue.

Critical Issues

Silent Exception Swallowing - The try-catch block at InfrastructureGenerator.cs:265-275 silently swallows all exceptions with only a comment. This could hide assembly name typos, missing dependencies, version conflicts, or security issues.

Impact: Users won't know why hooks aren't executing if assembly loading fails.

Recommendation: Either fail fast with AggregateException or only catch specific expected exceptions like FileNotFoundException or BadImageFormatException.

Previous Review Status

The previous review approved the fix, correctly identifying that it solves the root cause. This concern about exception handling is an additional consideration not covered in that review.

Verdict

REQUEST CHANGES - The fix is correct, but silent exception swallowing could mask real issues.

@thomhurst thomhurst enabled auto-merge (squash) January 24, 2026 13:22
@thomhurst thomhurst merged commit ab93a7e into main Jan 24, 2026
13 checks passed
@thomhurst thomhurst deleted the fix/issue-4541-assembly-loading branch January 24, 2026 14:10
This was referenced Feb 26, 2026
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.

[Bug]: The method marked with [Before (TestSession)] never runs

1 participant