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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Merged
Amaury Levé (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
Amaury Levé (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>

CopilotAI 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
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csRemoved the retry/disambiguation logic and SplitCtrfExtension; added WriteAsync mirroring the shared overwrite-and-warn rule.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync now calls WriteAsync and discards the explicit-name flag.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdated 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

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-actionsgithub-actionsBot 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

#DimensionSeverityVerdictNotes
1Algorithmic CorrectnessMAJOR✅ CleanOverwrite-and-warn logic is straightforward and correct
2Threading & ConcurrencyBLOCKING✅ CleanConfigureAwait(false) used correctly; no async anti-patterns
3Security & IPC Contract SafetyBLOCKING✅ CleanNo new security surface
4Public API & Binary CompatibilityBLOCKING✅ Cleaninternal sealed class — no public API changes
5Performance & AllocationsMAJOR✅ CleanSimpler code path; retry loop and string allocations removed
6Cross-TFM CompatibilityMAJORN/ANo TFM-specific APIs
7Test QualityMAJOR✅ CleanMSTest assertions used correctly; parameter order verified (Assert.Contains(substring, value))
8Error HandlingMAJOR✅ CleanIOException propagation tested; ExistFile mock properly set up
9LocalizationMAJOR✅ CleanReuses existing CtrfReportFileExistsAndWillBeOverwritten resource
10Naming & StyleMINOR✅ CleanFollows .editorconfig conventions
11DocumentationMINOR✅ CleanComments explain why (alignment rationale) not what
12LoggingMINORN/AWarning surfaced via return tuple — no direct logging changes
13Null SafetyMAJOR✅ Cleanwarning! used only after Assert.IsNotNull guard
14Resource ManagementMAJOR✅ CleanNo resource leaks introduced
15Scope & PR HygieneMINOR✅ CleanSingle concern: behavior alignment
16Test CoverageMAJOR✅ CleanOld behavior tests replaced with new behavior equivalents
17IPC Contract StabilityBLOCKINGN/ANo wire format changes
18Backward CompatibilityBLOCKING⚠️ AdvisoryBehavioral change for default file names (overwrite vs. suffix). Intentional normalization — internal class, aligns with all other report extensions.
19Simplicity & ReadabilityMINOR✅ Clean−66 / +13 net — major simplification
20Dependency ManagementMINOR✅ CleanNo dependency changes
21Configuration & DefaultsMINOR✅ CleanDefault behavior now consistent across all report formats
22PowerShell / Script QualityMINORN/ANo 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

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
MetricValue
Status❌ FAILED
Duration160.2 s
Projects49
Errors7 (6 RS0051 + 1 "Build failed")
Warnings0

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)
#CodeFileLineMessage
1RS0051BaseSerializer.cs359ReadFields(...) not part of declared API
2RS0051BaseSerializer.cs380WriteListPayload<T>(...) not part of declared API
3RS0051BaseSerializer.cs359(duplicate — net9.0 TFM)
4RS0051BaseSerializer.cs380(duplicate — net9.0 TFM)
5RS0051BaseSerializer.cs359(duplicate — netstandard2.0 TFM)
6RS0051BaseSerializer.cs380(duplicate — netstandard2.0 TFM)
7Build 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
Amaury Levé (Evangelink) merged commit 1ece911 into mainJul 9, 2026
27 of 34 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/ctrf-overwrite-align branch July 9, 2026 16:27
Sign up for freeto 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

@Evangelink