Repository navigation
Conversation
|
@codex review |
Findings
|
| Layer / File(s) | Summary |
|---|---|
Scope ownership and object tracking src/TUnit.Core/Attributes/TestData/DependencyInjectionDataSourceSourceAttribute.cs, src/TUnit.Core/Tracking/ObjectTracker.cs, src/TUnit.Core/Discovery/ObjectGraphDiscoverer.cs, tests/TUnit.TestProject/Bugs/_7205/DependencyInjectionScopeDisposalTests.cs, docs/docs/writing-tests/dependency-injection.md |
Disposable scopes mark resolved objects as externally owned. Nested tracking carries ownership through initializer values. Tests and documentation describe scope ownership and disposal behavior. |
Row event propagation src/TUnit.Core/TestBuilderContext.cs, src/TUnit.Core/AsyncEvent.cs, src/TUnit.Engine/Building/TestBuilder.cs, src/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs, tests/TUnit.UnitTests/AsyncEventDetachTests.cs, tests/TUnit.Engine.Tests/Issue7205Tests.cs, tests/TUnit.TestProject/Bugs/_7205/* |
Builder contexts share event sources. Batch and streaming paths transfer row events into test contexts, including skipped and failed rows. Event detachment preserves handlers for retained rows. Regression tests cover row scopes and excluded tests. |
Test and session callback lifecycle src/TUnit.Core/AbstractExecutableTest.cs, src/TUnit.Engine/Services/TestExecution/*, src/TUnit.Engine/Scheduling/*, src/TUnit.Engine/Services/StaticPropertyHandler.cs, src/TUnit.Engine/TestDiscoveryService.cs, src/TUnit.Engine/Framework/TUnitServiceProvider.cs, src/TUnit.Engine/TestSessionCoordinator.cs, src/TUnit.Core/Models/TestSessionContext.cs, src/TUnit.Core/Interfaces/ITestEvents.cs, docs/docs/migration/testcontext-interface-organization.md, tests/TUnit.UnitTests/StaticPropertyScopeDisposalTests.cs, tests/TUnit.UnitTests/DeferredDiscoveryCleanupTests.cs |
Test callbacks run once after the final attempt and on covered paths for tests that do not start. Static-property and deferred discovery callbacks run during session cleanup. Tests cover callback exceptions and session isolation. Documentation describes callback timing and tests that do not run. |
Estimated code review effort: 4 (Complex) | ~60 minutes
Change: Bug fix · Severity of issue fixed: Medium
Sequence Diagram(s)
sequenceDiagram
participant DependencyInjectionDataSourceAttribute
participant TestBuilder
participant TestBuilderContext
participant TestCoordinator
participant TestOnDisposeRaiser
participant Scope
DependencyInjectionDataSourceAttribute->>TestBuilderContext: Register scope disposal callback
TestBuilder->>TestBuilderContext: Transfer row events into test context
TestCoordinator->>TestOnDisposeRaiser: Raise callbacks after final attempt
TestOnDisposeRaiser->>Scope: Invoke disposal callback
Possibly related issues
- thomhurst/TUnit#7356 — The change handles session disposal for static-property scopes but does not remove the repeated property resolution described by the issue.
- thomhurst/TUnit#7338 — The change raises callbacks for tests skipped because a dependency failed.
- thomhurst/TUnit#7326 — The change transfers row events to failed-test contexts, preserving their disposal callbacks.
Possibly related PRs
- thomhurst/TUnit#7342 — Both changes modify object graph tracking and disposal behavior.
- thomhurst/TUnit#7339 — Both changes affect class-row fetching and reuse in
TestBuilder.
Merge Risk: 
Low · previous review 0eff2
-
The remaining confirmed issue is limited to retained contexts in the new tests. It warrants cleanup but does not block merging.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Linked Issues check
-
Explanation
The PR meets [
#7205] and [#7326].TestBuilderContextcarries row-fetch events to successful and failed tests, including class-row first fetches. Cleanup raisesOnDisposefor retries, skipped tests, failed rows, filtered tests, and bypass paths. DI scope ownership prevents duplicate service disposal. Tests cover source-generated and reflection modes, scope disposal, retries, skips, failed rows, and cleanup paths.
Full details: Out of Scope Changes check
-
Explanation
The changes stay within [
#7205] and [#7326]. Event propagation, disposal timing, failed-row cleanup, ownership handling, session cleanup, documentation, and supporting tests directly implement or protect the required disposal behavior. No unrelated change is demonstrated by the reviewed summary.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 48.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 47 functions across 25 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
A rabbit checks each scope at night,
And marks who owns each service right.
Row events find the tests they seek,
While callbacks run once, not twice a week.
When sessions close, the scopes depart,
The bunny files a tidy chart.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Review of #7328I read the diff and did not run the tests. I found no blocking issues. What looks good
Non-blocking suggestions
The follow-ups #7326 and #7327 are reasonable to defer. Overall this is a clean, well-tested fix. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fff2749501
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
[High impact] The PR appears safe to merge; no new actionable issue was found. SummaryThe PR carries data-source events to their tests and keeps DI scopes alive through retries. The latest changes also release scopes created for temporary instances and reflection discovery.
|
|
Dispositions for the Claude review of fff2749:
The same push also fixes the retry and skipped-test findings from Codex and Greptile: |
|
@codex review |
|
Review of #7328 The fix is sound. Sharing the per-iteration context's events lazily with the test-specific context, and carrying the first class-row fetch's events with Concern:
Neither is mentioned in the PR description. Please call it out, and confirm that no existing test or doc depends on per-attempt or before-instance-dispose ordering. Smaller points
Nothing blocking. Please just document the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16302801f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1630280 to
59045c9
Compare
ReviewThe approach is sound. Sharing events lazily, with Points to check:
I don't see blocking problems. |
|
Dispositions for the Claude reviews of 1630280 and 59045c9. None of these blocked the merge.
Also new in 4805990: Codex found that the object tracker disposed scope-owned services a second time. The scope now owns them exclusively (see the resolved thread). |
|
@codex review |
|
Review of PR 7328 The fix is sound. Events subscribed while a row is fetched now reach the test built from that row, and the lazy ShareEventsWith mirrors ShareStateBagWith without allocating. I reviewed the diff only and did not run any tests. Concerns
Good
None of this blocks the PR. I would address item 1 before merging and treat items 2 and 4 as follow-ups. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4805990528
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Dispositions for the Claude review of 4805990 (none blocking):
Also in 2cfbcc2: failed data-row tests now take their row's events, so a failed row's scope is disposed. This closes #7326. |
|
@codex review |
Review of #7328I read the diff and the earlier review threads, and I did not run the tests. I found no blocking issues. What looks good
Remaining points (non-blocking)
The earlier review dispositions look addressed. This is good to merge once CI is green. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cfbcc2981
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
…rows (#7205) A row whose method data source, class row refetch, or object initialization throws in the eager builder now hands the events it took (including the class row's) to the method's failed test, instead of losing them in the outer catch. Source-generated generic class row partitions evaluate their rows against a context of their own and defer the cleanup subscribed there to session end, as reflection discovery does for its inference rows. Before, a dependency injection scope created during evaluation stayed on the build's base context, which no test raises. The session-end deferral moves into TestSessionContext so Core can use it.
|
Pushed 3336621: two review fixes on bca3622. Review threads on bca3622
Claude review (comment 6569080513, on bca3622) CI on bca3622: Validation on 3336621 (net10.0, Release):
|
|
Review of #7328 (DI scope disposal via shared builder-context events) I read the diff and did not run the tests. The fix addresses a real leak (#7205). Tying scope disposal to 1. Handlers receive a null
2. Behavior change to 3. Global, permanent
4.
5. Smaller points
Overall this is a careful, well-tested fix. I'd want items 1 and 2 settled before merge. Items 3 and 4 are maintainability risks that I'd fix now or track as a follow-up issue. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33366217eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…njected properties (#7205) The temporary instance that supplies instance method rows injected its properties against bare events that nothing raised, so a dependency injection scope created there was never disposed, and the service it owns is no longer disposed by the object tracker. Its cleanup now runs at session end, as for rows read during discovery. Reflection generic-type discovery instances get the same deferral. Also documents that OnDispose handlers subscribed for data no test owns receive a null TestContext.
|
Pushed b1a2ba2: one review fix on 3336621. Codex P2 on 3336621 (Retain scope cleanup from temporary-instance property injection): valid. Fixed and resolved. The temporary instance that supplies instance method rows injected its properties against events that nothing raised. Its scope's cleanup now moves to the session's deferred cleanup. Reflection generic-type discovery instances had the same pre-existing leak, and they now get the same deferral. Claude review (comment 6569591414, on 3336621)
Validation on b1a2ba2 (net10.0, Release):
|
Review of #7328 (DI scope disposal, #7205)I read the PR description, the file list, What looks good
Concerns and suggestions
No blocking issues found from this reading. I'd approve once CI is green and the behavior change has a visible release note. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1a2ba27d2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Closing without merging: this PR is over the rework budget. It has had 20 automated review rounds, and the latest review (Codex on Open blocking finding
Files reworked most often: Split (native sub-issues of #7205):
Follow-ups filed from this PR remain open: #7327, #7338, #7356, #7373, #7375, #7376. |
Summary
DependencyInjectionDataSourceAttribute<TScope>disposes each scope through anOnDisposehandler it subscribes onTestBuilderContext.Current.Eventswhile its row is fetched. The test was built with a separate test-specific context whose events started empty, so those subscriptions were lost and no scope was ever disposed. This happened in both source-generated and reflection modes.TestBuilderContext.ShareEventsWith(internal) lets the test-specific context use the per-iteration context's events lazily, the same wayShareStateBagWithshares the state bag. Nothing is allocated unless something subscribes.UnclaimedClassRowto the test that takes it, or that tracks it when it was lent to a temporary instance for an instance method data source. A row handed back withReturn(incompatible generic data) keeps its events, and theSkipIfEmptyskipped test takes them.OnDisposeis now raised once per test, inTestCoordinator's final cleanup, rather than after each attempt. Retries reuse the row's objects, so disposing a scope after a failed attempt broke the retry. The cleanup also runs for skipped tests, so their scopes are disposed. This matches the documented meaning ofOnDispose(raised when the test context is disposed). The test instance is still disposed after every attempt. Behavior change:OnDisposehandlers registered by users or hooks used to run once per attempt, before the instance was disposed. They now run once per test, after the instance is disposed. No test or doc in the repository depends on the old timing.DependencyInjectionDataSourceAttributemarks them as externally owned (ObjectTracker.MarkExternallyOwned, internal), so the object tracker no longer disposes them a second time when the test ends, and it no longer disposes a root singleton early either. A non-disposable scope cannot dispose whatCreatereturns, so the tracker still disposes those objects. The XML docs onCreatestate this contract.IAsyncInitializerobjects that tracking discovery finds through the properties of such a service, at any depth, are marked externally owned too. The tracker still initializes them but no longer disposes them after the scope has. Values of TUnit-injected data-source properties stay with the tracker.SkipIfEmptygeneric-inference failures, in the eager and streaming paths.TestCoordinatorstill raiseOnDispose, once: a test skipped because a dependency failed, a test that the run's cancellation stopped before it started, and a test that circular-dependency detection fails and removes (TestOnDisposeRaiser). Before this change, the session-end sweep disposed their DI services. Those services are externally owned now, so without this they would not be disposed.TestSessionContext.GlobalStaticPropertyContext, which no test owns.StaticPropertyHandlernow raises that context'sOnDisposeonce at session end, before it untracks static properties, so those scopes and the services they own are disposed.docs/docs/writing-tests/dependency-injection.mddocuments when a scope is disposed and who disposes resolved services.BuildSingleTestAsync) gets the same treatment with a per-iteration context, so the two copies stay consistent. Its removal is tracked in TestBuilder.BuildTestsStreamingAsync is unused and duplicates the row-building loop #7327.Code that a data source's enumerator runs after a
yieldstill sees the context of the row it just built.UniqueBuilderContextsOnEnumerableDataGeneratorTestspins that contract, and this PR leaves it unchanged.Tests
New
tests/TUnit.TestProject/Bugs/_7205andTUnit.Engine.Tests/Issue7205Testscheck that every scope is created once per test and disposed. They cover class-level DI, class-level DI with method rows and[Repeat], class-level DI with an instance method data source (the lent first fetch), method-parameter DI, a[Retry]test whose first attempt fails, a[Skip]test, aSkipIfEmptytest with an empty method source, a row that fails because its temporary instance cannot be constructed (FailedRowScopeTests, an intentional failure), and a non-disposable scope whoseCreateconstructs a disposable service.The
_7205observer also checks that each scope-owned service is disposed exactly once. That check fails without the tracker change (the services were disposed twice) and passes with it, in both modes.New tests fail on
main(0 of 2 scopes disposed) and pass with the fix, in source-gen and reflection modes.FailedRowScopeTestsfails without the failed-row change (0 of 1 scopes disposed).NonDisposableScopeTestsfails without the disposable-scope check (the service is never disposed). Both pass in source-gen and reflection modes.After those two changes: these Engine tests pass in reflection mode:
Issue7205Tests(16 tests: 13 passed, 1 intentional failure, 2 skipped),Issue7123Tests,Issue7156Tests,Issue7157Tests,GenericConstraintValidationTests,GenericMethodWithDataSourceTests,RetryTests,SkipTests,FilteredSharedFixtureDisposalTests,DerivedSkipWithClassDataSourceTests. In source-gen and reflection modes,Bugs._7205,_7157,_7156,_6162,DependencyInjectionScopeIsolationTestandMEDITestpass, and_7123and_4715show only their intentional failures.TUnit.UnitTests: 669 total, 0 failed.Passing in both modes:
Bugs._7157,Bugs.Issue5267,Bugs._2075,Bugs._6162,DependencyInjectionScopeIsolationTest,MEDITest,UniqueBuilderContextsOnEnumerableDataGeneratorTests,ClassConstructorWithEnumerableTest.TUnit.UnitTests: 668 total, 0 failed.After the
OnDisposechange: the retry test fails without it (the retry sees a disposed scope)._7205(14 tests: 12 passed, 2 skipped) and_7157pass in source-gen and reflection modes, in Release and Debug. These Engine tests pass in source-gen and reflection modes:Issue7205Tests,Issue7157Tests,RetryTests,CustomRetryTests,SkipTests,SkipInHooksTests,SkipTestExceptionTests,DynamicSkipReasonTests,DerivedSkipWithClassDataSourceTests,FilteredSharedFixtureDisposalTests.TUnit.Engine.Testsin reflection mode, full suite: 572 of 582 passed (566 succeeded, 6 skipped). Nine of the 10 failures took 1–6 minutes under full-suite load and passed when rerun on their own (FailFast, KeyedNotInParallel, filtering, FailTests). The tenth,FSharp.Test, fails locally with anFSharp.Core 11.0.0.0load error from the local F# build output, which is unrelated to this change. CI covers it.After the nested-ownership change:
NestedScopeDependencyTestsfails without it (the dependency is disposed twice) and passes with it.Bugs._7205(17 tests: 14 passed, 1 intentional failure, 2 skipped),_7157,_6162,_4032,Issue5267,DependencyInjectionScopeIsolationTestandMEDITestpass in source-gen and reflection modes.TUnit.UnitTests: 669 total, 0 failed.After the injected-property change:
NestedProbeService.Fixture(a TUnit-injectedIAsyncInitializerbeneath a scope-owned service) was never disposed without it (0 of 1), and is disposed exactly once with it.Bugs._7205(17 tests: 14 passed, 1 intentional failure, 2 skipped),_7157,_4032,Issue5267,DependencyInjectionScopeIsolationTestandMEDITestpass in source-gen and reflection modes.TUnit.UnitTests: 669 total, 0 failed.AOT mode was not run locally. The change adds no reflection, so CI covers AOT.
After the bypass-path change:
DependencySkippedScopeTestsfails without it (its scope is not disposed) and passes with it.Bugs._7205(19 tests: 14 passed, 2 intentional failures, 3 skipped),_7157,DependencyInjectionScopeIsolationTest,MEDITest,UniqueBuilderContextsOnEnumerableDataGeneratorTestsandDependsOnTestspass in source-gen and reflection modes.TUnit.UnitTests: 2013 total, 0 failed.After the circular-dependency change:
CircularDependencyScopeTestsfails without it (0 of 2 scopes disposed) and passes with it.Bugs._7205(21 tests: 14 passed, 4 intentional failures, 3 skipped) passes in source-gen and reflection modes.After the static-property change: new
StaticPropertyScopeDisposalTests(TUnit.UnitTests) pin that the session-level raise disposes a static property's scope once and collects handler exceptions. A temporary static[DI]property (not committed) showed the static initializer's scope disposed at session end in source-gen and reflection modes, where nothing disposed it before.TUnit.UnitTests: 2019 total, 0 failed.Bugs._7205(21 tests: 14 passed, 4 intentional failures, 3 skipped),Bugs._7204andPropertySetterTestspass in source-gen and reflection modes.After the review fixes in ca412cb: new
InitFailingScopeTestsfails without the initialization move (0 of 1 scopes disposed) and passes with it.Bugs._7205(23 tests: 14 passed, 6 intentional failures, 3 skipped) passes in source-gen and reflection modes._7106,_7157,DependencyInjectionScopeIsolationTestandMEDITestpass in both modes._7107,_7118and_7161give identical counts in reflection mode before and after.TUnit.UnitTests: 676 total, 0 failed.After the review fixes in 0eff2fe: new
DeferredDiscoveryCleanupTestspin that deferred discovery cleanup is raised once, by its own session only. NewExternallyOwnedSingletonTestspin a root singleton shared by a DI scope and a non-owning source in each order; its race case fails without theUntrackObjectchange.TUnit.UnitTests: 681 total, 0 failed.Bugs._7205(23 tests: 14 passed, 6 intentional failures, 3 skipped),_7205.FilterExcludedObserver,_7157andDependencyInjectionScopeIsolationTestpass in source-gen and reflection modes.Follow-ups filed
BuildTestsStreamingAsyncis unused and duplicates the row loop.OnDisposeand object untracking.OnDisposehandlers stay on the class row's events.Createreturns.Closes #7205
Closes #7326
Summary by CodeRabbit