-
Notifications
You must be signed in to change notification settings - Fork 294
Add artifact naming service with template support for consistent naming across extensions #7856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
118 commits
Select commit
Hold shift + click to select a range
d51897b
Initial plan
Copilot eb60a65
Initial exploration and plan for artifact naming service
Copilot 32a3424
Add artifact naming service implementation with backward compatibility
Copilot 3f373e1
Add integration test for artifact naming service templates and fix mi…
Copilot 4d2d2f3
Fix process name retrieval and improve integration test robustness
Copilot ca27413
Change process-name placeholder to pname for consistency with short n…
Copilot 3ee129e
Change assembly placeholder to asm for consistency with short names
Copilot 042d74d
Merge branch 'main' into copilot/fix-6586
Evangelink 63a96be
Fixes
Evangelink 9afa902
Apply suggestions from code review
Evangelink 1de7408
Merge branch 'main' into copilot/fix-6586
Evangelink f934d44
Fixes
Evangelink 8e9c2a7
More fixes
Evangelink ef9e0ea
Merge branch 'main' into copilot/fix-6586
Evangelink a15fe1f
Merge remote-tracking branch 'origin/main' into dev/amauryleve/artifa…
Evangelink 0f47404
Merge main and fix code review issues in artifact naming service
Evangelink 8307cf1
Change regex pattern from [^>]+ to .+? for better readability (equiva…
Copilot f00ad73
Make placeholder matching case-sensitive and change time format to yy…
Copilot 20967d0
Improve test clarity: rename IGNORED to WRONG_CASE in case-sensitivit…
Copilot 04ba2db
Merge remote-tracking branch 'origin/main' into dev/amauryleve/artifa…
Evangelink a6e2766
Remove <id> placeholder, reuse TargetFrameworkParser for TFM, use cas…
Evangelink d163cb6
Replace IArtifactNamingService DI with static ArtifactNamingHelper
Evangelink e6080f7
Fix review issues: restore dropped field declarations, remove dead Ve…
Evangelink c4856f3
Move ArtifactNamingHelper docs to microsoft.testing.platform folder
Evangelink 20f6617
Address review comments: guard whitespace templates, strengthen integ…
Evangelink 707cd22
Merge remote-tracking branch 'origin/dev/amauryleve/artifact-naming-s…
Evangelink 2f52acd
Use real TFM from TargetFrameworkAttribute for <tfm> placeholder
Evangelink ef66b16
Make HangDump_TemplateFileName_CreateDump data-driven with <tfm>
Evangelink f4db2b2
Apply suggestion from @Evangelink
Evangelink ab86bcf
Address PR review comments
Evangelink 302a7e8
Address PR review comments
Evangelink cc693ff
Ensure dump destination directory exists for template paths
Evangelink d23cb1d
Normalize dump path with GetFullPath and add subdirectory template in…
Evangelink 8e0b53b
Apply suggestions from code review
Evangelink 48f5f6e
Fix log accumulation across DynamicData test invocations (#7925)
Evangelink 8dfae36
[Test Improver] Add unit tests for LoggerFactoryProxy (#7916)
Evangelink 667ce70
[Lean Squad] feat(ci): Task 9 — fix elan SHA256, cache ordering, cond…
Evangelink 7cb2ac1
Add code fix for MSTEST0042 — DuplicateDataRow (#7896)
Copilot 67e75a9
[Test Improver] Add unit tests for PasteArguments.AppendArgument (#7888)
Evangelink 98f2402
Add code duplication analysis workflow (#7874)
Evangelink 6954767
[Lean Squad] feat(fv): Task 2 — informal spec for CommandLineParseRes…
Evangelink ae80d6d
File Diet: Split CollectionAssert.cs into focused partial-class files…
Copilot 76e55bd
Add Repo Historian workflow and wire reviewers to history data (#7873)
Evangelink 0cdc09d
Convert ExitCodes from static class to enum (#7876)
Evangelink 05e25c1
Fix minor inconsistencies in analyzer/fixer naming and documentation …
Copilot eb26b5b
Eliminate delegate allocation in AbortForMaxFailedTestsExtension (#7923)
Copilot 30276ff
Harden Copilot workflow integrity, lockdown, and toolset configuratio…
Evangelink f625873
[Efficiency Improver] perf: eliminate yield-iterator state machine in…
Evangelink f0927bd
[Efficiency Improver] perf: skip PropertyBag construction in BFSTestN…
Evangelink 24acd67
[Lean Squad] feat(fv): Task 2 — informal spec for CommandLineParser.P…
Evangelink 6e63de7
[main] Update dependencies from microsoft/testfx (#7945)
dotnet-maestro[bot] 6077e9d
[main] Update dependencies from dotnet/arcade (#7944)
dotnet-maestro[bot] 82c0c24
[main] Update dependencies from devdiv/DevDiv/vs-code-coverage (#7946)
dotnet-maestro[bot] db698b8
Localized file check-in by OneLocBuild Task: Build definition ID 1218…
dotnet-bot 63e7509
Refactor `TestNodeProperties.cs` into focused single-responsibility f…
Copilot 6d1fe5f
[Lean Squad] feat(ci): Task 9 — FV docs validation workflow + updated…
Evangelink 3d6d158
[docs] Update glossary - daily scan (#7960)
Evangelink 5b44fae
[Lean Squad] feat(fv): Task 2 — informal spec for TreeNodeFilter.Matc…
Evangelink b248c13
[Perf Improver] perf: eliminate LINQ iterator allocations in GetTestC…
Evangelink bc1372e
Add code fix for MSTEST0060 — DuplicateTestMethodAttribute (#7894)
Copilot 3ca1d11
Localized file check-in by OneLocBuild Task: Build definition ID 1218…
dotnet-bot 8ffe187
[main] Update dependencies from microsoft/testfx (#7972)
dotnet-maestro[bot] 1704a3f
[main] Update dependencies from dotnet/arcade (#7971)
dotnet-maestro[bot] 437b705
[main] Update dependencies from microsoft/testfx (#7989)
dotnet-maestro[bot] 5c516dc
[main] Update dependencies from dotnet/arcade (#7988)
dotnet-maestro[bot] 1bc2d40
[main] Update dependencies from microsoft/testfx (#7997)
dotnet-maestro[bot] 0df1667
[main] Update dependencies from microsoft/testfx (#8005)
dotnet-maestro[bot] 1526cff
[main] Update dependencies from devdiv/DevDiv/vs-code-coverage (#8022)
dotnet-maestro[bot] 876d97f
[main] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 5.5.0-2.26…
dependabot[bot] 625ca0b
[main] Bump AwesomeAssertions from 9.3.0 to 9.4.0 (#8008)
dependabot[bot] eaacf8f
[main] Update dependencies from devdiv/DevDiv/vs-code-coverage (#8038)
dotnet-maestro[bot] 1bf932e
[main] Update dependencies from microsoft/testfx (#8039)
dotnet-maestro[bot] 7e67493
[main] Update dependencies from dotnet/arcade (#8049)
dotnet-maestro[bot] 209b625
[main] Update dependencies from microsoft/testfx (#8050)
dotnet-maestro[bot] 5583b45
[main] Update dependencies from devdiv/DevDiv/vs-code-coverage (#8051)
dotnet-maestro[bot] 7b7caf6
[main] Update dependencies from dotnet/arcade (#8061)
dotnet-maestro[bot] 64a1e75
[main] Update dependencies from microsoft/testfx (#8062)
dotnet-maestro[bot] cb15a07
[main] Update dependencies from microsoft/testfx (#8072)
dotnet-maestro[bot] 97d0f53
[main] Bump dotnet-coverage from 17.14.2 to 18.7.0 (#8009)
dependabot[bot] 18ff01c
Recompile GH workflows (#8087)
Evangelink 311babc
[docs] Update glossary - daily scan (#8089)
Evangelink b652a7f
[main] Bump Microsoft.CodeAnalysis.Analyzers from 3.11.0 to 5.3.0 (#8…
dependabot[bot] 3ce41dc
Fix typos, formatting, and class names in AnalyzerReleases.Shipped.md…
Copilot b8fe52c
[docs] Update glossary - weekly full scan (#8090)
Evangelink 17f6a80
Fix O(n²) output accumulation in data-driven tests causing large TRX …
Evangelink 810ac6b
Refactor TestMethodInfo.cs into focused partial class files (#7985)
Copilot 4a99647
[code-simplifier] Remove redundant `.ToString()` in `TimingInfo.ToStr…
Evangelink e110ba1
[main] Bump FSharp.Core from 9.0.202 to 11.0.100 (#8010)
dependabot[bot] b19e79e
Localized file check-in by OneLocBuild Task: Build definition ID 1218…
dotnet-bot 89ae441
[Lean Squad] feat: [Lean Squad] informal spec for EnvironmentVariable…
Evangelink f7f6f85
Bump nerdbank deps (#8096)
Evangelink a4818f0
MSTEST0007: report concrete condition attribute instead of ConditionB…
Copilot b5f3b3e
Fix typo: 'CompositeExtensonFactory' -> 'CompositeExtensionFactory' (…
Evangelink 8c20a2f
Analyzer catalog hygiene: retire MSTEST0039 constant and replace unac…
Copilot b366cc4
Bump OTel (#8099)
Evangelink 3615c41
[Lean Squad] feat(fv): Task 2 + Task 9 — CommandLineOptionsValidator …
Evangelink 77f0258
Localized file check-in by OneLocBuild Task: Build definition ID 1218…
dotnet-bot 90699de
Fix Hash input in _GenerateVersionSourceFileCache target (#8102)
Evangelink 9ad9a0c
[Lean Squad] feat(fv): Task 3+9 — Lean 4 formal spec for TreeNodeFilt…
Evangelink 009132b
[Lean Squad] feat(fv): Task 1 — expand TARGETS.md to 17 targets; add …
Evangelink 2404223
[Lean Squad] feat(fv): Task 2 — informal spec for PasteArguments.Appe…
Evangelink beb7aa5
[Lean Squad] Task 9: CI automation improvements — TARGETS.md expansio…
Evangelink 5f88e21
Remove Lean formal verification agentic workflow (#8121)
Evangelink 37f2673
[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttr…
Evangelink 167522a
Update changelogs for v4.2.2 release (#8132)
Evangelink 0b832eb
[Test Improver] Add unit tests for LoggingManager.BuildAsync (#8124)
Evangelink d51f424
Bump test deps (#8110)
Evangelink 4724fcc
[Test Improver] Add unit tests for ExtensionValidationHelper.Validate…
Evangelink a297469
Add code fix for MSTEST0031 — DoNotUseSystemDescriptionAttribute (#7898)
Copilot 0aa8d87
Merge branch 'main' into dev/amauryleve/artifact-naming-service
Evangelink 5f8bb21
Address PR review comments: improve tfm resolution and null handling
Evangelink a3256e5
Merge branch 'main' into dev/amauryleve/artifact-naming-service
Evangelink 50f8d99
Address second round of PR review comments
Evangelink c77ef7d
Merge branch 'dev/amauryleve/artifact-naming-service' of https://gith…
Evangelink 446c2af
Address third round of PR review comments
Evangelink 369256b
Use OS-appropriate path comparison for traversal guard
Evangelink 7f02e33
Use syntax suggested by nohwnd
Evangelink 67401d9
Address review feedback: drop {os}, source-gen regex, centralize valu…
Evangelink File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,4 +10,4 @@ | |
| "rollForward": false | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Artifact Naming Helper | ||
|
|
||
| The `ArtifactNamingHelper` is a shared static helper that provides a standardized way to generate consistent names and paths for test artifacts. It is compiled into each extension that needs it via file linking (no service registration or IVT required). | ||
|
|
||
| ## Template-Based Naming | ||
|
|
||
| Use placeholders in curly braces to create dynamic file names. Placeholder matching is **case-sensitive** — use lowercase placeholder names (e.g., `{pname}`, not `{PName}`). | ||
|
|
||
| ```text | ||
| {pname}_{pid}_{time}_hang.dmp | ||
| ``` | ||
|
|
||
| Resolves to: `MyTests_12345_2025-09-22_13-49-34.0000000_hang.dmp` | ||
|
|
||
| ## Available Placeholders | ||
|
|
||
| | Placeholder | Description | Example | | ||
| |-------------|-------------|---------| | ||
| | `{pname}` | Name of the process | `MyTests` | | ||
| | `{pid}` | Process ID | `12345` | | ||
| | `{asm}` | Assembly name (entry assembly, or `unknown` if unavailable) | `MyTests` | | ||
| | `{tfm}` | Target framework moniker (best-effort, detected at runtime — may differ from the build-time TFM, e.g. a `net462` assembly running on .NET Framework 4.8 reports `net481`) | `net9.0`, `net8.0` | | ||
| | `{time}` | Timestamp (high precision) | `2025-09-22_13-49-34.0000000` | | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| Legacy patterns like `%p` continue to work in the hang dump extension. | ||
|
|
||
| ## Custom Replacements | ||
|
|
||
| Override default values for specific scenarios: | ||
|
|
||
| ```csharp | ||
| var replacements = new Dictionary<string, string> | ||
| { | ||
| ["pname"] = "Notepad", | ||
| ["pid"] = "1111" | ||
| }; | ||
|
|
||
| string result = ArtifactNamingHelper.ResolveTemplate("{pname}_{pid}.dmp", replacements); | ||
| // Result: "Notepad_1111.dmp" | ||
| ``` | ||
|
|
||
| ## Hang Dump Integration | ||
|
|
||
| The hang dump extension uses the artifact naming helper and supports both legacy and modern patterns: | ||
|
|
||
| ```text | ||
| # Legacy pattern (still works) | ||
| --hangdump-filename "mydump_%p.dmp" | ||
|
|
||
| # New template pattern | ||
| --hangdump-filename "{pname}_{pid}_{time}_hang.dmp" | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/Platform/Microsoft.Testing.Platform/Services/ArtifactNamingHelper.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.Testing.Platform.OutputDevice; | ||
|
|
||
| namespace Microsoft.Testing.Platform.Services; | ||
|
|
||
| [Embedded] | ||
| internal static partial class ArtifactNamingHelper | ||
| { | ||
| #if NET | ||
| private static readonly Regex TemplateFieldRegex = GetTemplateFieldRegex(); | ||
|
|
||
| [GeneratedRegex(@"\{([^}]+)\}")] | ||
| private static partial Regex GetTemplateFieldRegex(); | ||
| #else | ||
| private static readonly Regex TemplateFieldRegex = new(@"\{([^}]+)\}", RegexOptions.Compiled); | ||
|
Evangelink marked this conversation as resolved.
|
||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// Builds the standard set of placeholder replacements for artifact naming. | ||
| /// Consumers pass process-specific values; the helper resolves the rest (asm, tfm). | ||
| /// </summary> | ||
| /// <param name="processName">The name of the process (resolves <c>{pname}</c>).</param> | ||
| /// <param name="processId">The process ID (resolves <c>{pid}</c>).</param> | ||
| /// <param name="timestamp">The timestamp to use (resolves <c>{time}</c>).</param> | ||
| public static Dictionary<string, string> GetStandardReplacements(string processName, string processId, DateTimeOffset timestamp) | ||
| { | ||
| var replacements = new Dictionary<string, string>(StringComparer.Ordinal) | ||
| { | ||
| ["pname"] = processName, | ||
| ["pid"] = processId, | ||
| ["time"] = timestamp.ToString("yyyy-MM-dd_HH-mm-ss.fffffff", CultureInfo.InvariantCulture), | ||
| }; | ||
|
|
||
| string? asmName = Assembly.GetEntryAssembly()?.GetName().Name; | ||
| replacements["asm"] = asmName ?? "unknown"; | ||
|
|
||
| string? tfm = TargetFrameworkParser.GetShortTargetFramework(Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkDisplayName) | ||
| ?? TargetFrameworkParser.GetShortTargetFramework(RuntimeInformation.FrameworkDescription); | ||
| replacements["tfm"] = tfm ?? "unknown"; | ||
|
|
||
| return replacements; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Resolves a template pattern by replacing {placeholder} tokens with values from the provided dictionary. | ||
| /// Unknown placeholders are preserved as-is. Placeholder matching is always case-sensitive (ordinal). | ||
| /// </summary> | ||
|
Evangelink marked this conversation as resolved.
|
||
| public static string ResolveTemplate(string template, IDictionary<string, string>? replacements = null) | ||
| { | ||
| if (RoslynString.IsNullOrWhiteSpace(template)) | ||
|
Evangelink marked this conversation as resolved.
|
||
| { | ||
| throw new ArgumentException("Template cannot be null, empty, or whitespace.", nameof(template)); | ||
| } | ||
|
|
||
| if (replacements is null || replacements.Count == 0) | ||
| { | ||
| return template; | ||
| } | ||
|
|
||
| // Ensure ordinal (case-sensitive) comparison regardless of the caller-provided dictionary comparer. | ||
| Dictionary<string, string> ordinalReplacements = replacements is Dictionary<string, string> dict && dict.Comparer == StringComparer.Ordinal | ||
| ? dict | ||
| : new Dictionary<string, string>(replacements, StringComparer.Ordinal); | ||
|
|
||
| return TemplateFieldRegex.Replace(template, match => | ||
| { | ||
| string fieldName = match.Groups[1].Value; | ||
| return ordinalReplacements.TryGetValue(fieldName, out string? value) ? value : match.Value; | ||
| }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.