[efficiency-improver] perf: single-pass PropertyBag walk in CtrfReport and HtmlReport TestResultCapture#9058
Merged
Evangelink merged 1 commit intoJun 12, 2026
Conversation
…esultCapture Replace 5/6 × SingleOrDefault<T>() + 1 × foreach/GetEnumerator() with one zero-allocation GetStructEnumerator() pass in TryCapture() for both Microsoft.Testing.Extensions.CtrfReport and Microsoft.Testing.Extensions.HtmlReport. CtrfReport before: 6 SingleOrDefault walks + 1 foreach (IEnumerator heap alloc) CtrfReport after: 1 GetStructEnumerator pass, 0 IEnumerator heap allocations HtmlReport before: 5 SingleOrDefault walks + 1 foreach (IEnumerator heap alloc) HtmlReport after: 1 GetStructEnumerator pass, 0 IEnumerator heap allocations For a test run with 1 000 tests and both report formats enabled: - ~50 000 pointer dereferences eliminated (CtrfReport: ~50k, HtmlReport: ~40k) - ~2 000 IEnumerator<IProperty> heap allocations removed Also refactors CtrfReport.GetClassAndMethodName() to accept TestMethodIdentifierProperty? directly (same pattern as JUnit/TrxReport). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves runtime efficiency in Microsoft.Testing.Platform report extensions by reducing repeated PropertyBag linked-list traversals and avoiding boxed enumerator allocations when capturing per-test results for CTRF and HTML report generation.
Changes:
- Refactors
TestResultCapture.TryCapture()in both CTRF and HTML report generators to collect all required properties via a singlePropertyBag.GetStructEnumerator()pass (after the existing O(1) non-terminal fast-path). - Preserves prior “throw on duplicate singleton-typed property” behavior via a local
GetSingleOrDefaultValuehelper while continuing to accumulate multi-valuedTestMetadataPropertytraits. - Updates CTRF’s internal
GetClassAndMethodNamehelper to accept the already-collectedTestMethodIdentifierProperty?, eliminating an extra property-bag lookup.
Show a summary per file
| File | Description |
|---|---|
| src/Platform/Microsoft.Testing.Extensions.HtmlReport/TestResultCapture.cs | Converts multiple property lookups + boxed enumeration into a single struct-enumerator pass for HTML captured results. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/TestResultCapture.cs | Applies the same single-pass property collection optimization for CTRF, including file location capture and identifier reuse. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal and Rationale
Reduce redundant CPU work and heap allocations in
TestResultCapture.TryCapture()in bothMicrosoft.Testing.Extensions.CtrfReportandMicrosoft.Testing.Extensions.HtmlReport, which run on every completed test result when the respective report formats (--report-ctrf,--report-html) are enabled.Focus area: Code-Level Efficiency — eliminating unnecessary linked-list traversals and heap allocations per test result.
Approach
Both
TryCapture()methods previously made multiple separate passes over thePropertyBaglinked list:CtrfReport before (7 passes):
SingleOrDefault<T>()(forTestNodeStateProperty,TimingProperty,TestMethodIdentifierProperty,StandardOutputProperty,StandardErrorProperty,TestFileLocationProperty)foreach (IProperty p in node.Properties)— calls the publicGetEnumerator()which boxes the internal struct enumerator asIEnumerator<IProperty>(1 heap allocation)HtmlReport before (6 passes):
SingleOrDefault<T>()(forTestNodeStateProperty,TimingProperty,TestMethodIdentifierProperty,StandardOutputProperty,StandardErrorProperty)foreach (IProperty p in node.Properties)— same boxing / heap allocationThe fix replaces all secondary passes with a single
GetStructEnumerator()pass using aswitchon the current node, collecting all required properties in one traversal. The initialSingleOrDefault<TestNodeStateProperty>()is kept as a fast-path guard for non-terminal messages (Discovered/InProgress), so non-terminal updates remain O(1) and the single-pass overhead is only paid for terminal results.CtrfReport.GetClassAndMethodName()is updated to accept the already-collectedTestMethodIdentifierProperty?directly, removing an internal walk on theTestNode.Both files already had
InternalsVisibleToaccess granted byMicrosoft.Testing.Platform.csproj, makingPropertyBag.GetStructEnumerator()(internal struct) available.Energy Efficiency Evidence
Proxy metric used: CPU cycles / heap allocations per test result (maps directly to energy — fewer pointer dereferences and GC pressure = less power draw per test run).
IEnumerator<IProperty>heap allocs per resultFor a test run with 1 000 tests with both report formats enabled:
IEnumerator<IProperty>heap allocations removedReproducibility: Build
src/Platform/Microsoft.Testing.Extensions.CtrfReportandsrc/Platform/Microsoft.Testing.Extensions.HtmlReport— succeeded with 0 warnings, 0 errors.Green Software Foundation Context
Hardware Efficiency: Fewer repeated traversals of the same linked-list nodes reduce CPU cache pressure. Fewer cache misses per test result → lower power draw per test run.
Software Carbon Intensity (SCI): Fewer instructions per functional unit (one terminal test result processed) directly reduces the energy term in the SCI equation.
Trade-offs
The single-pass
switchblock is slightly more verbose than the previous sequence of namedSingleOrDefault<T>()calls. However, the pattern is now established across five report generators in this codebase (DotnetTestDataConsumer,OpenTelemetryResultHandler,TrxTestResultExtractor,JUnitReport.TestResultCapture, and nowCtrfReport+HtmlReport) and is recognisable to contributors familiar with those files.Test Status
✅ Build succeeded (0 warnings, 0 errors) — both
netstandard2.0,net8.0, andnet9.0targetsAdd this agentic workflows to your repo
To install this agentic workflow, run