Skip to content

[duplicate-code] Duplicate Code: JUnitReportEngine Duplicates SharedExtensionHelpers (Sanitizer, TFM Helper, ReportFileNameHelper) #8948

@Evangelink

Description

@Evangelink

Analysis of commit 1b64756

Assignee: @copilot

Summary

JUnitReportEngine.cs contains private copies of four functionalities already provided by the SharedExtensionHelpers library: file name sanitization (ReportFileNameSanitizer), target framework moniker resolution (TargetFrameworkMonikerHelper), template-based file name resolution (ReportFileNameHelper), and the retry timeout constant (ReportFileWriterHelper.FileWriteRetryTimeout). This duplication exists because JUnitReport.csproj does not link any SharedExtensionHelpers files, unlike the HtmlReport.csproj and TrxReport.csproj projects which link the same shared code.

Duplication Details

Pattern 1: ReplaceInvalidFileNameChars / IsInvalidFileNameChar / IsReservedFileName

  • Severity: High
  • Occurrences: 3 duplicate methods
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportEngine.cs (lines 731–774)
    • src/Platform/SharedExtensionHelpers/ReportFileNameSanitizer.cs (lines 13–51) — authoritative source

The character set in IsInvalidFileNameChar is bit-for-bit identical:

// JUnitReportEngine.cs line 751 — DUPLICATE
private static bool IsInvalidFileNameChar(char c)
    => c is < ' ' or '"' or '<' or '>' or '|' or ':' or '*' or '?' or '\\' or '/' or '@' or '(' or ')' or '^' or ' ';

// ReportFileNameSanitizer.cs line 33–34 — AUTHORITATIVE
private static bool IsInvalidFileNameChar(char c)
    => c is < ' ' or '"' or '<' or '>' or '|' or ':' or '*' or '?' or '\\' or '/' or '@' or '(' or ')' or '^' or ' ';

Pattern 2: GetTargetFrameworkMoniker (identical to TargetFrameworkMonikerHelper)

  • Severity: Medium
  • Occurrences: Duplicated in full
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportEngine.cs (lines 725–729)
    • src/Platform/SharedExtensionHelpers/TargetFrameworkMonikerHelper.cs (lines 10–13) — authoritative source
// JUnitReportEngine.cs (lines 725–729) — DUPLICATE
private static string GetTargetFrameworkMoniker()
    => TargetFrameworkParser.GetShortTargetFramework(
        Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkDisplayName)
        ?? TargetFrameworkParser.GetShortTargetFramework(RuntimeInformation.FrameworkDescription)
        ?? "unknown";

// TargetFrameworkMonikerHelper.cs (lines 10–13) — AUTHORITATIVE
public static string GetTargetFrameworkMoniker()
    => TargetFrameworkParser.GetShortTargetFramework(Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkDisplayName)
        ?? TargetFrameworkParser.GetShortTargetFramework(RuntimeInformation.FrameworkDescription)
        ?? "unknown";

Note: HtmlReportEngine.cs correctly calls TargetFrameworkMonikerHelper.GetTargetFrameworkMoniker().

Pattern 3: ResolveXmlFileName duplicates ReportFileNameHelper.ResolveAndSanitize

  • Severity: Medium
  • Occurrences: Inline copy of ReportFileNameHelper.ResolveAndSanitize
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportEngine.cs (lines 712–723) — inline copy
    • src/Platform/SharedExtensionHelpers/ReportFileNameHelper.cs (lines 28–37) — authoritative
// JUnitReportEngine.ResolveXmlFileName (lines 712–723) — duplicates ReportFileNameHelper logic
private string ResolveXmlFileName(string template)
{
    string processName = ...;
    string processId = ...;
    Dictionary<string, string> replacements = ArtifactNamingHelper.GetStandardReplacements(processName, processId, _clock.UtcNow);
    string resolved = ArtifactNamingHelper.ResolveTemplate(template, replacements);
    string directoryPart = Path.GetDirectoryName(resolved) ?? string.Empty;
    string sanitizedFileName = ReplaceInvalidFileNameChars(Path.GetFileName(resolved));  // calls local duplicate
    return directoryPart.Length == 0 ? sanitizedFileName : Path.Combine(directoryPart, sanitizedFileName);
}

// HtmlReportEngine.ResolveHtmlFileName — correct (1 line delegation)
private string ResolveHtmlFileName(string template)
    => ReportFileNameHelper.ResolveAndSanitize(template, processName, processId, _clock.UtcNow);

