From d88d0ce661bc95fee4770dbb8196b4e907672564 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 13:41:01 +0000 Subject: [PATCH 01/21] Initial plan for issue From 1443d5ea07a1ecdae2d8b97adcaa14bc8f693446 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 13:50:13 +0000 Subject: [PATCH 02/21] Optimize performance of CommandLineOptionsValidator Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLineOptionsValidator.cs | 139 ++++++++++++++---- 1 file changed, 112 insertions(+), 27 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 9becb9e8c5..7a0e7febae 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -1,15 +1,26 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.CommandLine; using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Resources; +using Roslyn.Utilities; namespace Microsoft.Testing.Platform.CommandLine; +[PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] internal static class CommandLineOptionsValidator { + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] public static async Task ValidateAsync( CommandLineParseResult commandLineParseResult, IEnumerable systemCommandLineOptionsProviders, @@ -68,6 +79,7 @@ public static async Task ValidateAsync( return await ValidateConfigurationAsync(extensionOptionsByProvider.Keys, systemOptionsByProvider.Keys, commandLineOptions); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateExtensionOptionsDoNotContainReservedPrefix( Dictionary> extensionOptionsByProvider) { @@ -97,44 +109,75 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedPref : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOptions( Dictionary> extensionOptionsByProvider, Dictionary> systemOptionsByProvider) { - IEnumerable allExtensionOptions = extensionOptionsByProvider.Values.SelectMany(x => x).Select(x => x.Name).Distinct(); - IEnumerable allSystemOptions = systemOptionsByProvider.Values.SelectMany(x => x).Select(x => x.Name).Distinct(); - - IEnumerable invalidReservedOptions = allSystemOptions.Intersect(allExtensionOptions); - if (invalidReservedOptions.Any()) + // Create a HashSet of all system option names for faster lookup + HashSet systemOptionNames = new(); + foreach (var provider in systemOptionsByProvider) { - var stringBuilder = new StringBuilder(); - foreach (string reservedOption in invalidReservedOptions) + foreach (var option in provider.Value) { - IEnumerable faultyProviderNames = extensionOptionsByProvider.Where(tuple => tuple.Value.Any(x => x.Name == reservedOption)).Select(tuple => tuple.Key.DisplayName); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, reservedOption, string.Join("', '", faultyProviderNames))); + systemOptionNames.Add(option.Name); } + } - return ValidationResult.Invalid(stringBuilder.ToTrimmedString()); + StringBuilder? stringBuilder = null; + foreach (var provider in extensionOptionsByProvider) + { + foreach (var option in provider.Value) + { + if (systemOptionNames.Contains(option.Name)) + { + stringBuilder ??= new StringBuilder(); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, + PlatformResources.CommandLineOptionIsReserved, + option.Name, + provider.Key.DisplayName)); + } + } } - return ValidationResult.Valid(); + return stringBuilder?.Length > 0 + ? ValidationResult.Invalid(stringBuilder.ToTrimmedString()) + : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateOptionsAreNotDuplicated( Dictionary> extensionOptionsByProvider) { - IEnumerable duplications = extensionOptionsByProvider.Values.SelectMany(x => x) - .Select(x => x.Name) - .GroupBy(x => x) - .Where(x => x.Skip(1).Any()) - .Select(x => x.Key); - + // Use a dictionary to track option names and their providers + Dictionary> optionNameToProviders = new(); + + foreach (var kvp in extensionOptionsByProvider) + { + var provider = kvp.Key; + foreach (var option in kvp.Value) + { + string name = option.Name; + if (!optionNameToProviders.TryGetValue(name, out var providers)) + { + providers = new List(); + optionNameToProviders[name] = providers; + } + providers.Add(provider); + } + } + + // Check for duplications StringBuilder? stringBuilder = null; - foreach (string duplicatedOption in duplications) + foreach (var kvp in optionNameToProviders) { - IEnumerable faultyProvidersDisplayNames = extensionOptionsByProvider.Where(tuple => tuple.Value.Any(x => x.Name == duplicatedOption)).Select(tuple => tuple.Key.DisplayName); - stringBuilder ??= new(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsDeclaredByMultipleProviders, duplicatedOption, string.Join("', '", faultyProvidersDisplayNames))); + if (kvp.Value.Count > 1) + { + string duplicatedOption = kvp.Key; + stringBuilder ??= new(); + IEnumerable faultyProvidersDisplayNames = kvp.Value.Select(p => p.DisplayName); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsDeclaredByMultipleProviders, duplicatedOption, string.Join("', '", faultyProvidersDisplayNames))); + } } return stringBuilder?.Length > 0 @@ -142,15 +185,34 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateNoUnknownOptions( CommandLineParseResult parseResult, Dictionary> extensionOptionsByProvider, Dictionary> systemOptionsByProvider) { + // Create a HashSet of all valid option names for faster lookup + HashSet validOptionNames = new(); + foreach (var provider in extensionOptionsByProvider) + { + foreach (var option in provider.Value) + { + validOptionNames.Add(option.Name); + } + } + + foreach (var provider in systemOptionsByProvider) + { + foreach (var option in provider.Value) + { + validOptionNames.Add(option.Name); + } + } + StringBuilder? stringBuilder = null; foreach (CommandLineParseOption optionRecord in parseResult.Options) { - if (!extensionOptionsByProvider.Union(systemOptionsByProvider).Any(tuple => tuple.Value.Any(x => x.Name == optionRecord.Name))) + if (!validOptionNames.Contains(optionRecord.Name)) { stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineUnknownOption, optionRecord.Name)); @@ -162,11 +224,12 @@ private static ValidationResult ValidateNoUnknownOptions( : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateOptionsArgumentArity( CommandLineParseResult parseResult, Dictionary providerAndOptionByOptionName) { - StringBuilder stringBuilder = new(); + StringBuilder? stringBuilder = null; foreach (IGrouping groupedOptions in parseResult.Options.GroupBy(x => x.Name)) { // getting the arguments count for an option. @@ -181,23 +244,27 @@ private static ValidationResult ValidateOptionsArgumentArity( if (arity > option.Arity.Max && option.Arity.Max == 0) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsNoArguments, optionName, provider.DisplayName, provider.Uid)); } else if (arity < option.Arity.Min) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsAtLeastArguments, optionName, provider.DisplayName, provider.Uid, option.Arity.Min)); } else if (arity > option.Arity.Max) { + stringBuilder ??= new(); stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionExpectsAtMostArguments, optionName, provider.DisplayName, provider.Uid, option.Arity.Max)); } } - return stringBuilder.Length > 0 + return stringBuilder?.Length > 0 ? ValidationResult.Invalid(stringBuilder.ToTrimmedString()) : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateOptionsArgumentsAsync( CommandLineParseResult parseResult, Dictionary providerAndOptionByOptionName) @@ -221,6 +288,7 @@ private static async Task ValidateOptionsArgumentsAsync( : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateConfigurationAsync( Dictionary>.KeyCollection extensionsProviders, Dictionary>.KeyCollection systemProviders, @@ -234,6 +302,7 @@ private static async Task ValidateConfigurationAsync( : ValidationResult.Valid(); } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateConfigurationAsync( Dictionary>.KeyCollection providers, ICommandLineOptions commandLineOptions, @@ -253,8 +322,24 @@ private static async Task ValidateConfigurationAsync( return stringBuilder; } + [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static string ToTrimmedString(this StringBuilder stringBuilder) -#pragma warning disable RS0030 // Do not use banned APIs - => stringBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()); -#pragma warning restore RS0030 // Do not use banned APIs + { + // Use a more efficient approach to trim without creating unnecessary intermediate strings + string result = stringBuilder.ToString(); + int end = result.Length; + + // Find the last non-whitespace char + while (end > 0) + { + char c = result[end - 1]; + if (c != '\r' && c != '\n') + { + break; + } + end--; + } + + return end == result.Length ? result : result.Substring(0, end); + } } From 85ffb88033e73b98eef3035c1958717de13c5b56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 14:04:21 +0000 Subject: [PATCH 03/21] Remove PerformanceSensitiveAttribute usage Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 7a0e7febae..d4e8ee3b06 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -13,14 +13,11 @@ using Microsoft.Testing.Platform.Extensions.CommandLine; using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Resources; -using Roslyn.Utilities; namespace Microsoft.Testing.Platform.CommandLine; -[PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] internal static class CommandLineOptionsValidator { - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] public static async Task ValidateAsync( CommandLineParseResult commandLineParseResult, IEnumerable systemCommandLineOptionsProviders, @@ -79,7 +76,6 @@ public static async Task ValidateAsync( return await ValidateConfigurationAsync(extensionOptionsByProvider.Keys, systemOptionsByProvider.Keys, commandLineOptions); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateExtensionOptionsDoNotContainReservedPrefix( Dictionary> extensionOptionsByProvider) { @@ -109,7 +105,6 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedPref : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOptions( Dictionary> extensionOptionsByProvider, Dictionary> systemOptionsByProvider) @@ -145,7 +140,6 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateOptionsAreNotDuplicated( Dictionary> extensionOptionsByProvider) { @@ -185,7 +179,6 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateNoUnknownOptions( CommandLineParseResult parseResult, Dictionary> extensionOptionsByProvider, @@ -224,7 +217,6 @@ private static ValidationResult ValidateNoUnknownOptions( : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static ValidationResult ValidateOptionsArgumentArity( CommandLineParseResult parseResult, Dictionary providerAndOptionByOptionName) @@ -264,7 +256,6 @@ private static ValidationResult ValidateOptionsArgumentArity( : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateOptionsArgumentsAsync( CommandLineParseResult parseResult, Dictionary providerAndOptionByOptionName) @@ -288,7 +279,6 @@ private static async Task ValidateOptionsArgumentsAsync( : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateConfigurationAsync( Dictionary>.KeyCollection extensionsProviders, Dictionary>.KeyCollection systemProviders, @@ -302,7 +292,6 @@ private static async Task ValidateConfigurationAsync( : ValidationResult.Valid(); } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static async Task ValidateConfigurationAsync( Dictionary>.KeyCollection providers, ICommandLineOptions commandLineOptions, @@ -322,7 +311,6 @@ private static async Task ValidateConfigurationAsync( return stringBuilder; } - [PerformanceSensitive("https://github.com/microsoft/testfx/issues/5651", AllowGenericEnumeration = false)] private static string ToTrimmedString(this StringBuilder stringBuilder) { // Use a more efficient approach to trim without creating unnecessary intermediate strings From 39d6db5125c93f7f20134af8438a89bf5f9272e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 15:34:15 +0000 Subject: [PATCH 04/21] Fix code formatting in CommandLineOptionsValidator.cs Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index d4e8ee3b06..41ab6e70bc 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -127,9 +127,9 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti if (systemOptionNames.Contains(option.Name)) { stringBuilder ??= new StringBuilder(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, - PlatformResources.CommandLineOptionIsReserved, - option.Name, + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, + PlatformResources.CommandLineOptionIsReserved, + option.Name, provider.Key.DisplayName)); } } @@ -145,7 +145,6 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( { // Use a dictionary to track option names and their providers Dictionary> optionNameToProviders = new(); - foreach (var kvp in extensionOptionsByProvider) { var provider = kvp.Key; @@ -160,7 +159,7 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( providers.Add(provider); } } - + // Check for duplications StringBuilder? stringBuilder = null; foreach (var kvp in optionNameToProviders) From 78e96f85b9c3a78223c5fe8a452e08d2e32ee017 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 15:43:26 +0000 Subject: [PATCH 05/21] Add blank line after conditional block in CommandLineOptionsValidator.cs Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 41ab6e70bc..0ef9c11973 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -156,6 +156,7 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( providers = new List(); optionNameToProviders[name] = providers; } + providers.Add(provider); } } From 975d2d62ae015a02595020638cf483ca38ab35fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 May 2025 15:46:25 +0000 Subject: [PATCH 06/21] Fix whitespace formatting in CommandLineOptionsValidator.cs Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 0ef9c11973..82ac5a424a 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -193,7 +193,7 @@ private static ValidationResult ValidateNoUnknownOptions( validOptionNames.Add(option.Name); } } - + foreach (var provider in systemOptionsByProvider) { foreach (var option in provider.Value) @@ -316,7 +316,7 @@ private static string ToTrimmedString(this StringBuilder stringBuilder) // Use a more efficient approach to trim without creating unnecessary intermediate strings string result = stringBuilder.ToString(); int end = result.Length; - + // Find the last non-whitespace char while (end > 0) { @@ -327,7 +327,7 @@ private static string ToTrimmedString(this StringBuilder stringBuilder) } end--; } - + return end == result.Length ? result : result.Substring(0, end); } } From 86c987ca77661e56a8ab30c5fdc98d7009c7bf61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:02:58 +0000 Subject: [PATCH 07/21] Replace var with explicit type in foreach loop at line 114 Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 82ac5a424a..aef024960f 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -111,7 +111,7 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti { // Create a HashSet of all system option names for faster lookup HashSet systemOptionNames = new(); - foreach (var provider in systemOptionsByProvider) + foreach (KeyValuePair> provider in systemOptionsByProvider) { foreach (var option in provider.Value) { From 2e2ee0286c15651b12c3fcd9f457a054c019d1f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:08:01 +0000 Subject: [PATCH 08/21] Replace var with explicit types and fix whitespace formatting Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index aef024960f..b9a234e445 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -113,16 +113,16 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti HashSet systemOptionNames = new(); foreach (KeyValuePair> provider in systemOptionsByProvider) { - foreach (var option in provider.Value) + foreach (CommandLineOption option in provider.Value) { systemOptionNames.Add(option.Name); } } StringBuilder? stringBuilder = null; - foreach (var provider in extensionOptionsByProvider) + foreach (KeyValuePair> provider in extensionOptionsByProvider) { - foreach (var option in provider.Value) + foreach (CommandLineOption option in provider.Value) { if (systemOptionNames.Contains(option.Name)) { @@ -325,6 +325,7 @@ private static string ToTrimmedString(this StringBuilder stringBuilder) { break; } + end--; } From 07a470045031c3258309ae68753967ee971ccdbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:55:25 +0000 Subject: [PATCH 09/21] Fix formatting: move string.Format to separate line with proper indentation Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index b9a234e445..9524b67ad6 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -127,10 +127,11 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti if (systemOptionNames.Contains(option.Name)) { stringBuilder ??= new StringBuilder(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, - PlatformResources.CommandLineOptionIsReserved, - option.Name, - provider.Key.DisplayName)); + stringBuilder.AppendLine( + string.Format(CultureInfo.InvariantCulture, + PlatformResources.CommandLineOptionIsReserved, + option.Name, + provider.Key.DisplayName)); } } } From 6c344de56f9fea15b9aad371a43cfa9185ccf3de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Jun 2025 11:00:18 +0000 Subject: [PATCH 10/21] Replace var with explicit types in ValidateOptionsAreNotDuplicated method Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../CommandLine/CommandLineOptionsValidator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 9524b67ad6..d2aa33e174 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -146,13 +146,13 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( { // Use a dictionary to track option names and their providers Dictionary> optionNameToProviders = new(); - foreach (var kvp in extensionOptionsByProvider) + foreach (KeyValuePair> kvp in extensionOptionsByProvider) { - var provider = kvp.Key; - foreach (var option in kvp.Value) + ICommandLineOptionsProvider provider = kvp.Key; + foreach (CommandLineOption option in kvp.Value) { string name = option.Name; - if (!optionNameToProviders.TryGetValue(name, out var providers)) + if (!optionNameToProviders.TryGetValue(name, out List? providers)) { providers = new List(); optionNameToProviders[name] = providers; From 3ba2fb0f52bbd0ce1cb06e59310b6779869624e1 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 1 Jun 2025 13:18:49 +0200 Subject: [PATCH 11/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index d2aa33e174..fea51a3b67 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -127,11 +127,7 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti if (systemOptionNames.Contains(option.Name)) { stringBuilder ??= new StringBuilder(); - stringBuilder.AppendLine( - string.Format(CultureInfo.InvariantCulture, - PlatformResources.CommandLineOptionIsReserved, - option.Name, - provider.Key.DisplayName)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, option.Name, provider.Key.DisplayName)); } } } @@ -164,7 +160,7 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( // Check for duplications StringBuilder? stringBuilder = null; - foreach (var kvp in optionNameToProviders) + foreach (KeyValuePair> kvp in optionNameToProviders) { if (kvp.Value.Count > 1) { @@ -187,17 +183,17 @@ private static ValidationResult ValidateNoUnknownOptions( { // Create a HashSet of all valid option names for faster lookup HashSet validOptionNames = new(); - foreach (var provider in extensionOptionsByProvider) + foreach (KeyValuePair> provider in extensionOptionsByProvider) { - foreach (var option in provider.Value) + foreach (CommandLineOption option in provider.Value) { validOptionNames.Add(option.Name); } } - foreach (var provider in systemOptionsByProvider) + foreach (KeyValuePair> provider in systemOptionsByProvider) { - foreach (var option in provider.Value) + foreach (CommandLineOption option in provider.Value) { validOptionNames.Add(option.Name); } From e1369e785634b6748590845e1f3fd869c6b60a18 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 1 Jun 2025 13:58:15 +0200 Subject: [PATCH 12/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index fea51a3b67..c4084a4bc7 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -318,7 +317,7 @@ private static string ToTrimmedString(this StringBuilder stringBuilder) while (end > 0) { char c = result[end - 1]; - if (c != '\r' && c != '\n') + if (c is not ('\r' or '\n')) { break; } From 780fd3bc12f078557528499fd443d3b05510e2b0 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 1 Jun 2025 15:11:11 +0200 Subject: [PATCH 13/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index c4084a4bc7..378a2ef4fe 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; From fe102f2794226343b4d6f39856bb3e7b7dfc8221 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 1 Jun 2025 15:11:51 +0200 Subject: [PATCH 14/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 378a2ef4fe..c1c1a07aee 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -3,9 +3,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.CommandLine; From cae2a1bb238cf123d656c984211b26aa1beaaa1e Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 2 Jun 2025 14:13:18 +0200 Subject: [PATCH 15/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index c1c1a07aee..47b62c4f77 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.Testing.Platform.Extensions; From 97c82993ebc42ab18a265c4da0ac036861ef52b1 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 2 Jun 2025 18:42:17 +0200 Subject: [PATCH 16/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 47b62c4f77..b1d812a924 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Globalization; - using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.CommandLine; using Microsoft.Testing.Platform.Helpers; From 63ab4f7dc86117e84c30c8e68046e52b43eda5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sun, 22 Feb 2026 10:26:06 +0100 Subject: [PATCH 17/21] Add more tests --- .../CommandLine/CommandLineHandlerTests.cs | 125 +++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs index 50462c0cb1..8facc4dac1 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs @@ -38,8 +38,8 @@ public async Task ParseAndValidateAsync_InvalidCommandLineArguments_ReturnsFalse // Assert Assert.IsFalse(result.IsValid); - StringAssert.Contains(result.ErrorMessage, "Invalid command line arguments:"); - StringAssert.Contains(result.ErrorMessage, "Unexpected argument 'a'"); + Assert.Contains("Invalid command line arguments:", result.ErrorMessage); + Assert.Contains("Unexpected argument 'a'", result.ErrorMessage); } [TestMethod] @@ -75,7 +75,7 @@ public async Task ParseAndValidateAsync_DuplicateOption_ReturnsFalse() // Assert Assert.IsFalse(result.IsValid); - StringAssert.Contains(result.ErrorMessage, "Option '--userOption' is declared by multiple extensions: 'Microsoft Testing Platform command line provider', 'Microsoft Testing Platform command line provider'"); + Assert.Contains("Option '--userOption' is declared by multiple extensions: 'Microsoft Testing Platform command line provider', 'Microsoft Testing Platform command line provider'", result.ErrorMessage); } [TestMethod] @@ -171,6 +171,91 @@ public async Task ParseAndValidateAsync_UnknownOption_ReturnsFalse() Assert.AreEqual("Unknown option '--x'", result.ErrorMessage); } + [TestMethod] + public async Task ParseAndValidateAsync_MultipleUnknownOptions_ReportsAll() + { + // Arrange + string[] args = ["--x", "--y"]; + CommandLineParseResult parseResult = CommandLineParser.Parse(args, new SystemEnvironment()); + + ICommandLineOptionsProvider[] extensionCommandLineProvider = + [ + new ExtensionCommandLineProviderMockUnknownOption() + ]; + + // Act + ValidationResult result = await CommandLineOptionsValidator.ValidateAsync(parseResult, _systemCommandLineOptionsProviders, + extensionCommandLineProvider, new Mock().Object); + + // Assert + Assert.IsFalse(result.IsValid); + Assert.Contains("Unknown option '--x'", result.ErrorMessage); + Assert.Contains("Unknown option '--y'", result.ErrorMessage); + } + + [TestMethod] + public async Task ParseAndValidateAsync_MultipleReservedOptionsFromDifferentProviders_ReturnsFalse() + { + // Arrange + string[] args = []; + CommandLineParseResult parseResult = CommandLineParser.Parse(args, new SystemEnvironment()); + ICommandLineOptionsProvider[] extensionCommandLineProvider = + [ + new ExtensionCommandLineProviderMockReservedOptions(), + new ExtensionCommandLineProviderMockWithNamedOption("help", "Provider2") + ]; + + // Act + ValidationResult result = await CommandLineOptionsValidator.ValidateAsync(parseResult, _systemCommandLineOptionsProviders, + extensionCommandLineProvider, new Mock().Object); + + // Assert + Assert.IsFalse(result.IsValid); + Assert.Contains("Option '--help' is reserved and cannot be used by providers: 'help'", result.ErrorMessage); + } + + [TestMethod] + public async Task ParseAndValidateAsync_DuplicateOptionWithDistinctProviderNames_ReportsAllProviders() + { + // Arrange + string[] args = []; + CommandLineParseResult parseResult = CommandLineParser.Parse(args, new SystemEnvironment()); + ICommandLineOptionsProvider[] extensionCommandLineOptionsProviders = + [ + new ExtensionCommandLineProviderMockWithNamedOption("userOption", "ProviderOne"), + new ExtensionCommandLineProviderMockWithNamedOption("userOption", "ProviderTwo") + ]; + + // Act + ValidationResult result = await CommandLineOptionsValidator.ValidateAsync(parseResult, _systemCommandLineOptionsProviders, + extensionCommandLineOptionsProviders, new Mock().Object); + + // Assert + Assert.IsFalse(result.IsValid); + Assert.Contains("Option '--userOption' is declared by multiple extensions: 'ProviderOne', 'ProviderTwo'", result.ErrorMessage); + } + + [TestMethod] + public async Task ParseAndValidateAsync_ValidOptionsWithManyProviders_ReturnsTrue() + { + // Arrange + string[] args = ["--option1", "--option2", "--option3"]; + CommandLineParseResult parseResult = CommandLineParser.Parse(args, new SystemEnvironment()); + ICommandLineOptionsProvider[] extensionCommandLineOptionsProviders = + [ + new ExtensionCommandLineProviderMockWithNamedOption("option1", "Provider1"), + new ExtensionCommandLineProviderMockWithNamedOption("option2", "Provider2"), + new ExtensionCommandLineProviderMockWithNamedOption("option3", "Provider3") + ]; + + // Act + ValidationResult result = await CommandLineOptionsValidator.ValidateAsync(parseResult, _systemCommandLineOptionsProviders, + extensionCommandLineOptionsProviders, new Mock().Object); + + // Assert + Assert.IsTrue(result.IsValid); + } + [TestMethod] public async Task ParseAndValidateAsync_InvalidValidConfiguration_ReturnsFalse() { @@ -375,4 +460,38 @@ public IReadOnlyCollection GetCommandLineOptions() => public Task ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments) => ValidationResult.ValidTask; } + + private sealed class ExtensionCommandLineProviderMockWithNamedOption : ICommandLineOptionsProvider + { + private readonly string _option; + + public ExtensionCommandLineProviderMockWithNamedOption(string optionName, string displayName) + { + _option = optionName; + DisplayName = displayName; + } + + public string Uid { get; } = nameof(PlatformCommandLineProvider); + + /// + public string Version { get; } = AppVersion.DefaultSemVer; + + /// + public string DisplayName { get; } + + /// + public string Description { get; } = "Built-in command line provider"; + + /// + public Task IsEnabledAsync() => Task.FromResult(true); + + public IReadOnlyCollection GetCommandLineOptions() => + [ + new(_option, "Show command line option.", ArgumentArity.ZeroOrOne, false) + ]; + + public Task ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions) => ValidationResult.ValidTask; + + public Task ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments) => ValidationResult.ValidTask; + } } From 1cc37c321a27391bde921c7e5cde3b2003b79fa9 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 1 Apr 2026 23:07:41 +0200 Subject: [PATCH 18/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index cf284e8b5d..9241873377 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -128,7 +128,7 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti Dictionary> systemOptionsByProvider) { // Create a HashSet of all system option names for faster lookup - HashSet systemOptionNames = new(); + var systemOptionNames = new HashSet(); foreach (KeyValuePair> provider in systemOptionsByProvider) { foreach (CommandLineOption option in provider.Value) @@ -200,7 +200,7 @@ private static ValidationResult ValidateNoUnknownOptions( Dictionary> systemOptionsByProvider) { // Create a HashSet of all valid option names for faster lookup - HashSet validOptionNames = new(); + var validOptionNames = new HashSet(); foreach (KeyValuePair> provider in extensionOptionsByProvider) { foreach (CommandLineOption option in provider.Value) From a470f0cf229e8f32b3b27af2c8295b7fa59a9e3b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 15 Apr 2026 22:17:42 +0200 Subject: [PATCH 19/21] Update CommandLineOptionsValidator.cs --- .../CommandLine/CommandLineOptionsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 9241873377..0f5880be2d 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -159,7 +159,7 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( Dictionary> extensionOptionsByProvider) { // Use a dictionary to track option names and their providers - Dictionary> optionNameToProviders = new(); + var optionNameToProviders = new Dictionary>(); foreach (KeyValuePair> kvp in extensionOptionsByProvider) { ICommandLineOptionsProvider provider = kvp.Key; From 7c7db066a3280d8b2a72064e6652064cdc296822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 20 Apr 2026 18:58:23 +0200 Subject: [PATCH 20/21] Address review comments and fix issues in CommandLineOptionsValidator - Aggregate reserved options by name with distinct provider names (HashSet) to preserve original error message format showing all offending providers - Use HashSet for duplicate detection to avoid false positives when a single provider declares the same option twice - Fix ToTrimmedString to trim StringBuilder.Length before ToString() to avoid double allocation (ToString + Substring) - Fix misleading comments in ToTrimmedString (only trims CR/LF, not all whitespace) - Fix test mock Uid/Description: use unique Uid per mock instance and accurate description --- .../CommandLineOptionsValidator.cs | 51 ++++++++++--------- .../CommandLine/CommandLineHandlerTests.cs | 5 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index ad0646f1b2..77553fde42 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -111,19 +111,32 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti } } - StringBuilder? stringBuilder = null; - foreach (KeyValuePair> provider in extensionOptionsByProvider) + // Aggregate reserved options by name and track all offending providers + var reservedOptionToProviderNames = new Dictionary>(); + foreach (KeyValuePair> kvp in extensionOptionsByProvider) { - foreach (CommandLineOption option in provider.Value) + foreach (CommandLineOption option in kvp.Value) { if (systemOptionNames.Contains(option.Name)) { - stringBuilder ??= new StringBuilder(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, option.Name, provider.Key.DisplayName)); + if (!reservedOptionToProviderNames.TryGetValue(option.Name, out HashSet? providerNames)) + { + providerNames = new HashSet(); + reservedOptionToProviderNames[option.Name] = providerNames; + } + + providerNames.Add(kvp.Key.DisplayName); } } } + StringBuilder? stringBuilder = null; + foreach (KeyValuePair> kvp in reservedOptionToProviderNames) + { + stringBuilder ??= new StringBuilder(); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineOptionIsReserved, kvp.Key, string.Join("', '", kvp.Value))); + } + return stringBuilder?.Length > 0 ? ValidationResult.Invalid(stringBuilder.ToTrimmedString()) : ValidationResult.Valid(); @@ -132,17 +145,17 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti private static ValidationResult ValidateOptionsAreNotDuplicated( Dictionary> extensionOptionsByProvider) { - // Use a dictionary to track option names and their providers - var optionNameToProviders = new Dictionary>(); + // Use a dictionary to track option names and their distinct providers + var optionNameToProviders = new Dictionary>(); foreach (KeyValuePair> kvp in extensionOptionsByProvider) { ICommandLineOptionsProvider provider = kvp.Key; foreach (CommandLineOption option in kvp.Value) { string name = option.Name; - if (!optionNameToProviders.TryGetValue(name, out List? providers)) + if (!optionNameToProviders.TryGetValue(name, out HashSet? providers)) { - providers = new List(); + providers = []; optionNameToProviders[name] = providers; } @@ -152,7 +165,7 @@ private static ValidationResult ValidateOptionsAreNotDuplicated( // Check for duplications StringBuilder? stringBuilder = null; - foreach (KeyValuePair> kvp in optionNameToProviders) + foreach (KeyValuePair> kvp in optionNameToProviders) { if (kvp.Value.Count > 1) { @@ -302,22 +315,12 @@ private static async Task ValidateConfigurationAsync( private static string ToTrimmedString(this StringBuilder stringBuilder) { - // Use a more efficient approach to trim without creating unnecessary intermediate strings - string result = stringBuilder.ToString(); - int end = result.Length; - - // Find the last non-whitespace char - while (end > 0) + // Trim trailing CR/LF characters directly from the StringBuilder to avoid extra allocations + while (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] is '\r' or '\n') { - char c = result[end - 1]; - if (c is not ('\r' or '\n')) - { - break; - } - - end--; + stringBuilder.Length--; } - return end == result.Length ? result : result.Substring(0, end); + return stringBuilder.ToString(); } } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs index ac06776591..e4d597437f 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/CommandLine/CommandLineHandlerTests.cs @@ -529,9 +529,10 @@ public ExtensionCommandLineProviderMockWithNamedOption(string optionName, string { _option = optionName; DisplayName = displayName; + Uid = $"TestMock_{displayName}"; } - public string Uid { get; } = nameof(PlatformCommandLineProvider); + public string Uid { get; } /// public string Version { get; } = AppVersion.DefaultSemVer; @@ -540,7 +541,7 @@ public ExtensionCommandLineProviderMockWithNamedOption(string optionName, string public string DisplayName { get; } /// - public string Description { get; } = "Built-in command line provider"; + public string Description { get; } = "Test extension command line provider"; /// public Task IsEnabledAsync() => Task.FromResult(true); From d6777dd96d9ca724bf3bde2fe16de9e7e8f9aba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 20 Apr 2026 19:34:49 +0200 Subject: [PATCH 21/21] Simplify collection initialization (IDE0028) --- .../CommandLine/CommandLineOptionsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs index 77553fde42..88f1a28e72 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/CommandLineOptionsValidator.cs @@ -121,7 +121,7 @@ private static ValidationResult ValidateExtensionOptionsDoNotContainReservedOpti { if (!reservedOptionToProviderNames.TryGetValue(option.Name, out HashSet? providerNames)) { - providerNames = new HashSet(); + providerNames = []; reservedOptionToProviderNames[option.Name] = providerNames; }