ITADN

[duplicate-code] Duplicate low-level file-write method in HtmlReportEngine and CtrfReportEngine

#9610Opengithub-actions[bot] 创建于 2026-07-05
type/tech-debttype/automation
## Summary `HtmlReportEngine` and `CtrfReportEngine` each contain a private async method that performs the actual bytes-to-file write operation. The two methods have identical comments, the same `#if NETCOREAPP` branch structure, and the same `IFileStream` dispose pattern — only the method signatures differ (CTRF accepts a `FileMode` parameter; HTML hardcodes `FileMode.Create`). Meanwhile, `ReportFileWriterHelper` already exists in `SharedExtensionHelpers` as the intended home for shared file-writing helpers but does not yet cover this low-level write. ## Duplication Details ### Pattern: Identical `IFileStream` byte-write block with `#if NETCOREAPP` directive - **Severity**: Low–Medium - **Occurrences**: 2 - **Locations**: - `src/Platform/Microsoft.Testing.Extensions.HtmlReport/HtmlReportEngine.cs` (lines 84–94, `WriteFileAsync`) - `src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.cs` (lines 80–89, `WriteAsync`) - **Code Sample**: ```csharp // HtmlReportEngine.WriteFileAsync (FileMode hardcoded to Create) private async Task WriteFileAsync(string path, byte[] bytes) { // Note that we need to dispose the IFileStream, not the inner stream. // IFileStream implementations will be responsible to dispose their inner stream. using IFileStream stream = _fileSystem.NewFileStream(path, FileMode.Create); #if NETCOREAPP await stream.Stream.WriteAsync(bytes.AsMemory(), _cancellationToken).ConfigureAwait(false); #else await stream.Stream.WriteAsync(bytes, 0, bytes.Length, _cancellationToken).ConfigureAwait(false); #endif } // CtrfReportEngine.WriteAsync (FileMode is a parameter) private async Task WriteAsync(string path, FileMode mode, byte[] bytes) { // Note that we need to dispose the IFileStream, not the inner stream. // IFileStream implementations will be responsible to dispose their inner stream. using IFileStream stream = _fileSystem.NewFileStream(path, mode); #if NETCOREAPP await stream.Stream.WriteAsync(bytes.AsMemory(), _cancellationToken).ConfigureAwait(false); #else await stream.Stream.WriteAsync(bytes, 0, bytes.Length, _cancellationToken).ConfigureAwait(false); #endif } ``` ## Impact Analysis - **Maintainability**: Any platform-specific change to the write path (e.g., support for Span-based APIs on a new TFM) must be applied in two places. The identical comment block already signals copy-paste origin. - **Bug Risk**: If a future TFM requires a different write strategy (e.g., `WriteAsync` overload deprecation), one engine could be updated while the other is missed. - **Code Bloat**: The `ReportFileWriterHelper` class was specifically introduced for shared writing concerns but was not extended to cover this primitive — causing the drift. ## Refactoring Recommendations 1. **Add `WriteBytesAsync` to `ReportEngineBase`** - Add a `protected Task WriteBytesAsync(string path, FileMode mode, byte[] bytes)` method to `src/Platform/SharedExtensionHelpers/ReportEngineBase.cs`. - Both `HtmlReportEngine.WriteFileAsync` and `CtrfReportEngine.WriteAsync` delegate to this shared method. - The shared implementation uses the `_fileSystem` and `_cancellationToken` already available on the base class. - Estimated effort: 1 hour - Benefits: Single `#if NETCOREAPP` branch to maintain; comment lives in one place. 2. **Alternatively, add a static helper to `ReportFileWriterHelper`** - Add `internal static Task WriteBytesAsync(IFileSystem fileSystem, string path, FileMode mode, byte[] bytes, CancellationToken ct)` to `src/Platform/SharedExtensionHelpers/ReportFileWriterHelper.cs`. - Both engines call the static helper. - Estimated effort: 1 hour - Benefits: Does not require touching the base class; keeps the helper class cohesive. ## Implementation Checklist - [ ] Review duplication findings - [ ] Choose refactoring approach (base class method vs. static helper) - [ ] Implement shared write helper - [ ] Replace both `WriteFileAsync`/`WriteAsync` bodies to delegate to the shared helper - [ ] Verify all file-writing unit/acceptance tests still pass ## Analysis Metadata - **Analyzed Files**: 2 source files + 1 shared helper - **Detection Method**: Semantic code analysis — copy-paste with single-parameter variation - **Commit**: 75c028427c3200217bd106bd885765d9c6048e5d - **Analysis Date**: 2026-07-05 > 🤖 **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](https://github.com/microsoft/testfx/actions/runs/28730689162/agentic_workflow) workflow. · 181 AIC · ⌖ 9.2 AIC · ⊞ 8K · [◷]( · [◷](https://github.com/search?q=repo%3Amicrosoft%2Ftestfx+is%3Aissue+%22gh-aw-workflow-call-id%3A+microsoft%2Ftestfx%2Fduplicate-code-detector%22&type=issues)) > <details> <summary>Add this agentic workflows to your repo</summary> To install this agentic workflow, run ``` gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main ``` </details> > - [x] expires <!-- gh-aw-expires: 2026-07-07T05:37:32.649Z --> on Jul 7, 2026, 5:37 AM UTC <!-- gh-aw-agentic-workflow: Duplicate Code Detector, engine: copilot, version: 1.0.65, model: claude-sonnet-4.6, id: 28730689162, workflow_id: duplicate-code-detector, run: https://github.com/microsoft/testfx/actions/runs/28730689162 --> <!-- gh-aw-workflow-id: duplicate-code-detector --> <!-- gh-aw-workflow-call-id: microsoft/testfx/duplicate-code-detector -->
0 条评论