Skip to content

Add public injectable IArtifactNamingService to Microsoft.Testing.Platform#9783

Merged
Evangelink merged 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service-only
Jul 9, 2026
Merged

Add public injectable IArtifactNamingService to Microsoft.Testing.Platform#9783
Evangelink merged 1 commit into
mainfrom
dev/amauryleve/artifact-naming-service-only

Conversation

@Evangelink

@Evangelink Evangelink commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

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

Changes

  • IArtifactNamingService (public) — single member string ResolveFileName(string template). Expands the standard placeholders ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and sanitizes the leaf file name while preserving any directory portion of the template.
  • ArtifactNamingService (internal) — implementation that wraps the existing ArtifactNamingHelper templating with process name/id/timestamp resolved from ITestApplicationModuleInfo, IEnvironment and IClock.
  • ArtifactFileNameSanitizer (internal) — a core-internal copy of the invalid-char / reserved-name (CON/PRN/AUX/NUL/COM1-9/LPT1-9/CLOCK$) sanitization from the extension-side ReportFileNameSanitizer. The extension copy has shipped InternalAPI entries and is intentionally left in place; a comment marks the two for future consolidation.
  • Registered as a common service in TestHostBuilder.CommonServices.cs and resolvable via a new public ServiceProviderExtensions.GetArtifactNamingService(this IServiceProvider) accessor.
  • Public/internal API tracking files updated.
  • New ArtifactNamingServiceTests cover placeholder expansion, invalid-char sanitization, directory-portion preservation, and unknown-placeholder passthrough.

Verification

Built Microsoft.Testing.Platform.UnitTests and ran --filter "FullyQualifiedName~ArtifactNamingService" — 4/4 passing.

Note

This is one of two PRs split out from #9780 (the other, #9782, aligns the CTRF report writer overwrite behavior).

The pre-existing RS0051 InternalAPI baseline break from #9774 (BaseSerializer.ReadFields/WriteListPayload missing InternalAPI entries) that previously blocked the build has since been fixed on main; this branch was rebased onto that fix, so the two BaseSerializer entries now appear alongside the new service entries in InternalAPI.Unshipped.txt.

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR exposes Microsoft.Testing.Platform's artifact file-name templating as a new public, injectable core service, so that consumers can resolve placeholder templates ({pname}, {pid}, {asm}, {tfm}, {arch}, {time}) and get a sanitized leaf file name without relying on the existing linked-source ([Embedded] helper) pattern. It is one of two PRs split out from #9780 (the companion #9782 handles CTRF overwrite behavior).

The implementation wraps the existing ArtifactNamingHelper templating and mirrors the extension-side sanitization logic. I verified the service is registered as a common service and resolves correctly by interface (the ServiceProvider matches instances via IsInstanceOfType), the constructor dependencies (systemEnvironment, systemClock, _testApplicationModuleInfo) are all in scope at the registration site, IEnvironment.ProcessId/IClock.UtcNow/ArgumentGuard.Ensure all exist as used, and the new internal/public API tracking entries are correctly formatted and placed (InternalAPI tracking is in use in this project — a prior memory to the contrary was outdated and has been down-voted).

Changes:

  • Adds public IArtifactNamingService + ServiceProviderExtensions.GetArtifactNamingService accessor, backed by internal ArtifactNamingService.
  • Adds a core-internal ArtifactFileNameSanitizer copy of the extension-side ReportFileNameSanitizer (intentional duplication, documented for future consolidation).
  • Registers the service in TestHostBuilder.CommonServices.cs, updates Public/Internal API baselines, and adds ArtifactNamingServiceTests.
Show a summary per file
File Description
Services/IArtifactNamingService.cs New public interface with a single ResolveFileName(string) member and placeholder docs.
Services/ArtifactNamingService.cs Internal implementation resolving process name/id/time and sanitizing the leaf name.
Services/ArtifactFileNameSanitizer.cs Core-internal copy of the extension invalid-char/reserved-name sanitizer.
Services/ServiceProviderExtensions.cs New public GetArtifactNamingService accessor.
Hosts/TestHostBuilder.CommonServices.cs Registers ArtifactNamingService as a common service.
PublicAPI/PublicAPI.Unshipped.txt Declares the new public interface + accessor.
InternalAPI/InternalAPI.Unshipped.txt Declares the new internal types/members.
Services/ArtifactNamingServiceTests.cs Tests placeholder expansion, invalid-char sanitization, directory preservation, unknown-placeholder passthrough.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Medium

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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


Review Summary

Clean design — exposing the file-name templating as a public injectable service is the right move. The interface is minimal (single member), the implementation correctly delegates to the existing ArtifactNamingHelper, and the sanitizer mirrors the shipped extension-side copy byte-for-byte. Service registration and resolution via IsInstanceOfType will work correctly.

Dimension Verdicts

