🔍 Duplicate Code Detected: TestResultCapture / CapturedTestResult Pattern
Analysis of commit 3e056b0
Assignee: @copilot
Summary
The TestResultCapture static class and CapturedTestResult record are nearly identically duplicated in both Microsoft.Testing.Extensions.HtmlReport and Microsoft.Testing.Extensions.JUnitReport. The two copies share the same constants, the same TryCapture() projection logic, the same ClassifyOutcome() switch, the same GetClassAndMethodName() helper, and a nearly identical Truncate() utility — totalling ~130 shared lines across the two extension packages.
Duplication Details
Pattern: Duplicated TestResultCapture static class
- Severity: High
- Occurrences: 2 instances (HtmlReport and JUnitReport packages)
- Locations:
src/Platform/Microsoft.Testing.Extensions.HtmlReport/TestResultCapture.cs (131 lines)
src/Platform/Microsoft.Testing.Extensions.JUnitReport/TestResultCapture.cs (172 lines)
src/Platform/Microsoft.Testing.Extensions.HtmlReport/CapturedTestResult.cs (40 lines)
src/Platform/Microsoft.Testing.Extensions.JUnitReport/CapturedTestResult.cs (48 lines)
- Code Sample (identical constants and shared logic):
// Same in both files (lines 16-20 in each):
internal const int MaxStandardStreamLength = 32 * 1024;
internal const int MaxStackTraceLength = 32 * 1024;
internal const int MaxMessageLength = 16 * 1024;
internal const int MaxIdentityFieldLength = 4 * 1024;
internal const int MaxTraitFieldLength = 1024;
// ClassifyOutcome() is nearly identical in both (lines ~98-109 / ~120-140):
private static string ClassifyOutcome(TestNodeStateProperty state)
=> state switch
{
PassedTestNodeStateProperty => "passed",
SkippedTestNodeStateProperty => "skipped",
TimeoutTestNodeStateProperty => "timedOut",
ErrorTestNodeStateProperty => "errored",
FailedTestNodeStateProperty => "failed",
// ...
};
// GetClassAndMethodName() is identical in both (lines ~111-124 / ~142-155):
private static (string? ClassName, string? MethodName) GetClassAndMethodName(TestNode node) { ... }
Differences between the two versions:
- JUnit version adds
RawUid/ParentRawUid fields to CapturedTestResult and a ParentChainEntry record for building parent chains
- JUnit
TryCapture takes a TestNodeUpdateMessage wrapper instead of TestNode directly
- JUnit
Truncate handles surrogate pairs at the cut boundary (more defensive)
- JUnit
ClassifyOutcome adds a CancelledTestNodeStateProperty case
Impact Analysis
- Maintainability: When a new
TestNodeStateProperty is added or the truncation logic is refined, both files must be updated in sync. The JUnit Truncate already has an improvement (surrogate-pair protection) that the HTML version lacks, showing that drift has already begun.
- Bug Risk: The HtmlReport
Truncate does not protect against splitting a surrogate pair at the cut boundary — a bug present in the HtmlReport copy but already fixed in the JUnit copy.
- Code Bloat: ~130 lines of duplicated logic across the two assemblies.
Refactoring Recommendations
-
Extract to a shared internal helper (preferred):
- Create
src/Platform/SharedExtensionHelpers/TestResultCapture.cs with the shared constants, ClassifyOutcome, GetClassAndMethodName, and Truncate methods
- Let HtmlReport and JUnitReport inherit or call into the shared helper
- Keep report-format-specific logic (e.g.,
ParentChainEntry, parent-chain truncation) in the respective packages
-
Backport the surrogate-pair fix to HtmlReport:
- As a quick fix regardless of refactoring, apply the defensive
Truncate from JUnitReport to HtmlReport to close the existing bug
-
Source-link a partial class:
- Move shared members to a file compiled into both projects via
<Compile Include="..." Link="..." />
Implementation Checklist
Analysis Metadata
- Analyzed Files: 4 (2× TestResultCapture.cs, 2× CapturedTestResult.cs)
- Duplicated Lines: ~130 lines of shared logic
- Detection Method: Semantic code analysis + diff comparison
- Commit: 3e056b0
- Analysis Date: 2026-06-10
🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Duplicate Code Detector workflow.{ai_credits_suffix} · [◷]( · ◷)
Add this agentic workflows to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main
🔍 Duplicate Code Detected: TestResultCapture / CapturedTestResult Pattern
Analysis of commit 3e056b0
Assignee:
@copilotSummary
The
TestResultCapturestatic class andCapturedTestResultrecord are nearly identically duplicated in bothMicrosoft.Testing.Extensions.HtmlReportandMicrosoft.Testing.Extensions.JUnitReport. The two copies share the same constants, the sameTryCapture()projection logic, the sameClassifyOutcome()switch, the sameGetClassAndMethodName()helper, and a nearly identicalTruncate()utility — totalling ~130 shared lines across the two extension packages.Duplication Details
Pattern: Duplicated TestResultCapture static class
src/Platform/Microsoft.Testing.Extensions.HtmlReport/TestResultCapture.cs(131 lines)src/Platform/Microsoft.Testing.Extensions.JUnitReport/TestResultCapture.cs(172 lines)src/Platform/Microsoft.Testing.Extensions.HtmlReport/CapturedTestResult.cs(40 lines)src/Platform/Microsoft.Testing.Extensions.JUnitReport/CapturedTestResult.cs(48 lines)Differences between the two versions:
RawUid/ParentRawUidfields toCapturedTestResultand aParentChainEntryrecord for building parent chainsTryCapturetakes aTestNodeUpdateMessagewrapper instead ofTestNodedirectlyTruncatehandles surrogate pairs at the cut boundary (more defensive)ClassifyOutcomeadds aCancelledTestNodeStatePropertycaseImpact Analysis
TestNodeStatePropertyis added or the truncation logic is refined, both files must be updated in sync. The JUnitTruncatealready has an improvement (surrogate-pair protection) that the HTML version lacks, showing that drift has already begun.Truncatedoes not protect against splitting a surrogate pair at the cut boundary — a bug present in the HtmlReport copy but already fixed in the JUnit copy.Refactoring Recommendations
Extract to a shared internal helper (preferred):
src/Platform/SharedExtensionHelpers/TestResultCapture.cswith the shared constants,ClassifyOutcome,GetClassAndMethodName, andTruncatemethodsParentChainEntry, parent-chain truncation) in the respective packagesBackport the surrogate-pair fix to HtmlReport:
Truncatefrom JUnitReport to HtmlReport to close the existing bugSource-link a partial class:
<Compile Include="..." Link="..." />Implementation Checklist
Truncate()toHtmlReport/TestResultCapture.cs(immediate bug fix)ClassifyOutcome,GetClassAndMethodName,Truncate, and the 5 constants to a shared locationAnalysis Metadata
Add this agentic workflows to your repo
To install this agentic workflow, run