Analysis of commit d8c1e43
Assignee: @copilot
Summary
Three report generator classes — HtmlReportGenerator, JUnitReportGenerator, and CtrfReportGenerator — each override GenerateReportAsync with a nearly identical 12-line block that instantiates their respective report engine passing the same 10 arguments. All 7 base-class service properties (FileSystem, TestApplicationModuleInfo, Environment, CommandLineOptions, Configuration, Clock, TestFramework) come from ReportGeneratorBase protected properties, and the remaining 3 (testStartTime, exitCode, cancellationToken) are method parameters that ReportGeneratorBase.OnTestSessionFinishingAsync already resolves before calling the override. Any future change to ReportEngineBase's constructor (e.g., adding a parameter) requires identical edits in all three files.
Duplication Details
Pattern: Identical engine-construction block in GenerateReportAsync
- Severity: Medium
- Occurrences: 3
- Locations:
src/Platform/Microsoft.Testing.Extensions.HtmlReport/HtmlReportGenerator.cs (lines 41–52)
src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportGenerator.cs (lines 66–77)
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGenerator.cs (lines 41–52)
- Code Sample (all three are structurally identical):
var engine = new HtmlReportEngine( // or JUnitReportEngine / CtrfReportEngine
FileSystem,
TestApplicationModuleInfo,
Environment,
CommandLineOptions,
Configuration,
Clock,
TestFramework,
testStartTime,
exitCode,
cancellationToken);
return engine.GenerateReportAsync(tests);
Impact Analysis
- Maintainability: Every
ReportEngineBase constructor change requires updating three separate generator files. New report format generators (e.g., a future SarifReportGenerator) would have to repeat this boilerplate.
- Bug Risk: Developers adding a new service to
ReportEngineBase may update one or two generators and miss the third, causing a silent runtime failure or compilation error that only surfaces for that report format.
- Code Bloat: ~36 lines of nearly identical boilerplate across three files for something the base class already owns.
Refactoring Recommendations
-
Add a protected factory/context helper on ReportGeneratorBase
- Add a method or record to
ReportGeneratorBase<TGenerator, TCapturedTestResult> that bundles all base properties + the call-time arguments into a single object:
// In ReportGeneratorBase
protected ReportEngineContext CreateEngineContext(
DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken)
=> new(FileSystem, TestApplicationModuleInfo, Environment,
CommandLineOptions, Configuration, Clock, TestFramework,
testStartTime, exitCode, cancellationToken);
- Then each engine's constructor (and generator's
GenerateReportAsync) would accept ReportEngineContext instead of 10 separate parameters.
- Estimated effort: ~2 hours
- Benefits: Single place to update when
ReportEngineBase grows; new report formats get it for free.
-
Alternative: make each *ReportEngine accept a ReportGeneratorBase reference
- Each engine calls the protected properties directly through the base reference.
- Trades explicit dependencies for a tighter coupling; simpler short-term but harder to unit-test engines in isolation.
- Estimated effort: ~1 hour
Implementation Checklist
Analysis Metadata
- Analyzed Files: 6 (3 generators + 3 engines)
- Detection Method: Semantic code analysis — identical constructor-argument list in all three generator overrides
- Commit: d8c1e43
- Analysis Date: 2026-06-28
🤖 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. · 1.2K AIC · ⌖ 25.3 AIC · ⊞ 43.5K · [◷]( · ◷)
Add this agentic workflows to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main
Analysis of commit d8c1e43
Assignee:
@copilotSummary
Three report generator classes —
HtmlReportGenerator,JUnitReportGenerator, andCtrfReportGenerator— each overrideGenerateReportAsyncwith a nearly identical 12-line block that instantiates their respective report engine passing the same 10 arguments. All 7 base-class service properties (FileSystem,TestApplicationModuleInfo,Environment,CommandLineOptions,Configuration,Clock,TestFramework) come fromReportGeneratorBaseprotected properties, and the remaining 3 (testStartTime,exitCode,cancellationToken) are method parameters thatReportGeneratorBase.OnTestSessionFinishingAsyncalready resolves before calling the override. Any future change toReportEngineBase's constructor (e.g., adding a parameter) requires identical edits in all three files.Duplication Details
Pattern: Identical engine-construction block in
GenerateReportAsyncsrc/Platform/Microsoft.Testing.Extensions.HtmlReport/HtmlReportGenerator.cs(lines 41–52)src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportGenerator.cs(lines 66–77)src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGenerator.cs(lines 41–52)Impact Analysis
ReportEngineBaseconstructor change requires updating three separate generator files. New report format generators (e.g., a futureSarifReportGenerator) would have to repeat this boilerplate.ReportEngineBasemay update one or two generators and miss the third, causing a silent runtime failure or compilation error that only surfaces for that report format.Refactoring Recommendations
Add a protected factory/context helper on
ReportGeneratorBaseReportGeneratorBase<TGenerator, TCapturedTestResult>that bundles all base properties + the call-time arguments into a single object:GenerateReportAsync) would acceptReportEngineContextinstead of 10 separate parameters.ReportEngineBasegrows; new report formats get it for free.Alternative: make each
*ReportEngineaccept aReportGeneratorBasereferenceImplementation Checklist
ReportEngineContextrecord vs. direct generator reference approachHtmlReportEngine,JUnitReportEngine,CtrfReportEngineconstructorsHtmlReportGenerator,JUnitReportGenerator,CtrfReportGeneratoraccordinglyAnalysis Metadata
Add this agentic workflows to your repo
To install this agentic workflow, run