From f2c8736112933ccc05397b7145f2518d159406c9 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:32:44 -0500 Subject: [PATCH 1/2] Upgraded from .net 6.0 to 8.0 and upgraded nuget packages --- ...cMaster.Extensions.CommandLineUtils.csproj | 6 ++++- ...ster.Extensions.Hosting.CommandLine.csproj | 12 ++++++---- ...r.Extensions.CommandLineUtils.Tests.csproj | 24 ++++++++++++------- ...xtensions.Hosting.CommandLine.Tests.csproj | 20 ++++++++++------ 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj b/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj index d3f8037a..bc7c3eea 100644 --- a/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj +++ b/src/CommandLineUtils/McMaster.Extensions.CommandLineUtils.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true true Command-line parsing API. @@ -25,4 +25,8 @@ McMaster.Extensions.CommandLineUtils.ArgumentEscaper + + + + diff --git a/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj b/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj index af94d444..08a5b766 100644 --- a/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj +++ b/src/Hosting.CommandLine/McMaster.Extensions.Hosting.CommandLine.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net8.0 true true Provides command-line parsing API integration with the generic host API (Microsoft.Extensions.Hosting). @@ -9,12 +9,16 @@ - - + + + + + + diff --git a/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj b/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj index 1ab1af5f..2219b9b0 100644 --- a/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj +++ b/test/CommandLineUtils.Tests/McMaster.Extensions.CommandLineUtils.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net6.0 + net8.0;net9.0 annotations @@ -11,14 +11,22 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj b/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj index 5e19f487..ed83c547 100644 --- a/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj +++ b/test/Hosting.CommandLine.Tests/McMaster.Extensions.Hosting.CommandLine.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net6.0 + net8.0;net9.0 @@ -9,12 +9,18 @@ - - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 8de0f31046138c811314e2fa0ed67d1c99723f77 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:21:40 -0500 Subject: [PATCH 2/2] Upgraded code to make it net 8 compatible Adopt nullable reference types and modernize tests Updated method signatures to use nullable reference types (`string?`, `params string?[]`, etc.) for improved nullability handling and alignment with C# 8.0 standards. Refactored floating-point parsing tests to use explicit `double` types and introduced `GetDoublePointSymbolsData` for better test coverage. Improved assertions by replacing outdated patterns (e.g., `Assert.True(false)`) with clearer alternatives like `Assert.Fail`. Enhanced null handling in tests by updating parameters and default values to handle nullable scenarios explicitly. Modernized syntax with updated collection initializers and nullable annotations. Fixed a typo in a method name and ensured consistent use of nullable annotations across the codebase. Added test cases to cover edge cases and improve validation logic. Performed general code cleanup, including removing redundant comments, consolidating test data, and improving formatting for readability and maintainability. --- .../AttributeValidatorTests.cs | 2 +- .../CommandLineApplicationExecutorTests.cs | 2 +- .../CommandLineApplicationTests.cs | 4 +- .../CommandLineProcessorTests.cs | 8 ++-- .../CommandOptionTests.cs | 4 +- .../CustomValidationAttributeTest.cs | 6 +-- .../DefaultHelpTextGeneratorTests.cs | 2 +- .../ExecuteMethodConventionTests.cs | 4 +- .../LegalFilePathAttributeTests.cs | 2 +- .../StringExtensionsTests.cs | 4 +- .../ValidateMethodConventionTests.cs | 2 +- .../CommandLineUtils.Tests/ValidationTests.cs | 2 +- .../ValueParserProviderTests.cs | 45 ++++++++++++------- 13 files changed, 51 insertions(+), 36 deletions(-) diff --git a/test/CommandLineUtils.Tests/AttributeValidatorTests.cs b/test/CommandLineUtils.Tests/AttributeValidatorTests.cs index 3caa4ae4..2cfad77f 100644 --- a/test/CommandLineUtils.Tests/AttributeValidatorTests.cs +++ b/test/CommandLineUtils.Tests/AttributeValidatorTests.cs @@ -94,7 +94,7 @@ private void OnExecute() { } [InlineData("email", 1)] [InlineData("email@email@email", 1)] [InlineData("email@example.com", 0)] - public void ValidatesEmailArgument(string email, int exitCode) + public void ValidatesEmailArgument(string? email, int exitCode) { Assert.Equal(exitCode, CommandLineApplication.Execute(new TestConsole(_output), email)); } diff --git a/test/CommandLineUtils.Tests/CommandLineApplicationExecutorTests.cs b/test/CommandLineUtils.Tests/CommandLineApplicationExecutorTests.cs index d82bdc3a..99b6cbfd 100755 --- a/test/CommandLineUtils.Tests/CommandLineApplicationExecutorTests.cs +++ b/test/CommandLineUtils.Tests/CommandLineApplicationExecutorTests.cs @@ -197,7 +197,7 @@ private class HelpClass { private void OnExecute() { - Assert.True(false, "This should not execute"); + Assert.Fail("This should not execute"); } } diff --git a/test/CommandLineUtils.Tests/CommandLineApplicationTests.cs b/test/CommandLineUtils.Tests/CommandLineApplicationTests.cs index 9b93e45d..c7d3d5c8 100644 --- a/test/CommandLineUtils.Tests/CommandLineApplicationTests.cs +++ b/test/CommandLineUtils.Tests/CommandLineApplicationTests.cs @@ -720,7 +720,7 @@ public void NestedInheritedOptions() [InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "a", "--", "b" }, "val")] [InlineData(new[] { "--", "--help" }, new[] { "--help" }, null)] [InlineData(new[] { "--", "--version" }, new[] { "--version" }, null)] - public void ArgumentSeparator(string[] input, string[] expectedRemaining, string topLevelValue) + public void ArgumentSeparator(string[] input, string[] expectedRemaining, string? topLevelValue) { var app = new CommandLineApplication { @@ -1047,7 +1047,7 @@ public void ThrowsExceptionOnEmptyCommandOrArgument() [InlineData(null)] [InlineData("")] [InlineData("-")] - public void ThrowsExceptionOnInvalidArgument(string inputOption) + public void ThrowsExceptionOnInvalidArgument(string? inputOption) { var app = new CommandLineApplication(); diff --git a/test/CommandLineUtils.Tests/CommandLineProcessorTests.cs b/test/CommandLineUtils.Tests/CommandLineProcessorTests.cs index 2de2fa2e..f86917f4 100644 --- a/test/CommandLineUtils.Tests/CommandLineProcessorTests.cs +++ b/test/CommandLineUtils.Tests/CommandLineProcessorTests.cs @@ -25,7 +25,7 @@ public class CommandLineProcessorTests [InlineData(new[] { "-af", "Path.txt" }, true, false, false, "Path.txt")] [InlineData(new[] { "-f:Path.txt" }, false, false, false, "Path.txt")] [InlineData(new[] { "-a", "-c", "-f", "Path.txt" }, true, false, true, "Path.txt")] - public void CanParseClusteredOptions(string[] input, bool a, bool b, bool c, string f) + public void CanParseClusteredOptions(string[] input, bool a, bool b, bool c, string? f) { var app = new CommandLineApplication { @@ -62,7 +62,7 @@ public void CanParseClusteredOptionMultipleTimes() [InlineData("-vl=diag", "diag")] [InlineData("-vl", null)] [InlineData("-lv", null)] - public void ItClustersSingleOrNoValueOptions(string input, string expectedLogValue) + public void ItClustersSingleOrNoValueOptions(string input, string? expectedLogValue) { var app = new CommandLineApplication { @@ -252,7 +252,7 @@ public void CanUseSingleDashAsArgumentValue() [InlineData("--log: ", " ")] [InlineData("--log:verbose", "verbose")] [InlineData("--log=verbose", "verbose")] - public void CanParseSingleOrNoValueParameter(string input, string expected) + public void CanParseSingleOrNoValueParameter(string input, string? expected) { var app = new CommandLineApplication(); var opt = app.Option("--log", "Log level", CommandOptionType.SingleOrNoValue); @@ -266,7 +266,7 @@ public void CanParseSingleOrNoValueParameter(string input, string expected) [InlineData(new[] { "--param1" }, null, null)] [InlineData(new[] { "--param1", "--param2", "p2" }, null, "p2")] [InlineData(new[] { "--param1:p1", "--param2", "p2" }, "p1", "p2")] - public void CanParseSingleOrNoValueParameters(string[] args, string param1, string param2) + public void CanParseSingleOrNoValueParameters(string[] args, string? param1, string? param2) { var app = new CommandLineApplication(); var opt1 = app.Option("--param1", "param1", CommandOptionType.SingleOrNoValue); diff --git a/test/CommandLineUtils.Tests/CommandOptionTests.cs b/test/CommandLineUtils.Tests/CommandOptionTests.cs index 7e05006c..308d5e63 100644 --- a/test/CommandLineUtils.Tests/CommandOptionTests.cs +++ b/test/CommandLineUtils.Tests/CommandOptionTests.cs @@ -28,7 +28,7 @@ public class CommandOptionTests [InlineData("--name=", null, null, "name", "VALUE")] [InlineData("-a: --name:", "a", null, "name", "VALUE")] [InlineData("-a= --name=", "a", null, "name", "VALUE")] - public void ItParsesSingleValueTemplate(string template, string shortName, string symbolName, string longName, string valueName) + public void ItParsesSingleValueTemplate(string template, string? shortName, string? symbolName, string? longName, string? valueName) { var opt = new CommandOption(template, CommandOptionType.SingleValue); Assert.Equal(shortName, opt.ShortName); @@ -40,7 +40,7 @@ public void ItParsesSingleValueTemplate(string template, string shortName, strin [Theory] [InlineData("--name[:]", null, null, "name", "VALUE")] [InlineData("--name[=]", null, null, "name", "VALUE")] - public void ItParsesSingleOrNoValueTemplate(string template, string shortName, string symbolName, string longName, string valueName) + public void ItParsesSingleOrNoValueTemplate(string template, string? shortName, string? symbolName, string longName, string valueName) { var opt = new CommandOption(template, CommandOptionType.SingleOrNoValue); Assert.Equal(shortName, opt.ShortName); diff --git a/test/CommandLineUtils.Tests/CustomValidationAttributeTest.cs b/test/CommandLineUtils.Tests/CustomValidationAttributeTest.cs index f6284cde..0cd119f2 100644 --- a/test/CommandLineUtils.Tests/CustomValidationAttributeTest.cs +++ b/test/CommandLineUtils.Tests/CustomValidationAttributeTest.cs @@ -12,11 +12,11 @@ public class CustomValidationAttributeTest [InlineData(null)] [InlineData("-c", "red")] [InlineData("-c", "blue")] - public void CustomValidationAttributePasses(params string[] args) + public void CustomValidationAttributePasses(params string?[] args) { var app = new CommandLineApplication(); app.Conventions.UseDefaultConventions(); - var result = app.Parse(args ?? System.Array.Empty()); + var result = app.Parse(args ?? []); Assert.Equal(ValidationResult.Success, result.SelectedCommand.GetValidationResult()); var program = Assert.IsType>(result.SelectedCommand); Assert.Same(app, program); @@ -30,7 +30,7 @@ public void CustomValidationAttributePasses(params string[] args) [InlineData("-c", "")] [InlineData("-c", null)] [InlineData("-c", "green")] - public void CustomValidationAttributeFails(params string[] args) + public void CustomValidationAttributeFails(params string?[] args) { var app = new CommandLineApplication(); app.Conventions.UseAttributes(); diff --git a/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs b/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs index 9fdd88b8..b3a89598 100644 --- a/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs +++ b/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs @@ -288,7 +288,7 @@ public class MyApp [InlineData("--help", "--help", " --help Show help information.", " Subcommand ")] [InlineData("-?", "-?", " -? Show help information.", " Subcommand ")] [InlineData(null, "-?|-h|--help", " -?|-h|--help Show help information.", " Subcommand ")] - public void ShowHelpWithSubcommands(string helpOption, string expectedHintText, string expectedOptionsText, + public void ShowHelpWithSubcommands(string? helpOption, string expectedHintText, string expectedOptionsText, string expectedCommandsText) { var app = new CommandLineApplication { Name = "test" }; diff --git a/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs b/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs index a283c818..a25443cd 100644 --- a/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs +++ b/test/CommandLineUtils.Tests/ExecuteMethodConventionTests.cs @@ -133,11 +133,11 @@ public async Task ItExecutesAsyncMethod() var app = new CommandLineApplication(console); app.Conventions.UseOnExecuteMethodFromModel(); var executeTask = app.ExecuteAsync(Array.Empty()); - await ProgramWithAsyncOnExecute.ExecuteStarted.Task.ConfigureAwait(false); + await ProgramWithAsyncOnExecute.ExecuteStarted.Task.ConfigureAwait(true); Assert.False(app.Model.Token.IsCancellationRequested); Assert.NotEqual(CancellationToken.None, app.Model.Token); console.RaiseCancelKeyPress(); - var result = await executeTask.ConfigureAwait(false); + var result = await executeTask.ConfigureAwait(true); Assert.Equal(4, result); Assert.True(app.Model.Token.IsCancellationRequested); } diff --git a/test/CommandLineUtils.Tests/LegalFilePathAttributeTests.cs b/test/CommandLineUtils.Tests/LegalFilePathAttributeTests.cs index c8f6cc01..3f2bf4ea 100644 --- a/test/CommandLineUtils.Tests/LegalFilePathAttributeTests.cs +++ b/test/CommandLineUtils.Tests/LegalFilePathAttributeTests.cs @@ -46,7 +46,7 @@ public void ValidatesLegalFilePaths(string filePath) [InlineData(null)] [InlineData("")] [InlineData("\0")] - public void FailsInvalidLegalFilePaths(string filePath) + public void FailsInvalidLegalFilePaths(string? filePath) { var console = new TestConsole(_output); Assert.NotEqual(0, CommandLineApplication.Execute(console, filePath)); diff --git a/test/CommandLineUtils.Tests/StringExtensionsTests.cs b/test/CommandLineUtils.Tests/StringExtensionsTests.cs index da7f6c9b..ce107778 100644 --- a/test/CommandLineUtils.Tests/StringExtensionsTests.cs +++ b/test/CommandLineUtils.Tests/StringExtensionsTests.cs @@ -27,7 +27,7 @@ public class StringExtensionsTests [InlineData("field___", "field")] [InlineData("m_field", "m-field")] [InlineData("m_Field", "m-field")] - public void ToKebabCase(string input, string expected) + public void ToKebabCase(string? input, string? expected) { Assert.Equal(expected, input.ToKebabCase()); } @@ -39,7 +39,7 @@ public void ToKebabCase(string input, string expected) [InlineData("word", "WORD")] [InlineData("_field", "FIELD")] [InlineData("MSBuildTask", "MSBUILD_TASK")] - public void ToConstantCase(string input, string expected) + public void ToConstantCase(string? input, string? expected) { Assert.Equal(expected, input.ToConstantCase()); } diff --git a/test/CommandLineUtils.Tests/ValidateMethodConventionTests.cs b/test/CommandLineUtils.Tests/ValidateMethodConventionTests.cs index b3f5acf3..a37097eb 100644 --- a/test/CommandLineUtils.Tests/ValidateMethodConventionTests.cs +++ b/test/CommandLineUtils.Tests/ValidateMethodConventionTests.cs @@ -102,7 +102,7 @@ private ValidationResult OnValidate(ValidationContext context, CommandLineContex [InlineData("-m 111 subcommand", null)] [InlineData("subcommand -s 111 -e 123", null)] [InlineData("-m 111 subcommand -s 100 -e 123", null)] - public void ValidatorShouldGetDeserailizedModelInSubcommands(string args, string error) + public void ValidatorShouldGetDeserializedModelInSubcommands(string args, string? error) { var app = new CommandLineApplication(); app.Conventions.UseDefaultConventions(); diff --git a/test/CommandLineUtils.Tests/ValidationTests.cs b/test/CommandLineUtils.Tests/ValidationTests.cs index 83f233cb..b4cfee74 100644 --- a/test/CommandLineUtils.Tests/ValidationTests.cs +++ b/test/CommandLineUtils.Tests/ValidationTests.cs @@ -304,7 +304,7 @@ public void DoesNotValidate_WhenShowingInfo() app.HelpOption(); var errorMessage = "Version arg is required"; app.Argument("version", "Arg").IsRequired(errorMessage: errorMessage); - app.OnValidationError((_) => Assert.False(true, "Validation callbacks should not be executed")); + app.OnValidationError((_) => Assert.Fail("Validation callbacks should not be executed")); Assert.Equal(0, app.Execute("--help")); Assert.DoesNotContain(errorMessage, sb.ToString()); diff --git a/test/CommandLineUtils.Tests/ValueParserProviderTests.cs b/test/CommandLineUtils.Tests/ValueParserProviderTests.cs index 96c0bb3f..8b323ad9 100644 --- a/test/CommandLineUtils.Tests/ValueParserProviderTests.cs +++ b/test/CommandLineUtils.Tests/ValueParserProviderTests.cs @@ -141,17 +141,32 @@ public void Dispose() // Workaround https://github.com/dotnet/roslyn/issues/33199 https://github.com/xunit/xunit/issues/1897 #nullable disable + public static IEnumerable GetDoublePointSymbolsData() + { + using (new InCulture(CultureInfo.InvariantCulture)) + { + var format = CultureInfo.CurrentCulture.NumberFormat; + return + [ + [format.PositiveInfinitySymbol, double.PositiveInfinity], + [format.NegativeInfinitySymbol, double.NegativeInfinity], + [format.NaNSymbol, double.NaN] + ]; + } + } + + public static IEnumerable GetFloatingPointSymbolsData() { using (new InCulture(CultureInfo.InvariantCulture)) { var format = CultureInfo.CurrentCulture.NumberFormat; - return new[] - { - new object[] { format.PositiveInfinitySymbol, float.PositiveInfinity }, - new object[] { format.NegativeInfinitySymbol, float.NegativeInfinity }, - new object[] { format.NaNSymbol, float.NaN}, - }; + return + [ + [format.PositiveInfinitySymbol, float.PositiveInfinity], + [format.NegativeInfinitySymbol, float.NegativeInfinity], + [format.NaNSymbol, float.NaN] + ]; } } #nullable enable @@ -267,11 +282,11 @@ public void ParsesFloatNullable(string arg, float? result) } [Theory] - [InlineData("0.0", 0.0)] - [InlineData("123456789.987654321", 123456789.987654321)] - [InlineData("-123.456", -123.456)] + [InlineData("0.0", 0.0D)] + [InlineData("123456789.987654321", 123456789.987654321D)] + [InlineData("-123.456", -123.456D)] [InlineData("-1E10", -1E10)] - [MemberData(nameof(GetFloatingPointSymbolsData))] + [MemberData(nameof(GetDoublePointSymbolsData))] [InlineData("", null)] public void ParsesDoubleNullable(string arg, double? result) { @@ -401,8 +416,8 @@ public void ParsesStringList() public void ParsesStringSet() { var parsed = CommandLineParser.ParseArgs("--string-set", "first", "--string-set", "second"); - Assert.Contains("first", parsed.StringSet); - Assert.Contains("second", parsed.StringSet); + Assert.Contains("first", parsed.StringSet!); + Assert.Contains("second", parsed.StringSet!); } [Theory] @@ -420,7 +435,7 @@ public void ParsesEnum(Color color) [InlineData("--value-tuple:", "")] [InlineData("--value-tuple: ", " ")] [InlineData("--value-tuple:path", "path")] - public void ParsesValueTupleOfBoolAndType(string input, string expected) + public void ParsesValueTupleOfBoolAndType(string input, string? expected) { var parsed = CommandLineParser.ParseArgs(input); Assert.True(parsed.ValueTuple.HasValue); @@ -527,7 +542,7 @@ public void ParsesBoolArray(int repeat) var parsed = CommandLineParser.ParseArgs(args.ToArray()); Assert.NotNull(parsed.Flags); Assert.Equal(repeat, parsed.Flags?.Length); - Assert.All(parsed.Flags, value => Assert.True(value)); + Assert.All(parsed.Flags!, Assert.True); } [Theory] @@ -631,7 +646,7 @@ public void ThrowsIfNoValueParserProviderIsAvailable() [InlineData(typeof(Color), null, default(Color))] [InlineData(typeof(Color?), "", null)] [MemberData(nameof(TupleData))] - public void ItParsesParamofT(Type type, string input, object expected) + public void ItParsesParamofT(Type type, string? input, object? expected) { using (new InCulture(CultureInfo.InvariantCulture)) {