Align CTRF report writer overwrite behavior and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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 and add public IArtifactNamingService - #9780

Closed
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service
Closed

Align CTRF report writer overwrite behavior and add public IArtifactNamingService#9780
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Two related changes to MTP report file naming.

1. Align CTRF report writer to overwrite + warn (matches TRX/HTML/JUnit)

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 aligns it with the shared rule already used by the TRX, HTML and JUnit report extensions: always overwrite (FileMode.Create) and emit a warning when the file already existed, giving users a single, predictable rule.

  • 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 was replaced by GenerateReportAsync_OverwritesAndWarns_When_DefaultFileExists, and the IOException test was renamed to ..._When_WriteFails — both mirroring the HtmlReportEngineTests equivalents. (27/27 passing.)

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

2. New public, injectable IArtifactNamingService in MTP core

Exposes file-name templating as a public, injectable core service so it can be consumed outside the existing linked-source ([Embedded] helper) pattern.

  • IArtifactNamingService (public) — single member ResolveFileName(string template); expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion.
  • ArtifactNamingService (internal) — wraps the existing ArtifactNamingHelper templating with process/name/time resolution from ITestApplicationModuleInfo/IEnvironment/IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char/reserved-name sanitization from the extension-side ReportFileNameSanitizer (which has shipped InternalAPI entries and is intentionally left in place; the two are marked for future consolidation).
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(...) accessor.
  • Public/internal API tracking files updated; new ArtifactNamingServiceTests (4/4 passing) cover placeholder expansion, sanitization, directory preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and Microsoft.Testing.Extensions.UnitTests and ran the targeted filters:

  • ArtifactNamingService — 4/4 passing
  • CtrfReportEngineTests — 27/27 passing

⚠️ Heads-up: pre-existing, unrelated baseline break

There is a pre-existing repo-wide build break introduced by #9774 that is not addressed here and needs a separate fix: IPC/Serializers/BaseSerializer.cs gained two protected static methods (ReadFields, WriteListPayload) but no matching InternalAPI.Unshipped.txt entries were added, so analyzer RS0051 fails as an error in every project that links that source (Microsoft.Testing.Platform and the HangDump / MSBuild / Retry / TrxReport extensions). A transient local workaround was applied only to verify this PR and then reverted, so it is intentionally not part of this change.

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

Copy link
Copy Markdown
MemberAuthor

Split into two independent PRs per maintainer request: #9782 (Align CTRF report writer overwrite behavior with TRX/HTML/JUnit) and #9783 (Add public injectable IArtifactNamingService to Microsoft.Testing.Platform). Closing this combined PR in favor of those two.

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 makes two related changes to Microsoft.Testing.Platform (MTP) artifact/report file naming:

  1. Aligns the CTRF report writer to the shared "overwrite + warn" rule.CtrfReportEngine was the only report extension that, for default-generated file names, used FileMode.CreateNew plus a _1/_2 disambiguating-suffix retry loop. The change replaces that with a small WriteAsync that always overwrites (FileMode.Create) and warns when the file pre-existed — mirroring the HtmlReportEngine/TrxReportEngine/JUnitReportEngine behavior (verified all three already share this exact rule via ReportEngineBase).
  2. Adds a new public, injectable IArtifactNamingService in MTP core. It exposes file-name templating (placeholder expansion + leaf sanitization) as a common service, backed by an internal ArtifactNamingService (wrapping the existing [Embedded]ArtifactNamingHelper) and a core-internal ArtifactFileNameSanitizer (an intentional, documented copy of the extension-side ReportFileNameSanitizer).

Changes:

  • Replace CTRF's WriteWithRetryAsync/SplitCtrfExtension with an overwrite-and-warn WriteAsync, and drop the now-unused fileNameExplicitlyProvided threading.
  • Introduce IArtifactNamingService (public) + ArtifactNamingService/ArtifactFileNameSanitizer (internal), register it as a common service, and add a ServiceProviderExtensions.GetArtifactNamingService accessor.
  • Update Public/Internal API tracking files and add unit tests for both changes.
