Skip to content

[duplicate-code] Duplicate Code: Report extension registration boilerplate repeated across Ctrf/JUnit/Html with inconsistent guardΒ #9131

@Evangelink

Description

@Evangelink

πŸ” Duplicate Code Detected: Report Extension Registration Boilerplate

Analysis of commit cf37d3b

Assignee: @copilot

Summary

Three *ReportExtensions.cs files β€” CtrfReportExtensions, JUnitReportExtensions, and HtmlReportExtensions β€” each contain an Add{X}ReportProvider extension method that follows an identical 11-parameter CompositeExtensionFactory construction pattern and an identical three-call registration block. Beyond simple repetition, there is a consistency defect: CtrfReportExtensions is missing the if (builder is not TestApplicationBuilder) guard that both JUnitReportExtensions and HtmlReportExtensions include.

Duplication Details

Pattern: Identical 11-parameter CompositeExtensionFactory construction + registration

  • Severity: Medium
  • Occurrences: 3 β€” one per report format
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportExtensions.cs (lines 24–48)
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportExtensions.cs (lines 27–55)
    • src/Platform/Microsoft.Testing.Extensions.HtmlReport/HtmlReportExtensions.cs (lines 27–55)

CtrfReportExtensions (missing guard):

public static void AddCtrfReportProvider(this ITestApplicationBuilder builder)
{
    var commandLine = new CtrfReportGeneratorCommandLine();

    var compositeCtrfReportGenerator =
        new CompositeExtensionFactory<CtrfReportGenerator>(serviceProvider =>
            new CtrfReportGenerator(
                serviceProvider.GetConfiguration(),
                serviceProvider.GetCommandLineOptions(),
                serviceProvider.GetRequiredService<IFileSystem>(),
                serviceProvider.GetTestApplicationModuleInfo(),
                serviceProvider.GetMessageBus(),
                serviceProvider.GetSystemClock(),
                serviceProvider.GetEnvironment(),
                serviceProvider.GetOutputDevice(),
                serviceProvider.GetTestFramework(),
                serviceProvider.GetTestApplicationProcessExitCode(),
                serviceProvider.GetLoggerFactory().CreateLogger<CtrfReportGenerator>()));

    builder.TestHost.AddDataConsumer(compositeCtrfReportGenerator);
    builder.TestHost.AddTestSessionLifetimeHandler(compositeCtrfReportGenerator);
    builder.CommandLine.AddProvider(() => commandLine);
}

JUnitReportExtensions / HtmlReportExtensions (same structure with guard):

public static void AddJUnitReportProvider(this ITestApplicationBuilder builder)
{
    if (builder is not TestApplicationBuilder)
    {
        throw new InvalidOperationException(ExtensionResources.InvalidTestApplicationBuilderType);
    }
    // ... same 11-parameter factory construction and registration ...
}

Impact Analysis

  • Maintainability: Adding a new report format means copy-pasting this entire block. Any change to the service-provider parameter list (e.g., adding a new injected service to ReportGeneratorBase) requires updating all three files. This has already led to an inconsistency where CTRF lacks the TestApplicationBuilder guard.
  • Bug Risk: The missing builder is not TestApplicationBuilder guard in CtrfReportExtensions means CTRF report registration will silently succeed against custom ITestApplicationBuilder implementations that cannot honour the extension, while JUnit and HTML fail fast.
  • Code Bloat: ~36 lines of near-identical factory construction repeated 3 times across 3 files.

Refactoring Recommendations

  1. Immediate fix: add the missing TestApplicationBuilder guard to CtrfReportExtensions

    public static void AddCtrfReportProvider(this ITestApplicationBuilder builder)
    {
        if (builder is not TestApplicationBuilder)
        {
            throw new InvalidOperationException(ExtensionResources.InvalidTestApplicationBuilderType);
        }
        // ...
    }
    • Estimated effort: < 15 minutes
    • Benefits: consistent guard behavior across all three report extensions
  2. Extract shared factory helper to SharedExtensionHelpers or ReportGeneratorBase

    • Consider a static factory helper method in SharedExtensionHelpers that encapsulates the 11-parameter construction:
      // Conceptual β€” actual generic constraints must accommodate all three generators
      internal static CompositeExtensionFactory<TGenerator> CreateReportGeneratorFactory<TGenerator>(...)
          where TGenerator : ReportGeneratorBase<TGenerator, CapturedTestResult>
    • Estimated effort: 2–4 hours
    • Benefits: adding a fourth report format or changing the service-parameter list becomes a one-file change

Implementation Checklist

  • Review duplication findings
  • Add missing if (builder is not TestApplicationBuilder) guard to CtrfReportExtensions.AddCtrfReportProvider
  • Evaluate extracting the shared 11-parameter factory construction pattern to a helper
  • Update tests that test CtrfReportExtensions with a custom builder if applicable
  • Verify no functionality broken

Analysis Metadata

  • Analyzed Files: 3 (CtrfReportExtensions.cs, JUnitReportExtensions.cs, HtmlReportExtensions.cs)
  • Detection Method: Semantic code analysis + structural comparison
  • Commit: cf37d3b
  • Analysis Date: 2026-06-15

πŸ€– 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. Β· 979.9 AIC Β· βŒ– 14.3 AIC Β· [β—·]( Β· β—·)

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 17, 2026, 6:14 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