Skip to content

[duplicate-code] Duplicate Code: TestResultCapture and CapturedTestResult Duplicated in HtmlReport and JUnitReport #8986

@Evangelink

Description

@Evangelink

🔍 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

  1. 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
  2. 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
  3. Source-link a partial class:

    • Move shared members to a file compiled into both projects via <Compile Include="..." Link="..." />

Implementation Checklist

  • Review duplication findings
  • Backport the safer Truncate() to HtmlReport/TestResultCapture.cs (immediate bug fix)
  • Decide on sharing strategy for constants and shared helpers
  • Extract ClassifyOutcome, GetClassAndMethodName, Truncate, and the 5 constants to a shared location
  • Update both packages to use the shared location
  • Verify generated HTML and JUnit XML output unchanged after refactoring

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
  • expires on Jun 12, 2026, 5:55 AM UTC

Metadata

Metadata

Labels

type/automationCreated or maintained by an agentic workflow.type/tech-debtCode health, refactoring, simplification.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions