perf: cache reporting properties on test contexts - #6791
Conversation
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. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe reporting property cache moved from a global test-ID dictionary to per- ChangesReporting property cache
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Refactor Sequence Diagram(s)sequenceDiagram
participant TestNodeLocationTests
participant TestExtensions
participant TestContext
TestNodeLocationTests->>TestExtensions: Request cached properties
TestExtensions->>TestContext: Read per-context cache
TestExtensions->>TestContext: Write properties for the current scope
TestNodeLocationTests->>TestExtensions: ClearCaches()
TestExtensions->>TestContext: Clear registered context caches
TestNodeLocationTests->>TestExtensions: Create final node
TestExtensions->>TestContext: Release cached properties
Merge Risk: ⚪ Minimal · up to The reporting-cache change is covered for refresh, cleanup, and concurrent node creation, with no actionable merge risk identified. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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 the cache at dawn Comment |
Greptile SummaryThis PR moves immutable reporting-property caching from a global TestId-keyed dictionary onto each TestContext, preserving cache invalidation while reducing reporting overhead.
Confidence Score: 5/5The PR appears safe to merge; no actionable correctness or security issues remain. The current implementation releases reporting metadata on terminal updates, registry removal, and cache resets while scope checks prevent invalidated properties from being reused. The previous retention finding is fully addressed.
|
| Filename | Overview |
|---|---|
| src/TUnit.Core/TestContext.cs | Adds per-context reporting-cache storage and cleanup during registry removal and global cache resets. |
| src/TUnit.Engine/Extensions/TestExtensions.cs | Replaces the global per-test cache with scope-aware TestContext caching and terminal-state cleanup. |
| tests/TUnit.Engine.Tests/TestNodeLocationTests.cs | Adds regression coverage for invalidation, final-state release, and concurrent update isolation. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ToTestNode called] --> B{Cached properties match current scope?}
B -->|Yes| C[Reuse immutable reporting properties]
B -->|No| D[Create and publish reporting properties]
D --> E{Scope changed during creation?}
E -->|Yes| F[Remove newly published stale entry]
E -->|No| C
C --> G[Create independent PropertyBag and TestNode]
G --> H{Final state?}
H -->|Yes| I[Clear context reporting cache]
H -->|No| J[Keep cache for next update]
K[ClearCaches] --> L[Rotate global scope]
L --> M[Sweep registered contexts and clear properties]
Reviews (2): Last reviewed commit: "fix: release reporting caches after disc..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c994193334
ℹ️ 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".
Review: perf: cache reporting properties on test contexts (#6791)Summary of the change: Moves the reporting-property cache from a global Correctness of the concurrency-critical path: I traced the "publish, then verify scope, then self-revert" pattern in Two suggestions, not blockers:
Everything else checks out:
No blocking issues found. The two suggestions above are about long-term maintainability/testability rather than correctness bugs. |
Review fixes and current performance (751f6d3)
Addressed both memory-retention findings. Every terminal
ToTestNodeupdate releases the context's cached reporting properties, including deferred-enumeration placeholders and failures before coordinator execution.ClearCachesalso clears reporting fields on registered contexts, covering discovery-only requests. A scope check after publication removes stale properties if construction races with cache reset. Published nodes keep their independent property snapshots.Regression verification: the new cleanup assertions fail in all seven cases against the previous PR's saved Core/Engine binaries (c994193). The fixed binaries pass all 11 TestNodeLocationTests and all 17 GitHubReporterTests. The generated 10,000-test executable passes in both source-generated and reflection modes. Release builds pass for net10.0 and netstandard2.0; the test-project build reports existing analyzer/code-fixer warnings. The previously documented HtmlReporterTests baseline failure remains outside this fix; that suite was not rerun for this revision.
Reran the reporting benchmark with the original dictionary baseline, previous PR, and fixed implementation in the same BenchmarkDotNet run.
Before= 656b66e;BeforeReview= c994193;After= 751f6d3. Each operation resets caches and packages discovered, in-progress, and passed updates for 1,000 contexts. The measurement includes the new registry sweep and terminal cleanup. Context construction remains outside measurement, and all variants share the fixed Core assembly.Against the original dictionary, the fixed implementation takes 38.1% less time without categories and 31.4% less with three categories, with 17.6% and 12.4% fewer allocated bytes, respectively. Compared with the previous PR, the measured mean is 3.0% higher without categories and 9.5% lower with three categories; allocation totals are unchanged at the displayed precision. These are isolated reporting results, not whole-executable speedups. The whole-executable timing evidence below belongs to the original PR revision and was not rerun for this fix.
No builds, tests, or other benchmarks launched by this task overlapped this measurement. Dry validation passed first; the measured run used 20 iterations and six warmups.
Reproduce the review comparison
Use the original reproduction instructions below to build 656b66e into
baseline-runnerand c994193 intoidea4-runner. Build 751f6d3 intopr6791-fixed. Keep the three folders beside the benchmark project. SetPERF_ROOTto their parent, update the signing-key path, and use these project/source files. Program.cs remains the BenchmarkSwitcher entry point shown below.Original revision evidence (c994193)
Every test-node update looks up cached reporting metadata in a global ConcurrentDictionary keyed by TestId. Store that immutable metadata on its TestContext instead. A scope token preserves ClearCaches invalidation, volatile publication supports concurrent updates, and completion releases the cached properties so retained contexts do not retain reporting state. Every update still creates its own TestNode and PropertyBag.
Packaging discovered, in-progress and passed updates for 1,000 plain tests takes 286.2 us instead of 456.6 us (37.3% less) and allocates 954.80 KB instead of 1159.09 KB (17.6% less). With three categories per test, time falls 23.7% and allocations fall 12.4%. These are reporting-operation measurements, not complete test executions.
The benchmark excludes TestContext construction. The implementation adds one object reference to each TestContext (8 bytes per field on this x64 machine); this cost is outside the allocation column. Both engine versions run against the candidate Core assembly to share compatible TestContext instances. Completion-time clearing was added after the isolated measurement; it is covered by the final executable comparison and regression test.
Validation:
Whole-executable check, 20 alternating AB/BA pairs after three warmups per variant: before mean 1211.16 ms, median 1031.23 ms; after mean 1245.34 ms, median 993.07 ms. Paired mean reduction -34.18 ms, approximate 95% t interval [-140.76, 72.40] ms. This noisy result establishes neither an end-to-end gain nor a regression. A previous sample set overlapped a validation run and was discarded; only the clean rerun appears below. No whole-suite speedup is claimed.
Baseline
656b66e723; candidatec994193334. Windows 11 / Intel i7-12700K, SDK 11.0.100-preview.7.26381.103, .NET 10.0.12, BenchmarkDotNet 0.15.8. Isolated AssemblyLoadContexts with shared Core/MTP dependencies, InProcessEmitToolchain, 20 iterations, six warmups. No other benchmarks/builds/tests launched by this task during the accepted measurements. Each operation resets reporting caches before packaging all three updates for every test.Reproduce the microbenchmark
Save this project and source in an external RuntimeBench directory. Replace the signing-key checkout path in the project. Build the baseline and PR into sibling baseline-runner and idea4-runner directories:
Program.cs:
RuntimeBench.csproj:
ReportingBench.cs:
Raw whole-executable samples (milliseconds)
```csv "Pair","Variant","Milliseconds" "1","Before","1844.8189" "1","After","2023.8598" "2","After","2266.0609" "2","Before","2310.8018" "3","Before","1471.2523" "3","After","2150.7197" "4","After","1503.5988" "4","Before","1085.087" "5","Before","1104.559" "5","After","1196.6912" "6","After","1825.4811" "6","Before","1730.2069" "7","Before","1637.9832" "7","After","1123.7757" "8","After","1103.8656" "8","Before","1057.9552" "9","Before","1115.3834" "9","After","963.0704" "10","After","976.8791" "10","Before","958.4455" "11","Before","989.8522" "11","After","987.1668" "12","After","954.4256" "12","Before","968.6624" "13","Before","981.9879" "13","After","992.2406" "14","After","967.3678" "14","Before","1004.5135" "15","Before","980.9562" "15","After","978.1436" "16","After","946.2435" "16","Before","1090.6588" "17","Before","993.9318" "17","After","950.8155" "18","After","993.9059" "18","Before","970.9519" "19","Before","968.484" "19","After","1040.5333" "20","After","962.0398" "20","Before","956.7691"