Pattern 4: Hardcoded retry timeout vs. ReportFileWriterHelper.FileWriteRetryTimeout

JUnitReportEngine.WriteWithRetryAsync (line 158) uses TimeSpan.FromSeconds(5) hardcoded, while the correct constant is ReportFileWriterHelper.FileWriteRetryTimeout (also 5s today, but the hardcoded value won't track future changes).

Root Cause

JUnitReport.csproj links none of the SharedExtensionHelpers source files:

<!-- JUnitReport.csproj — missing these links that HtmlReport/TrxReport have: -->
<!-- <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameSanitizer.cs" /> -->
<!-- <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TargetFrameworkMonikerHelper.cs" /> -->
<!-- <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameHelper.cs" /> -->
<!-- <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileWriterHelper.cs" /> -->

Impact Analysis

  • Maintainability: The IsInvalidFileNameChar character set defines which characters are forbidden in report file names. Any future additions (e.g., platform-specific invalid chars) must be applied in both ReportFileNameSanitizer.cs and JUnitReportEngine.cs, risking divergence.
  • Bug Risk: The JUnit ReplaceInvalidFileNameChars method differs subtly from ReportFileNameSanitizer.ReplaceInvalidFileNameChars in the reserved-name check implementation — it uses a manual string comparison while the shared version uses a regex. Future edge cases (e.g., CLOCK$ with extension) might behave differently.
  • Code Bloat: ~50 lines of dead-weight duplicated code across 4 distinct helper patterns.

Refactoring Recommendations

  1. Add SharedExtensionHelpers file links to JUnitReport.csproj

    <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameSanitizer.cs" Link="Helpers\ReportFileNameSanitizer.cs" />
    <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\TargetFrameworkMonikerHelper.cs" Link="Helpers\TargetFrameworkMonikerHelper.cs" />
    <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileNameHelper.cs" Link="Helpers\ReportFileNameHelper.cs" />
    <Compile Include="$(RepoRoot)src\Platform\SharedExtensionHelpers\ReportFileWriterHelper.cs" Link="Helpers\ReportFileWriterHelper.cs" />

    Note: ReportFileNameHelper.cs also requires ReportFileNameSanitizer.cs (already above).

  2. Simplify ResolveXmlFileName to delegate to ReportFileNameHelper.ResolveAndSanitize, matching HtmlReportEngine.ResolveHtmlFileName.

  3. Update BuildDefaultFileName to call TargetFrameworkMonikerHelper.GetTargetFrameworkMoniker() and ReportFileNameSanitizer.ReplaceInvalidFileNameChars.

  4. Replace hardcoded TimeSpan.FromSeconds(5) with ReportFileWriterHelper.FileWriteRetryTimeout.

  5. Delete duplicate private methods from JUnitReportEngine.cs: ReplaceInvalidFileNameChars, IsInvalidFileNameChar, IsReservedFileName, GetTargetFrameworkMoniker.

Implementation Checklist

  • Add 4 <Compile> links to JUnitReport.csproj (see Recommendation 1)
  • Replace ResolveXmlFileName body with ReportFileNameHelper.ResolveAndSanitize call
  • Replace GetTargetFrameworkMoniker() call in BuildDefaultFileName with TargetFrameworkMonikerHelper.GetTargetFrameworkMoniker()
  • Replace ReplaceInvalidFileNameChars(raw) call in BuildDefaultFileName with ReportFileNameSanitizer.ReplaceInvalidFileNameChars(raw)
  • Replace TimeSpan.FromSeconds(5) in WriteWithRetryAsync with ReportFileWriterHelper.FileWriteRetryTimeout
  • Remove private methods: ReplaceInvalidFileNameChars, IsInvalidFileNameChar, IsReservedFileName, GetTargetFrameworkMoniker
  • Verify build succeeds for all target frameworks
  • Run JUnit report tests to confirm no behavioral regressions

Analysis Metadata

  • Analyzed Files: JUnitReportEngine.cs, HtmlReportEngine.cs, ReportFileNameSanitizer.cs, TargetFrameworkMonikerHelper.cs, ReportFileNameHelper.cs, ReportFileWriterHelper.cs, JUnitReport.csproj, HtmlReport.csproj
  • Detection Method: Semantic code analysis — cross-file body comparison and project reference audit
  • Commit: 1b64756
  • Analysis Date: 2026-06-09

Generated by Duplicate Code Detector · sonnet46 3.6M ·

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 11, 2026, 5:45 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