Show a summary per file
FileDescription
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.FileWriter.csReplaces retry/suffix loop with overwrite-and-warn WriteAsync, matching sibling report engines.
src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.csGenerateReportCoreAsync calls WriteAsync and discards the WasExplicit flag.
src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.csNew public interface with documented ResolveFileName(template) and placeholder list.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.csInternal implementation wrapping ArtifactNamingHelper + leaf sanitization/directory preservation.
src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.csCore-internal copy of ReportFileNameSanitizer, documented as pending consolidation.
src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.csAdds public GetArtifactNamingService accessor.
src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.CommonServices.csRegisters ArtifactNamingService as a common service.
src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txtDeclares the new public interface + extension accessor.
src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txtDeclares the new internal types/members.
test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/ArtifactNamingServiceTests.csNew tests for placeholder expansion, sanitization, directory preservation, unknown placeholders.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.csUpdates default-name tests to expect FileMode.Create overwrite-and-warn semantics.

Notes from verification (no comments stored):

  • The service resolves correctly through the public accessor: ServiceProvider.GetServicesInternal uses IsInstanceOfType, so registering the concrete ArtifactNamingService satisfies GetRequiredService<IArtifactNamingService>(), and the type is not in InternalOnlyExtensions.
  • The new public interface uses a plain method (no init accessors) and is declared in PublicAPI.Unshipped.txt; InternalAPI entries for the new internal types/members are complete.
  • ArtifactNamingService.ResolveFileName matches the established ReportFileNameHelper.ResolveAndSanitize behavior (sanitize leaf, preserve directory), and ArtifactFileNameSanitizer is a byte-for-byte copy of ReportFileNameSanitizer aside from namespace — duplication is explicitly documented as intentional pending consolidation.
  • The CTRF change leaves no dangling references to the removed helpers, and the base fields it stopped using directly remain in use elsewhere.

Review details

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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — Two new protected static methods on BaseSerializer (introduced in base commit a946ef7) are missing from InternalAPI.Unshipped.txt, triggering RS0051 across all 3 TFMs.

Root cause: Missing internal API declarations for BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> (RS0051 × 6)

The base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") added two protected static methods to BaseSerializer.cs:

  1. ReadFields(Stream stream, Func<ushort, int, bool> tryReadField) — line 359
  2. WriteListPayload<T>(Stream stream, ushort fieldId, T[]? list, Action<Stream, T> writeItem) — line 380

These were not declared in InternalAPI/InternalAPI.Unshipped.txt. Since BaseSerializer is tracked via internal API files (not public), the fix is to add both entries there.

Fix — Append to src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt:

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

The CrashDump and CtrfReport project failures are cascading — they depend on Microsoft.Testing.Platform which failed to compile.


Build overview
MetricValue
Status❌ FAILED
Duration164.1 s
Projects49
Errors7 (6 unique RS0051 + 1 "Build failed")
Warnings0

Failed projects:

ProjectDurationReason
Microsoft.Testing.Platform.csproj46.4 sRS0051 errors (root cause)
Microsoft.Testing.Extensions.CrashDump.csproj79.3 sCascading (depends on MTP)
Microsoft.Testing.Extensions.CtrfReport.csproj78.2 sCascading (depends on MTP)
All MSBuild errors (7)
CodeProjectFile:LineMessage
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359ReadFields(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380WriteListPayload<T>(...) not part of declared API
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — net9.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:359(duplicate — netstandard2.0 TFM)
RS0051Microsoft.Testing.PlatformBaseSerializer.cs:380(duplicate — netstandard2.0 TFM)
Build.projBuild failed.

🤖 Generated by the Build Failure Analysis workflow · commit 7591119

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

@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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 168 AIC · ⌖ 6.91 AIC · ⊞ 7.3K ·

Microsoft.Testing.Platform.Services.ArtifactNamingService
Microsoft.Testing.Platform.Services.ArtifactNamingService.ArtifactNamingService(Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Helpers.IClock! clock) -> void
Microsoft.Testing.Platform.Services.ArtifactNamingService.ResolveFileName(string! template) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!

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.

The base branch (a946ef7) introduced BaseSerializer.ReadFields and BaseSerializer.WriteListPayload<T> as protected static methods but they were never declared here, causing RS0051 errors across all 3 TFMs.

Suggested change
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
static Microsoft.Testing.Platform.Services.ArtifactFileNameSanitizer.ReplaceInvalidFileNameChars(string! fileName) -> string!
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

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