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
Failed data-row tests drop builder-context events, so DI scopes of failed rows are never disposed #7326
When a data row fails after its data source factories ran, the engine builds a failed test with a fresh TestBuilderContext (CreateFailedTestContext in src/TUnit.Engine/Building/TestBuilder.cs). That context has no events, so anything the data sources subscribed on TestBuilderContext.Current.Events while the row was fetched is dropped. For example, the scope a DependencyInjectionDataSourceAttribute<TScope> creates is never disposed.
#7205 routes these events to tests built from successful rows. Failed-row tests still track the row's objects for disposal (#7123), but they do not receive the row's events.
Where
CreateFailedTestForDataRowError / CreateFailedTestForDataGenerationError → CreateFailedTestContext in TestBuilder.cs. The callers have the per-iteration context (contextAccessor.Current) available, and the eager path also has unclaimedClassRow.TakeEvents().
Repro
Use a DependencyInjectionDataSourceAttribute<TScope> on a class whose method rows fail generic resolution (or any row that hits CreateFailedTestForDataRowError after the class factory ran). Count created and disposed scopes in a dependent test: the failed row's scope is created but not disposed.
Expected
A failed-row test raises the OnDispose events its row's data sources subscribed, the same way it disposes the objects it tracks.
Rechecked the earlier blocker against freshly fetched origin/main at 10a0af7. It no longer holds: #7417 was closed and split into #7476 (merged as e30a1bb) and #7521. Both native prerequisites, #7308 and #7381, are closed.
#7476 already delivers the remaining failed-row fix. In src/TUnit.Engine/Building/TestBuilder.cs, ordinary row failures pass contextAccessor.Current into CreateFailedTestForDataRowError; CreateFailedTestContext copies the owning StateBag and clones its events (lines 1919–1934). The previously missing eager empty-row generic-resolution failure now creates skippedRowContext via unclaimedClassRow.CreateEnumerationFailureContext and passes it into CreateFailedTestForDataGenerationError (lines 505–530). Thus the row callbacks and cleanup follow the synthetic failed test rather than being dropped.
tests/TUnit.UnitTests/ClassRowCleanupTests.cs:371, NonRunnableRowsPreservePublicCallbacksAndState, covers both a throwing factory and an empty skipped row, asserting owning state and all four public callbacks including OnDispose. #7476 records generated/reflection UnitTests (823 passed, 3 skipped per mode), failed/static/deferred cleanup regressions and successful Native AOT publishing; its hosted checks have no failures. The merge commit is an ancestor of current main.
Closing as already implemented by #7476. This audit inspected current source, regression coverage, ancestry and hosted check state; no code changes or local tests were needed. No open replacement PR, remaining acceptance criterion, human decision or external dependency requires deferral.
Activity
fix(engine): dispose failed rows' scopes and leave unowned DI service…
fix(engine): dispose failed rows' scopes and leave unowned DI service…
fix(engine): dispose failed rows' scopes and leave unowned DI service…
thomhurst commentedon Nov 3, 2026
Rechecked the earlier blocker against freshly fetched origin/main at 10a0af7. It no longer holds: #7417 was closed and split into #7476 (merged as e30a1bb) and #7521. Both native prerequisites, #7308 and #7381, are closed.
#7476 already delivers the remaining failed-row fix. In src/TUnit.Engine/Building/TestBuilder.cs, ordinary row failures pass contextAccessor.Current into CreateFailedTestForDataRowError; CreateFailedTestContext copies the owning StateBag and clones its events (lines 1919–1934). The previously missing eager empty-row generic-resolution failure now creates skippedRowContext via unclaimedClassRow.CreateEnumerationFailureContext and passes it into CreateFailedTestForDataGenerationError (lines 505–530). Thus the row callbacks and cleanup follow the synthetic failed test rather than being dropped.
tests/TUnit.UnitTests/ClassRowCleanupTests.cs:371, NonRunnableRowsPreservePublicCallbacksAndState, covers both a throwing factory and an empty skipped row, asserting owning state and all four public callbacks including OnDispose. #7476 records generated/reflection UnitTests (823 passed, 3 skipped per mode), failed/static/deferred cleanup regressions and successful Native AOT publishing; its hosted checks have no failures. The merge commit is an ancestor of current main.
Closing as already implemented by #7476. This audit inspected current source, regression coverage, ancestry and hosted check state; no code changes or local tests were needed. No open replacement PR, remaining acceptance criterion, human decision or external dependency requires deferral.