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
Follow-up from PR #6556 review (Codex P1 finding on injected-property event receivers).
Problem
IFirstTestInTestSessionEventReceiver / IFirstTestInAssemblyEventReceiver invocations are one-shot: EventReceiverOrchestrator memoizes the invocation task (keyed on session/assembly) and enumerates _registry.GetReceiversOfType<T>()at the moment the first gated test reaches it. Object-scoped receivers (class instances, constructor/method arguments, injected properties, and even attributes of later tests) are only registered into the EventReceiverRegistry when their own test enters TestCoordinator.ExecuteTestAsync.
So when receiver R (any object-scoped source) belongs to a test that is not first, and another receiver of the same interface was registered by an earlier test, the earlier test's invocation memoizes the one-shot event with a receiver list that predates R — R's callback never fires.
Notes on scope:
The gate-false path (HasFirstTestInSessionReceivers() etc.) returns without memoizing, so when the late-registered receiver is the only one of its interface, it still fires — just parented to its own test rather than the chronologically first test. The miss requires a second same-interface receiver earlier in the run.
This is pre-existing and source-agnostic — it affected class-instance receivers (RegisterClassInstanceReceiver runs even later, in PrepareTest) and ctor/method-argument receivers before fix: run event receivers on injected properties #6556; that PR merely brought injected properties to parity with the other eligible-object sources for per-test events.
A regression test can't currently be written reliably: TUnit.TestProject already contains IFirstTestInAssemblyEventReceiver implementors, so any "receiver on a later-running class" test is racy in full-suite runs — which is itself a demonstration of the problem.
Proposed direction
Perform an eager RegisterReceivers pass over all executable test contexts before execution begins (natural spot: alongside EventReceiverOrchestrator.InitializeTestCounts), so the registry is complete before any one-shot event can be memoized. The receiver caches are already built once per test during registration (GetTestRegisteredReceivers in TestFilterService), so the incremental cost is bounded; needs measuring against large suites since it moves work ahead of first-test start.
Context
Follow-up from PR #6556 review (Codex P1 finding on injected-property event receivers).
Problem
IFirstTestInTestSessionEventReceiver/IFirstTestInAssemblyEventReceiverinvocations are one-shot:EventReceiverOrchestratormemoizes the invocation task (keyed on session/assembly) and enumerates_registry.GetReceiversOfType<T>()at the moment the first gated test reaches it. Object-scoped receivers (class instances, constructor/method arguments, injected properties, and even attributes of later tests) are only registered into theEventReceiverRegistrywhen their own test entersTestCoordinator.ExecuteTestAsync.So when receiver R (any object-scoped source) belongs to a test that is not first, and another receiver of the same interface was registered by an earlier test, the earlier test's invocation memoizes the one-shot event with a receiver list that predates R — R's callback never fires.
Notes on scope:
HasFirstTestInSessionReceivers()etc.) returns without memoizing, so when the late-registered receiver is the only one of its interface, it still fires — just parented to its own test rather than the chronologically first test. The miss requires a second same-interface receiver earlier in the run.RegisterClassInstanceReceiverruns even later, inPrepareTest) and ctor/method-argument receivers before fix: run event receivers on injected properties #6556; that PR merely brought injected properties to parity with the other eligible-object sources for per-test events.TUnit.TestProjectalready containsIFirstTestInAssemblyEventReceiverimplementors, so any "receiver on a later-running class" test is racy in full-suite runs — which is itself a demonstration of the problem.Proposed direction
Perform an eager
RegisterReceiverspass over all executable test contexts before execution begins (natural spot: alongsideEventReceiverOrchestrator.InitializeTestCounts), so the registry is complete before any one-shot event can be memoized. The receiver caches are already built once per test during registration (GetTestRegisteredReceiversinTestFilterService), so the incremental cost is bounded; needs measuring against large suites since it moves work ahead of first-test start./cc PR #6556