# Dimension Verdict
1 Algorithmic Correctness ⚠️ Minor edge case (see inline: Path.GetFileName → empty on trailing separator)
2 Public API Design ✅ Minimal surface, no init, correctly in PublicAPI.Unshipped.txt
3 Backward Compatibility ✅ Additive only — new interface, new extension method
4 Performance ✅ No hot-path concerns; per-call GetStandardReplacements is acceptable for a naming utility
5 Cross-TFM Correctness GeneratedRegex conditional on NET7_0_OR_GREATER with fallback
6 Thread Safety ✅ No mutable shared state; each call is self-contained
7 Error Handling ⚠️ See inline: guard at service boundary for empty leaf name
8 Localization N/A — no user-facing strings added
9 IPC Contract N/A
10 Security ✅ Path sanitization prevents invalid characters; directory portion preserved as-is (callers control the base directory)
11 Testability ✅ Constructor-injected dependencies, Moq-based tests
12 Test Quality ✅ Good coverage of core scenarios; minor gap on reserved-name and {asm}/{tfm}/{arch} paths (hard to mock static reflection)
13 Code Style ⚠️ Minor: missing blank line in CommonServices.cs (see inline)
14 Documentation ⚠️ XML doc for {asm}/{tfm} should note "unknown" fallback (see inline)
15 InternalAPI Tracking ✅ Entries match the actual internal surface
16 Scope Discipline ✅ Single concern
17 Simplicity ✅ Clean delegation pattern
18 Build Infrastructure N/A
19 Analyzer/CodeFix N/A
20 Extension Points ✅ Registered as common service; extensible via DI replacement
21 CLI Options N/A
22 Agentic Workflows N/A

CI Status

Linux builds (Debug & Release) are failing. Please verify the failures are unrelated to this change before merging.

Summary

Three inline comments posted — one style nit, one robustness improvement for an edge case, and one documentation accuracy suggestion. None are blocking. Overall this is a well-structured addition to the platform.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🔴 Build Failure Analysis

Status: 7 errors, all RS0051 (undeclared API)
Root cause: Two new BaseSerializer methods from the base branch commit a946ef7 ("Stabilize extension UIDs and add naming governance") are missing from InternalAPI.Unshipped.txt.

Errors (deduplicated — repeated per TFM: net8.0, net9.0, netstandard2.0)

File Line Symbol
BaseSerializer.cs 359 ReadFields(Stream, Func<ushort, int, bool>)
BaseSerializer.cs 380 WriteListPayload<T>(Stream, ushort, T[]?, Action<Stream, T>)

Why this fails

The project tracks internal API surface via InternalAPI.Unshipped.txt (enforced by the RS0051 Roslyn analyzer). The base commit added ReadFields and WriteListPayload<T> as protected static methods to the internal abstract class BaseSerializer but did not add the corresponding entries. This PR inherits that gap.

Cascading failures

Microsoft.Testing.Extensions.CrashDump.csproj and Microsoft.Testing.Extensions.AzureFoundry.csproj failed because they depend on Microsoft.Testing.Platform.csproj, which failed to compile.

Fix

Add the following two lines 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

These should be inserted in alphabetical order among the existing entries.

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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copilot AI review requested due to automatic review settings July 9, 2026 17:55
@Evangelink Evangelink force-pushed the dev/amauryleve/artifact-naming-service-only branch from 17095c1 to 425820e Compare July 9, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 5
  • Review effort level: Low

Comment thread src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt Outdated
Comment thread src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingService.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Platform/Services/IArtifactNamingService.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Platform/Services/ArtifactFileNameSanitizer.cs Outdated
@github-actions

This comment has been minimized.

@Evangelink Evangelink added the state/needs-review Awaiting review from the team. label Jul 9, 2026
@Evangelink Evangelink enabled auto-merge (squash) July 9, 2026 18:21
Copilot AI review requested due to automatic review settings July 9, 2026 18:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Medium

@github-actions

This comment has been minimized.

Copilot AI review requested due to automatic review settings July 9, 2026 19:05
@Evangelink Evangelink force-pushed the dev/amauryleve/artifact-naming-service-only branch from d58afa3 to fe9bf60 Compare July 9, 2026 19:05
…tform

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Evangelink Evangelink force-pushed the dev/amauryleve/artifact-naming-service-only branch from fe9bf60 to 3640da3 Compare July 9, 2026 19:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

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

Copilot AI review requested due to automatic review settings July 9, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

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

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test quality grade — PR #9783

GradeTestNotes
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
ExpandsStandardPlaceholders
Six assertions (StartsWith, Contains, EndsWith, three DoesNotContain) verify placeholder expansion, output format, and absence of raw tokens — no issues found.
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
LeavesUnknownPlaceholderAsIs
Two Contains checks verify {unknown} is preserved and {pname} is expanded — consider AreEqual("{unknown}_MyApp.json", result) for stronger full-output verification.
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
PrefixesReservedDeviceName
Precise equality assertion on the reserved-device-name edge case; explanatory comment clarifies the Windows constraint — no issues found.
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
PreservesDirectoryPortionAndSanitizesLeaf
Path.Combine equality covers directory preservation and leaf sanitization in one cross-platform check — no issues found.
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
SanitizesInvalidCharactersInExpandedName
Tight equality assertion verifies asterisk-to-underscore sanitization precisely — no issues found.
A (90–100) new ArtifactNamingServiceTests.
ResolveFileName_
ThrowsWhenTemplateHasNoLeafFileName
ThrowsExactly verifies the precise exception type; explanatory comment identifies the invalid-template scenario — no issues found.

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

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

@Evangelink Evangelink disabled auto-merge July 9, 2026 20:23
@Evangelink Evangelink enabled auto-merge (squash) July 9, 2026 20:24
@Evangelink Evangelink merged commit 1b4496f into main Jul 9, 2026
47 checks passed
@Evangelink Evangelink deleted the dev/amauryleve/artifact-naming-service-only branch July 9, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-review Awaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants