Skip to content

Align CTRF report writer overwrite behavior with TRX/HTML/JUnit#9782

Merged
Evangelink merged 1 commit into
mainfrom
dev/amauryleve/ctrf-overwrite-align
Jul 9, 2026
Merged

Align CTRF report writer overwrite behavior with TRX/HTML/JUnit#9782
Evangelink merged 1 commit into
mainfrom
dev/amauryleve/ctrf-overwrite-align

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Aligns the CTRF report writer with the shared overwrite rule already used by the TRX, HTML and JUnit report extensions.

Previously CtrfReportEngine was the only report extension that, for a default-generated file name, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop instead of overwriting. This change makes it always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule across all report formats.

Changes

  • CtrfReportEngine.FileWriter.cs: replaced WriteWithRetryAsync (and the SplitCtrfExtension helper) with a small WriteAsync that mirrors HtmlReportEngine.
  • CtrfReportEngine.cs: GenerateReportCoreAsync now calls WriteAsync and no longer threads the fileNameExplicitlyProvided flag.
  • CtrfReportEngineTests.cs: default-name test now expects FileMode.Create; GenerateReportAsync_AppendsDisambiguatingSuffix_When_DefaultFileExists replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents.

The CtrfReportFileExistsAndWillBeOverwritten resource already existed, so no resx/xlf changes were needed.

Verification

Built Microsoft.Testing.Extensions.UnitTests and ran --filter "FullyQualifiedName~CtrfReportEngineTests" — 27/27 passing.

Note

This is one of two PRs split out from #9780 (the other adds a public IArtifactNamingService). Heads-up: there is a pre-existing, unrelated RS0051 InternalAPI baseline break from #9774 that can block a full build.cmd; it is tracked/explained in the companion PR that touches Microsoft.Testing.Platform.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns the CTRF report writer's file-overwrite behavior with the TRX, HTML, and JUnit report extensions. Previously CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop rather than overwriting. The change makes CTRF follow the shared, product-wide rule: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single predictable behavior across all report formats.

Changes:

  • Replaced WriteWithRetryAsync and the SplitCtrfExtension helper with a small WriteAsync that mirrors HtmlReportEngine.WriteAsync (overwrite + warn).
  • Updated GenerateReportCoreAsync to call WriteAsync and stop threading the fileNameExplicitlyProvided flag.
  • Updated unit tests to expect FileMode.Create, replacing the disambiguation test with an overwrite-and-warn test and renaming the IOException test to reflect write-failure propagation.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.cs Removed the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.cs GenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.cs Updated tests to expect FileMode.Create; replaced the suffix test with an overwrite-and-warn test and renamed the IOException test.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Medium

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test quality grade — PR #9782

GradeTestNotes
B (80–89) mod CtrfReportEngineTests.
GenerateReportAsync_
DefaultFileName_
IncludesModuleNameAndTargetFramework
Path equality + regex filename check are solid; body slightly long (~37 lines) from mock setup — consider a shared engine-construction helper.
B (80–89) mod CtrfReportEngineTests.
GenerateReportAsync_
OverwritesAndWarns_
When_
DefaultFileExists
Strong: 4 complementary assertions cover path, no-suffix, warning presence, and warning content; body slightly long (~43 lines) due to verbose mock setup.
B (80–89) mod CtrfReportEngineTests.
GenerateReportAsync_
PropagatesIOException_
When_
WriteFails
Exact IOException type + callCount == 1 assertion guards against retry regression; slightly long (~39 lines) due to necessary mock setup.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

🤖 Automated content by GitHub Copilot. Generated by the Grade Tests on PR (on open / sync) workflow. · 62.3 AIC · ⌖ 8.28 AIC · ⊞ 9.5K · [◷]( · )

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 Automated review by GitHub Copilot. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.

Review Summary

Clean PR — solid simplification that aligns CTRF report behavior with the TRX, HTML, and JUnit report extensions. The new WriteAsync implementation is a near-identical structural match to HtmlReportEngine.WriteAsync (verified), which is the stated goal.

Verdict Table

