You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Reflection mode: SourceRegistrar.IsEnabled reset to true by module initializers of late-loaded assemblies #7089
In reflection mode, SourceRegistrar.IsEnabled can switch back to true partway through a run. The engine sets it to false at startup, but any assembly compiled with the TUnit source generator that is first touched later (for example TUnit.Playwright, TUnit.FsCheck, or a user's shared test library) runs its generated module initializer, which sets SourceRegistrar.IsEnabled = true. Code that checks SourceRegistrar.IsEnabled after that point sees the wrong mode.
The clearest visible effect: in reflection mode, closed generic test classes fail with
InvalidOperationException: Cannot use reflection-based hook discovery when source generation is enabled
Steps to reproduce
Use a test project that references a library built with the TUnit source generator and uses one of its attributes in a test, for example tests/TUnit.UnitTests, which references TUnit.Playwright.
Run it in reflection mode: dotnet run -f net10.0 -- --reflection --treenode-filter "/*/*/GenericRepro*/*".
Expected behaviour
The test passes, and SourceRegistrar.IsEnabled stays false for the whole reflection-mode run, as it does in source-generation mode, where the same test passes.
Actual behaviour
The test fails with the InvalidOperationException above. A test that reads SourceRegistrar.IsEnabled during a --reflection run gets True.
Root cause
InfrastructureGenerator emits a [ModuleInitializer] into every assembly that has the generator, and the first thing it does is global::TUnit.Core.SourceRegistrar.IsEnabled = true;. TUnitServiceProvider then sets the property to the mode it actually chose (ExecutionModeHelper.IsSourceGenerationMode). Nothing stops a later write. A module initializer runs the first time its module is touched, which can be long after startup. I traced the write in TUnit.UnitTests --reflection: reflection discovery called GetCustomAttributes on a test method, which created an attribute from TUnit.Playwright and so ran that assembly's module initializer. This happened after the engine had set the property to false.
Impact
Everything that reads SourceRegistrar.IsEnabled during discovery or execution can see the wrong mode once such an assembly loads:
ReflectionHookDiscoveryService.DiscoverInstanceHooksForType, which TestBuilder calls for every closed generic test class, throws, so those tests fail in reflection mode.
PropertyInjectionPlanBuilder.Build takes the source-generated property-injection path in reflection mode.
ModeDetector.IsSourceGenerationAvailable and user code that branches on SourceRegistrar.IsEnabled get the wrong answer.
Whether a run is affected depends on which assemblies happen to load, and when, so it is order dependent.
Activity
fix(core): keep the engine-selected execution mode when late assembli…