From 2113343cb5df08240872b87305e5b3c2d53b1ebd Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Thu, 23 Jul 2026 16:19:05 +0200 Subject: [PATCH 1/3] Honor Configuration environment variable in CLI options Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 72bf4a4e-15d3-4177-9246-c49ad3071b13 --- .../Common/CommonOptions.cs | 31 ++++++-- ...tBuildsAndRunsTestsWithDifferentOptions.cs | 15 ++++ .../ParserTests/CommonOptionsTests.cs | 74 +++++++++++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs index fb71ab59bf6b..3a9b59986c1a 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs @@ -196,13 +196,35 @@ public static Option CreateUseCurrentRuntimeOption(string description) => public const string ConfigurationOptionName = "--configuration"; - public static Option CreateConfigurationOption(string description) => - new Option(ConfigurationOptionName, "-c") + public static Option CreateConfigurationOption(string description) + { + var option = new Option(ConfigurationOptionName, "-c") { Description = description, HelpName = CommandDefinitionStrings.ConfigurationArgumentName, - IsDynamic = true - }.ForwardAsSingle(o => $"--property:Configuration={o}"); + IsDynamic = true, + DefaultValueFactory = _ => Environment.GetEnvironmentVariable("Configuration") + }; + + return option.SetForwardingFunction((configuration, parseResult) => + { + if (configuration is null) + { + return []; + } + + var propertyOption = parseResult.CommandResult.Command.Options.FirstOrDefault(o => o.Name == "--property"); + if (parseResult.GetResult(option) is OptionResult { Implicit: true } && + propertyOption is not null && + parseResult.GetResult(propertyOption) is OptionResult propertyResult && + propertyResult.GetValueOrDefault?>()?.ContainsKey("Configuration") is true) + { + return []; + } + + return [$"--property:Configuration={configuration}"]; + }); + } public static Option CreateVersionSuffixOption() => new Option("--version-suffix") @@ -380,4 +402,3 @@ public static void ValidateSelfContainedOptions(bool hasSelfContainedOption, boo Arity = ArgumentArity.Zero }; } - diff --git a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs index 2e139459aab6..e995ea20d5ac 100644 --- a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs +++ b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs @@ -55,6 +55,21 @@ public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeAtLeastOneTe result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed); } + [TestMethod] + public void RunWithSolutionPath_ShouldUseConfigurationEnvironmentVariable() + { + TestAsset testInstance = TestAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString()).WithSource(); + + CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("Configuration", TestingConstants.Release) + .Execute("--solution", "MultiTestProjectSolutionWithTests.sln"); + + Assert.MatchesRegex(RegexPatternHelper.GenerateProjectRegexPattern("TestProject", TestingConstants.Failed, true, TestingConstants.Release), result.StdOut); + Assert.MatchesRegex(RegexPatternHelper.GenerateProjectRegexPattern("OtherTestProject", TestingConstants.Passed, true, TestingConstants.Release), result.StdOut); + result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed); + } + [TestMethod, CombinatorialData] public void RunWithSolutionFilterPathWithFailingTests_ShouldReturnExitCodeGenericFailure( [CombinatorialValues(TestingConstants.Debug, TestingConstants.Release)] string configuration, diff --git a/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs b/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs index 2419e57e9d0c..7e8daed2ccca 100644 --- a/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs +++ b/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs @@ -3,12 +3,86 @@ using System.CommandLine; using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Tests.ParserTests; [TestClass] public class CommonOptionsTests { + [TestMethod] + public void ConfigurationDefaultsToEnvironmentVariable() + { + string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration"); + + try + { + Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration"); + var command = new RootCommand(); + var option = CommonOptions.CreateConfigurationOption("Configuration"); + command.Options.Add(option); + + var result = command.Parse([]); + + result.GetValue(option).Should().Be("EnvironmentConfiguration"); + result.OptionValuesToBeForwarded(command).Should().ContainSingle() + .Which.Should().Be("--property:Configuration=EnvironmentConfiguration"); + } + finally + { + Environment.SetEnvironmentVariable("Configuration", originalConfiguration); + } + } + + [TestMethod] + public void ExplicitConfigurationOverridesEnvironmentVariable() + { + string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration"); + + try + { + Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration"); + var command = new RootCommand(); + var option = CommonOptions.CreateConfigurationOption("Configuration"); + command.Options.Add(option); + + var result = command.Parse(["--configuration", "ExplicitConfiguration"]); + + result.GetValue(option).Should().Be("ExplicitConfiguration"); + result.OptionValuesToBeForwarded(command).Should().ContainSingle() + .Which.Should().Be("--property:Configuration=ExplicitConfiguration"); + } + finally + { + Environment.SetEnvironmentVariable("Configuration", originalConfiguration); + } + } + + [TestMethod] + public void ExplicitConfigurationPropertyOverridesEnvironmentVariable() + { + string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration"); + + try + { + Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration"); + var command = new RootCommand(); + var propertyOption = CommonOptions.CreatePropertyOption(); + var configurationOption = CommonOptions.CreateConfigurationOption("Configuration"); + command.Options.Add(propertyOption); + command.Options.Add(configurationOption); + + var result = command.Parse(["--property:Configuration=PropertyConfiguration"]); + + result.OptionValuesToBeForwarded(command).Should().ContainSingle() + .Which.Should().Be("--property:Configuration=PropertyConfiguration"); + } + finally + { + Environment.SetEnvironmentVariable("Configuration", originalConfiguration); + } + } + [TestMethod] public void Duplicates() { From d7f1906c17af6656ddb4a93c64a25be47a17d451 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Thu, 23 Jul 2026 20:17:33 +0200 Subject: [PATCH 2/3] Address configuration option review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 72bf4a4e-15d3-4177-9246-c49ad3071b13 --- .../Common/CommonOptions.cs | 29 ++++--------------- .../ParserTests/CommonOptionsTests.cs | 19 ++++++------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs index 3a9b59986c1a..b3c9b952f58f 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Common/CommonOptions.cs @@ -196,35 +196,18 @@ public static Option CreateUseCurrentRuntimeOption(string description) => public const string ConfigurationOptionName = "--configuration"; - public static Option CreateConfigurationOption(string description) - { - var option = new Option(ConfigurationOptionName, "-c") + public static Option CreateConfigurationOption(string description) => + new Option(ConfigurationOptionName, "-c") { Description = description, HelpName = CommandDefinitionStrings.ConfigurationArgumentName, IsDynamic = true, - DefaultValueFactory = _ => Environment.GetEnvironmentVariable("Configuration") - }; - - return option.SetForwardingFunction((configuration, parseResult) => - { - if (configuration is null) + DefaultValueFactory = _ => { - return []; + string? configuration = Environment.GetEnvironmentVariable("Configuration"); + return string.IsNullOrWhiteSpace(configuration) ? null : configuration; } - - var propertyOption = parseResult.CommandResult.Command.Options.FirstOrDefault(o => o.Name == "--property"); - if (parseResult.GetResult(option) is OptionResult { Implicit: true } && - propertyOption is not null && - parseResult.GetResult(propertyOption) is OptionResult propertyResult && - propertyResult.GetValueOrDefault?>()?.ContainsKey("Configuration") is true) - { - return []; - } - - return [$"--property:Configuration={configuration}"]; - }); - } + }.ForwardAsSingle(o => $"--property:Configuration={o}"); public static Option CreateVersionSuffixOption() => new Option("--version-suffix") diff --git a/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs b/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs index 7e8daed2ccca..d098fc233ce3 100644 --- a/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs +++ b/test/dotnet.Tests/ParserTests/CommonOptionsTests.cs @@ -59,23 +59,24 @@ public void ExplicitConfigurationOverridesEnvironmentVariable() } [TestMethod] - public void ExplicitConfigurationPropertyOverridesEnvironmentVariable() + [DataRow("")] + [DataRow(" ")] + [DataRow("\t")] + public void EmptyOrWhitespaceConfigurationEnvironmentVariableIsIgnored(string configuration) { string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration"); try { - Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration"); + Environment.SetEnvironmentVariable("Configuration", configuration); var command = new RootCommand(); - var propertyOption = CommonOptions.CreatePropertyOption(); - var configurationOption = CommonOptions.CreateConfigurationOption("Configuration"); - command.Options.Add(propertyOption); - command.Options.Add(configurationOption); + var option = CommonOptions.CreateConfigurationOption("Configuration"); + command.Options.Add(option); - var result = command.Parse(["--property:Configuration=PropertyConfiguration"]); + var result = command.Parse([]); - result.OptionValuesToBeForwarded(command).Should().ContainSingle() - .Which.Should().Be("--property:Configuration=PropertyConfiguration"); + result.GetValue(option).Should().BeNull(); + result.OptionValuesToBeForwarded(command).Should().BeEmpty(); } finally { From afa85c331677c06e341541791e266aa6af370454 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 24 Jul 2026 11:05:08 +0200 Subject: [PATCH 3/3] Fix CLI default value test regressions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 72bf4a4e-15d3-4177-9246-c49ad3071b13 --- .../Microsoft.DotNet.Cli.Definitions/Help/HelpBuilder.cs | 6 ++++-- test/dotnet.Tests/CliSchemaTests.cs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Help/HelpBuilder.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Help/HelpBuilder.cs index 4b79d871777c..4975f91d6c65 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Help/HelpBuilder.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Help/HelpBuilder.cs @@ -253,8 +253,10 @@ string GetSymbolDefaultValue(Symbol symbol) var isSingleArgument = defaultArguments.Length == 1; var argumentDefaultValues = defaultArguments - .Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context)); - return $"[{string.Join(", ", argumentDefaultValues)}]"; + .Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context)) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .ToArray(); + return argumentDefaultValues.Length == 0 ? "" : $"[{string.Join(", ", argumentDefaultValues)}]"; } } diff --git a/test/dotnet.Tests/CliSchemaTests.cs b/test/dotnet.Tests/CliSchemaTests.cs index 467d3cbc609b..e897f372b3d4 100644 --- a/test/dotnet.Tests/CliSchemaTests.cs +++ b/test/dotnet.Tests/CliSchemaTests.cs @@ -77,7 +77,7 @@ public CliSchemaTests() ], "helpName": "CONFIGURATION", "valueType": "System.String", - "hasDefaultValue": false, + "hasDefaultValue": true, "arity": { "minimum": 1, "maximum": 1 @@ -799,7 +799,7 @@ public CliSchemaTests() ], "helpName": "CONFIGURATION", "valueType": "System.String", - "hasDefaultValue": false, + "hasDefaultValue": true, "arity": { "minimum": 1, "maximum": 1