# Dimension Severity Verdict Notes
1 Algorithmic Correctness MAJOR ✅ Clean Overwrite-and-warn logic is straightforward and correct
2 Threading & Concurrency BLOCKING ✅ Clean ConfigureAwait(false) used correctly; no async anti-patterns
3 Security & IPC Contract Safety BLOCKING ✅ Clean No new security surface
4 Public API & Binary Compatibility BLOCKING ✅ Clean internal sealed class — no public API changes
5 Performance & Allocations MAJOR ✅ Clean Simpler code path; retry loop and string allocations removed
6 Cross-TFM Compatibility MAJOR N/A No TFM-specific APIs
7 Test Quality MAJOR ✅ Clean MSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8 Error Handling MAJOR ✅ Clean IOException propagation tested; ExistFile mock properly set up
9 Localization MAJOR ✅ Clean Reuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10 Naming & Style MINOR ✅ Clean Follows .editorconfig conventions
11 Documentation MINOR ✅ Clean Comments explain why (alignment rationale) not what
12 Logging MINOR N/A Warning surfaced via return tuple — no direct logging changes
13 Null Safety MAJOR ✅ Clean warning! used only after Assert.IsNotNull guard
14 Resource Management MAJOR ✅ Clean No resource leaks introduced
15 Scope & PR Hygiene MINOR ✅ Clean Single concern: behavior alignment
16 Test Coverage MAJOR ✅ Clean Old behavior tests replaced with new behavior equivalents
17 IPC Contract Stability BLOCKING N/A No wire format changes
18 Backward Compatibility BLOCKING ⚠️ Advisory Behavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19 Simplicity & Readability MINOR ✅ Clean −66 / +13 net — major simplification
20 Dependency Management MINOR ✅ Clean No dependency changes
21 Configuration & Defaults MINOR ✅ Clean Default behavior now consistent across all report formats
22 PowerShell / Script Quality MINOR N/A No scripts

Notes

  • The benign TOCTOU race between ExistFile and WriteBytesAsync (only affects warning accuracy, not write correctness) mirrors the identical pattern in HtmlReportEngine, JUnitReportEngine, and existing explicit-path handling — acceptable for a best-effort informational warning.
  • The discard (string finalPath, _) = ResolveOutputPath(...) is clean — the fileNameExplicitlyProvided flag is genuinely unused now.

No blocking or major issues found. LGTM.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The build fails because two new protected static methods in BaseSerializer.cs (introduced in the base branch) are missing from the Internal API declaration file, triggering RS0051 across all three target frameworks and cascading to every downstream project.

Root cause: Missing InternalAPI declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T>

The internal abstract class BaseSerializer gained two new methods (ReadFields and WriteListPayload<T>) in commit a946ef7 ("Stabilize extension UIDs and add naming governance"), but the corresponding entries were never added to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt. The Roslyn Public/Internal API analyzer (RS0051) fires once per target framework (net8.0, net9.0, netstandard2.0), producing 6 errors total. Since Microsoft.Testing.Platform.csproj fails to compile, all downstream projects (CrashDump, AzureFoundry, AzureDevOpsReport, CtrfReport, MSBuild, TestAdapter, etc.) also fail.

This is a pre-existing issue in the base branch — the PR's own changes (CtrfReport simplification) are not at fault.

Affected file / errors

Proposed fix — Add these two lines to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

 #nullable enable
+static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.ReadFields(System.IO.Stream! stream, System.Func<ushort, int, bool>! tryReadField) -> void
+static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.WriteListPayload<T>(System.IO.Stream! stream, ushort fieldId, T[]? list, System.Action<System.IO.Stream!, T>! writeItem) -> void

Note: This file is not part of the PR diff (the issue originates from the base branch). You can either fix it in this PR or merge a separate fix to main first.


Build overview
Metric Value
Status ❌ FAILED
Duration 160.2 s
Projects 49
Errors 7 (6 RS0051 + 1 "Build failed")
Warnings 0

Failed projects (cascade):

  • Microsoft.Testing.Platform.csproj (root cause — net8.0, net9.0, netstandard2.0)
  • Microsoft.Testing.Extensions.CrashDump.csproj (depends on Platform)
  • Microsoft.Testing.Extensions.AzureFoundry.csproj (depends on Platform)
  • Microsoft.Testing.Extensions.AzureDevOpsReport.csproj (depends on Platform)
All MSBuild errors (7)
# Code File Line Message
1 RS0051 BaseSerializer.cs 359 ReadFields(...) not part of declared API
2 RS0051 BaseSerializer.cs 380 WriteListPayload<T>(...) not part of declared API
3 RS0051 BaseSerializer.cs 359 (duplicate — net9.0 TFM)
4 RS0051 BaseSerializer.cs 380 (duplicate — net9.0 TFM)
5 RS0051 BaseSerializer.cs 359 (duplicate — netstandard2.0 TFM)
6 RS0051 BaseSerializer.cs 380 (duplicate — netstandard2.0 TFM)
7 Build failed. (summary)

🤖 Generated by the Build Failure Analysis workflow using binlog-mcp · commit 297ab1d

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 120.6 AIC · ⌖ 6.99 AIC · ⊞ 7.3K · [◷]( · )

@Evangelink Evangelink merged commit 1ece911 into main Jul 9, 2026
27 of 34 checks passed
@Evangelink Evangelink deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants