From 0e37267bdd84212ab1424548cf6a072a55563205 Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Mon, 7 Sep 2026 21:37:10 +0200 Subject: [PATCH 1/2] Move shared TargetFrameworks into Directory.Build.props LibraryTargetFrameworks (net8.0;net9.0;net10.0) is defined once and referenced by the two libraries and the test project, same layout as AutoPoco. Benchmarks stay net10.0 only. Co-Authored-By: Claude Fable 5.1 --- Directory.Build.props | 5 +++++ .../holonsoft.CmdLineParser.Abstractions.csproj | 2 +- .../holonsoft.CmdLineParser.Tests.csproj | 2 +- holonsoft.CmdLineParser/holonsoft.CmdLineParser.csproj | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index cabc943..c19743b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,10 @@ + + + net8.0;net9.0;net10.0 + + latest enable diff --git a/holonsoft.CmdLineParser.Abstractions/holonsoft.CmdLineParser.Abstractions.csproj b/holonsoft.CmdLineParser.Abstractions/holonsoft.CmdLineParser.Abstractions.csproj index 58d02a7..3f8d6a8 100644 --- a/holonsoft.CmdLineParser.Abstractions/holonsoft.CmdLineParser.Abstractions.csproj +++ b/holonsoft.CmdLineParser.Abstractions/holonsoft.CmdLineParser.Abstractions.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + $(LibraryTargetFrameworks) Command Line Parser Abstractions Attributes and enums for holonsoft.CmdLineParser. Reference this package from assemblies that only define argument classes. true diff --git a/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj b/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj index c0ead52..ba86f82 100644 --- a/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj +++ b/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + $(LibraryTargetFrameworks) Exe false true diff --git a/holonsoft.CmdLineParser/holonsoft.CmdLineParser.csproj b/holonsoft.CmdLineParser/holonsoft.CmdLineParser.csproj index 1d2d6da..9fefd53 100644 --- a/holonsoft.CmdLineParser/holonsoft.CmdLineParser.csproj +++ b/holonsoft.CmdLineParser/holonsoft.CmdLineParser.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + $(LibraryTargetFrameworks) Command Line Parser Reflection based fast command line parser (args[] -> POCO). Attribute your fields or properties, get a filled object plus structured errors and help text. true From 9b9c6960dba96b0cdab43d06f9dc7d8c78164b91 Mon Sep 17 00:00:00 2001 From: "christian.vogt@hiquaro.com" Date: Mon, 7 Sep 2026 21:55:15 +0200 Subject: [PATCH 2/2] Switch tests to Shouldly assertions All 21 test classes use Shouldly instead of xunit Assert. Shouldly and Xunit are global usings in the test project. String containment checks pass Case.Sensitive explicitly because Shouldly defaults to case-insensitive, unlike xunit. Test classes and nested argument classes are sealed where nothing derives from them. Test names, order and asserted values are unchanged; 313 tests per target framework, same as before. Co-Authored-By: Claude Fable 5.1 --- Directory.Packages.props | 1 + README.md | 2 +- .../ArgumentAttributeTests.cs | 37 ++-- .../ArgumentLexerTests.cs | 49 +++-- .../ArgumentTypesTests.cs | 65 ++++--- .../CommandLineParserTests.cs | 151 ++++++++------- .../CompletionScriptTests.cs | 53 +++--- .../DefaultArgumentTests.cs | 61 +++--- .../DictionaryTests.cs | 85 ++++----- .../EdgeCaseTests.cs | 179 +++++++++--------- .../EnvironmentVariableTests.cs | 49 +++-- .../ErrorReportingTests.cs | 99 +++++----- .../ExclusiveGroupTests.cs | 33 ++-- holonsoft.CmdLineParser.Tests/FuzzTests.cs | 17 +- .../HelpPolishTests.cs | 61 +++--- holonsoft.CmdLineParser.Tests/HelpTests.cs | 87 +++++---- .../MessageFormatterTests.cs | 31 ++- .../ParserBehaviorTests.cs | 57 +++--- .../PropertyTargetTests.cs | 43 ++--- .../ResponseFileTests.cs | 59 +++--- .../TypeConversionTests.cs | 167 ++++++++-------- .../ValidationTests.cs | 145 +++++++------- .../VerbParserTests.cs | 147 +++++++------- .../holonsoft.CmdLineParser.Tests.csproj | 6 + 24 files changed, 835 insertions(+), 849 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fce6adb..996d3b6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/README.md b/README.md index 90f644d..e20d6ea 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ Breaking changes in 5.0: ## Tests -Unit tests use [xUnit.net](https://github.com/xunit/xunit) v3 on Microsoft.Testing.Platform and run on all three target frameworks. The suite includes a fuzz test that feeds random token sequences to every parser and asserts that user input never causes an exception, only reported errors. +Unit tests use [xUnit.net](https://github.com/xunit/xunit) v3 on Microsoft.Testing.Platform with [Shouldly](https://github.com/shouldly/shouldly) assertions and run on all three target frameworks. The suite includes a fuzz test that feeds random token sequences to every parser and asserts that user input never causes an exception, only reported errors. ```text dotnet test diff --git a/holonsoft.CmdLineParser.Tests/ArgumentAttributeTests.cs b/holonsoft.CmdLineParser.Tests/ArgumentAttributeTests.cs index db0ba3f..428c8d6 100644 --- a/holonsoft.CmdLineParser.Tests/ArgumentAttributeTests.cs +++ b/holonsoft.CmdLineParser.Tests/ArgumentAttributeTests.cs @@ -1,15 +1,14 @@ using System.Globalization; using System.Reflection; using holonsoft.CmdLineParser.Abstractions; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ArgumentAttributeTests { +public sealed class ArgumentAttributeTests { [Fact] public void ArgumentTypeIsPreserved() { - Assert.Equal(ArgumentTypes.Required | ArgumentTypes.Multiple, new ArgumentAttribute(ArgumentTypes.Required | ArgumentTypes.Multiple).ArgumentType); - Assert.Equal(ArgumentTypes.Exclusive, new DefaultArgumentAttribute(ArgumentTypes.Exclusive).ArgumentType); + new ArgumentAttribute(ArgumentTypes.Required | ArgumentTypes.Multiple).ArgumentType.ShouldBe(ArgumentTypes.Required | ArgumentTypes.Multiple); + new DefaultArgumentAttribute(ArgumentTypes.Exclusive).ArgumentType.ShouldBe(ArgumentTypes.Exclusive); } [Theory] @@ -19,7 +18,7 @@ public void ArgumentTypeIsPreserved() { public void HasNoDefaultShortName(string? shortName, bool expected) { var attribute = new ArgumentAttribute(ArgumentTypes.AtMostOnce) { ShortName = shortName }; - Assert.Equal(expected, attribute.HasNoDefaultShortName); + attribute.HasNoDefaultShortName.ShouldBe(expected); } [Theory] @@ -29,14 +28,14 @@ public void HasNoDefaultShortName(string? shortName, bool expected) { public void HasNoDefaultLongName(string? longName, bool expected) { var attribute = new ArgumentAttribute(ArgumentTypes.AtMostOnce) { LongName = longName }; - Assert.Equal(expected, attribute.HasNoDefaultLongName); + attribute.HasNoDefaultLongName.ShouldBe(expected); } [Fact] public void HasDefaultValueOnlyForNonNull() { - Assert.False(new ArgumentAttribute(ArgumentTypes.AtMostOnce).HasDefaultValue); - Assert.True(new ArgumentAttribute(ArgumentTypes.AtMostOnce) { DefaultValue = 0 }.HasDefaultValue); - Assert.True(new ArgumentAttribute(ArgumentTypes.AtMostOnce) { DefaultValue = "" }.HasDefaultValue); + new ArgumentAttribute(ArgumentTypes.AtMostOnce).HasDefaultValue.ShouldBeFalse(); + new ArgumentAttribute(ArgumentTypes.AtMostOnce) { DefaultValue = 0 }.HasDefaultValue.ShouldBeTrue(); + new ArgumentAttribute(ArgumentTypes.AtMostOnce) { DefaultValue = "" }.HasDefaultValue.ShouldBeTrue(); } [Theory] @@ -47,15 +46,15 @@ public void HasDefaultValueOnlyForNonNull() { public void HasHelpTextIgnoresWhitespace(string? helpText, bool expected) { var attribute = new ArgumentAttribute(ArgumentTypes.AtMostOnce) { HelpText = helpText }; - Assert.Equal(expected, attribute.HasHelpText); + attribute.HasHelpText.ShouldBe(expected); } [Fact] public void CultureNameResolvesToCultureInfo() { var attribute = new ArgumentAttribute(ArgumentTypes.AtMostOnce) { Culture = "de-DE" }; - Assert.Equal("de-DE", attribute.Culture); - Assert.Equal(CultureInfo.GetCultureInfo("de-DE"), attribute.CultureInfo); + attribute.Culture.ShouldBe("de-DE"); + attribute.CultureInfo.ShouldBe(CultureInfo.GetCultureInfo("de-DE")); } [Theory] @@ -65,13 +64,13 @@ public void CultureNameResolvesToCultureInfo() { public void EmptyCultureMeansParserDefault(string? culture) { var attribute = new ArgumentAttribute(ArgumentTypes.AtMostOnce) { Culture = culture }; - Assert.Null(attribute.CultureInfo); + attribute.CultureInfo.ShouldBeNull(); } [Fact] public void CultureIsNullUntilSet() { - Assert.Null(new ArgumentAttribute(ArgumentTypes.AtMostOnce).CultureInfo); - Assert.Null(new ArgumentAttribute(ArgumentTypes.AtMostOnce).Culture); + new ArgumentAttribute(ArgumentTypes.AtMostOnce).CultureInfo.ShouldBeNull(); + new ArgumentAttribute(ArgumentTypes.AtMostOnce).Culture.ShouldBeNull(); } [Fact] @@ -79,14 +78,14 @@ public void AttributesTargetFieldsAndPropertiesOnce() { foreach (var type in new[] { typeof(ArgumentAttribute), typeof(DefaultArgumentAttribute) }) { var usage = type.GetCustomAttribute()!; - Assert.True(usage.ValidOn.HasFlag(AttributeTargets.Field), type.Name); - Assert.True(usage.ValidOn.HasFlag(AttributeTargets.Property), type.Name); - Assert.False(usage.AllowMultiple, type.Name); + usage.ValidOn.HasFlag(AttributeTargets.Field).ShouldBeTrue(type.Name); + usage.ValidOn.HasFlag(AttributeTargets.Property).ShouldBeTrue(type.Name); + usage.AllowMultiple.ShouldBeFalse(type.Name); } } [Fact] public void DefaultArgumentIsAnArgument() { - Assert.IsAssignableFrom(new DefaultArgumentAttribute(ArgumentTypes.AtMostOnce)); + new DefaultArgumentAttribute(ArgumentTypes.AtMostOnce).ShouldBeAssignableTo(); } } diff --git a/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs b/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs index 9bbd451..8891261 100644 --- a/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs +++ b/holonsoft.CmdLineParser.Tests/ArgumentLexerTests.cs @@ -1,9 +1,8 @@ using holonsoft.CmdLineParser.Internal; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ArgumentLexerTests { +public sealed class ArgumentLexerTests { private static ArgumentLexer Create(Action? configure = null) { var options = new CommandLineParserOptions(); configure?.Invoke(options); @@ -18,9 +17,9 @@ private static ArgumentLexer Create(Action? configure public void OptionPrefixes(string raw, string expectedName) { var token = Create().Classify(raw, afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Option, token.Kind); - Assert.Equal(expectedName, token.Name); - Assert.Null(token.Value); + token.Kind.ShouldBe(LexedTokenKind.Option); + token.Name.ShouldBe(expectedName); + token.Value.ShouldBeNull(); } [Theory] @@ -33,9 +32,9 @@ public void OptionPrefixes(string raw, string expectedName) { public void InlineValues(string raw, string expectedName, string expectedValue) { var token = Create().Classify(raw, afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Option, token.Kind); - Assert.Equal(expectedName, token.Name); - Assert.Equal(expectedValue, token.Value); + token.Kind.ShouldBe(LexedTokenKind.Option); + token.Name.ShouldBe(expectedName); + token.Value.ShouldBe(expectedValue); } [Theory] @@ -55,32 +54,32 @@ public void InlineValues(string raw, string expectedName, string expectedValue) public void Values(string raw, string expectedValue) { var token = Create().Classify(raw, afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Value, token.Kind); - Assert.Equal(expectedValue, token.Value); + token.Kind.ShouldBe(LexedTokenKind.Value); + token.Value.ShouldBe(expectedValue); } [Fact] public void EndOfOptionsMarker() { var lexer = Create(); - Assert.Equal(LexedTokenKind.EndOfOptions, lexer.Classify("--", afterEndOfOptions: false).Kind); + lexer.Classify("--", afterEndOfOptions: false).Kind.ShouldBe(LexedTokenKind.EndOfOptions); var afterwards = lexer.Classify("-looks-like-option", afterEndOfOptions: true); - Assert.Equal(LexedTokenKind.Value, afterwards.Kind); - Assert.Equal("-looks-like-option", afterwards.Value); + afterwards.Kind.ShouldBe(LexedTokenKind.Value); + afterwards.Value.ShouldBe("-looks-like-option"); } [Fact] public void EndOfOptionsMarkerCanBeDisabled() { var token = Create(o => o.RecognizeEndOfOptionsMarker = false).Classify("--", afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Value, token.Kind); - Assert.Equal("--", token.Value); + token.Kind.ShouldBe(LexedTokenKind.Value); + token.Value.ShouldBe("--"); } [Fact] public void SlashPrefixDefaultFollowsTheOperatingSystem() { - Assert.Equal(OperatingSystem.IsWindows(), new CommandLineParserOptions().AllowSlashPrefix); + new CommandLineParserOptions().AllowSlashPrefix.ShouldBe(OperatingSystem.IsWindows()); } [Theory] @@ -91,9 +90,9 @@ public void SlashPrefixDefaultFollowsTheOperatingSystem() { public void SlashPrefixWhenEnabled(string raw, string expectedName, string? expectedValue) { var token = Create(o => o.AllowSlashPrefix = true).Classify(raw, afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Option, token.Kind); - Assert.Equal(expectedName, token.Name); - Assert.Equal(expectedValue, token.Value); + token.Kind.ShouldBe(LexedTokenKind.Option); + token.Name.ShouldBe(expectedName); + token.Value.ShouldBe(expectedValue); } [Theory] @@ -103,18 +102,18 @@ public void SlashPrefixWhenEnabled(string raw, string expectedName, string? expe public void SlashPrefixWhenDisabled(string raw) { var token = Create(o => o.AllowSlashPrefix = false).Classify(raw, afterEndOfOptions: false); - Assert.Equal(LexedTokenKind.Value, token.Kind); - Assert.Equal(raw, token.Value); + token.Kind.ShouldBe(LexedTokenKind.Value); + token.Value.ShouldBe(raw); } [Fact] public void ValueSeparatorsAreConfigurable() { var colonOnly = Create(o => o.ValueSeparators = [':']).Classify("-name=value", afterEndOfOptions: false); - Assert.Equal("name=value", colonOnly.Name); - Assert.Null(colonOnly.Value); + colonOnly.Name.ShouldBe("name=value"); + colonOnly.Value.ShouldBeNull(); var none = Create(o => o.ValueSeparators = []).Classify("-name:value", afterEndOfOptions: false); - Assert.Equal("name:value", none.Name); + none.Name.ShouldBe("name:value"); } [Theory] @@ -128,6 +127,6 @@ public void ValueSeparatorsAreConfigurable() { [InlineData("--5", false)] [InlineData("5", false)] public void NegativeNumberDetection(string raw, bool expected) { - Assert.Equal(expected, ArgumentLexer.LooksLikeNegativeNumber(raw)); + ArgumentLexer.LooksLikeNegativeNumber(raw).ShouldBe(expected); } } diff --git a/holonsoft.CmdLineParser.Tests/ArgumentTypesTests.cs b/holonsoft.CmdLineParser.Tests/ArgumentTypesTests.cs index e46b8c7..650b1e5 100644 --- a/holonsoft.CmdLineParser.Tests/ArgumentTypesTests.cs +++ b/holonsoft.CmdLineParser.Tests/ArgumentTypesTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ArgumentTypesTests { - public class Args { +public sealed class ArgumentTypesTests { + public sealed class Args { [Argument(ArgumentTypes.Required, ShortName = "r")] public int Required; @@ -37,102 +36,102 @@ public class Args { public void RequiredMissingIsReportedExactlyOnce() { var result = Parse("-o", "1"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingArgument, error.Kind); - Assert.Equal("Required", error.ArgumentName); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingArgument); + error.ArgumentName.ShouldBe("Required"); } [Fact] public void RequiredMissingIsReportedForEmptyArguments() { var result = Parse(); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingArgument, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingArgument); } [Fact] public void AtMostOnceScalarGivenTwiceIsDuplicate() { var result = Parse("-r", "1", "-o", "1", "-o", "2"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.DuplicateArgument, error.Kind); - Assert.Equal("o", error.ArgumentName); - Assert.Equal(1, result.Value.Once); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.DuplicateArgument); + error.ArgumentName.ShouldBe("o"); + result.Value.Once.ShouldBe(1); } [Fact] public void LastOccurrenceWinsTakesLastValue() { var result = Parse("-r", "1", "-l", "1", "-l", "2", "--Last", "3"); - Assert.Empty(result.Errors); - Assert.Equal(3, result.Value.Last); + result.Errors.ShouldBeEmpty(); + result.Value.Last.ShouldBe(3); } [Fact] public void UniqueOnScalarBehavesLikeAtMostOnce() { var ok = Parse("-r", "1", "-u", "5"); - Assert.Empty(ok.Errors); - Assert.Equal(5, ok.Value.UniqueScalar); + ok.Errors.ShouldBeEmpty(); + ok.Value.UniqueScalar.ShouldBe(5); var duplicate = Parse("-r", "1", "-u", "5", "-u", "6"); - Assert.Contains(duplicate.Errors, e => e.Kind == ParserErrorKinds.DuplicateArgument); + duplicate.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.DuplicateArgument); } [Fact] public void MultipleCollectionGathersAllOccurrencesAndAllowsDuplicates() { var result = Parse("-r", "1", "-m", "a", "b", "-m", "a"); - Assert.Empty(result.Errors); - Assert.Equal(["a", "b", "a"], result.Value.Multiple!); + result.Errors.ShouldBeEmpty(); + result.Value.Multiple!.ShouldBe(["a", "b", "a"]); } [Fact] public void MultipleUniqueCollectionRejectsDuplicateValues() { var result = Parse("-r", "1", "-mu", "a", "-mu", "a"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.CollectionValuesAreNotUnique, error.Kind); - Assert.Null(result.Value.MultipleUnique); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.CollectionValuesAreNotUnique); + result.Value.MultipleUnique.ShouldBeNull(); } [Fact] public void MultipleUniqueCollectionAcceptsDistinctValuesOverSeveralOccurrences() { var result = Parse("-r", "1", "-mu", "a", "b", "-mu", "c"); - Assert.Empty(result.Errors); - Assert.Equal(["a", "b", "c"], result.Value.MultipleUnique!); + result.Errors.ShouldBeEmpty(); + result.Value.MultipleUnique!.ShouldBe(["a", "b", "c"]); } [Fact] public void AtMostOnceCollectionGivenTwiceIsDuplicate() { var result = Parse("-r", "1", "-c", "a", "-c", "b"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.DuplicateArgument, error.Kind); - Assert.Equal(["a", "b"], result.Value.CollectionOnce!); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.DuplicateArgument); + result.Value.CollectionOnce!.ShouldBe(["a", "b"]); } [Fact] public void ExclusiveAloneSkipsRequiredCheck() { var result = Parse("-v"); - Assert.Empty(result.Errors); - Assert.True(result.Value.Version); + result.Errors.ShouldBeEmpty(); + result.Value.Version.ShouldBeTrue(); } [Fact] public void ExclusiveCombinedWithOtherArgumentIsConflict() { var result = Parse("-v", "-o", "1"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ExclusiveArgumentConflict, error.Kind); - Assert.Equal("Version", error.ArgumentName); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ExclusiveArgumentConflict); + error.ArgumentName.ShouldBe("Version"); } [Fact] public void ObsoleteMisspelledAliasStillWorks() { #pragma warning disable CS0618 - Assert.Equal(ArgumentTypes.LastOccurrenceWins, ArgumentTypes.LastOccurenceWins); + ArgumentTypes.LastOccurenceWins.ShouldBe(ArgumentTypes.LastOccurrenceWins); #pragma warning restore CS0618 } } diff --git a/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs b/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs index f675a00..05826de 100644 --- a/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs +++ b/holonsoft.CmdLineParser.Tests/CommandLineParserTests.cs @@ -3,14 +3,13 @@ using holonsoft.CmdLineParser.Abstractions.Enums; using holonsoft.CmdLineParser.Tests.Dtos; using holonsoft.CmdLineParser.Tests.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; /// /// The scenarios of the original test suite, kept as regression guard for the 5.0 rewrite. /// -public class CommandLineParserTests { +public sealed class CommandLineParserTests { /// /// The original suite mixes '-' and '/' prefixes, so the slash prefix is enabled explicitly. It is off by default on /// Unix-like systems. @@ -66,9 +65,9 @@ public void UnknownArgumentsAndHelpDoNotThrow() { var result = parser.ParseArguments(Args1); - Assert.True(result.HelpRequested); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.UnknownArgument, e.Kind)); - Assert.Equal(2, result.Errors.Count); + result.HelpRequested.ShouldBeTrue(); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.UnknownArgument); + result.Errors.Count.ShouldBe(2); } [Fact] @@ -77,9 +76,9 @@ public void GarbageInputProducesOnlyUnknownArgumentErrors() { var result = parser.ParseArguments(Args2); - Assert.True(result.HasErrors); - Assert.True(result.HelpRequested); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.UnknownArgument, e.Kind)); + result.HasErrors.ShouldBeTrue(); + result.HelpRequested.ShouldBeTrue(); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.UnknownArgument); } [Fact] @@ -88,9 +87,9 @@ public void EmptyArgumentsReturnInstanceWithDefaults() { var result = parser.Parse([]); - Assert.IsType(result); - Assert.Equal(17, result.MaxErrorsBeforeStop); - Assert.True(result.Lines); + result.ShouldBeOfType(); + result.MaxErrorsBeforeStop.ShouldBe(17); + result.Lines.ShouldBeTrue(); } [Fact] @@ -100,15 +99,15 @@ public void SimpleValuesAndDefaults() { var result = parser.Parse(Args3, (kind, hint) => reported.Add((kind, hint))); - Assert.Equal(3, result.StartConnections); - Assert.Equal(5, result.MaxConnections); - Assert.Equal(17, result.MaxErrorsBeforeStop); - Assert.Equal(@"{AppPath}\myconfig.xml", result.Configpath); - Assert.True(result.Lines); + result.StartConnections.ShouldBe(3); + result.MaxConnections.ShouldBe(5); + result.MaxErrorsBeforeStop.ShouldBe(17); + result.Configpath.ShouldBe(@"{AppPath}\myconfig.xml"); + result.Lines.ShouldBeTrue(); - var missing = Assert.Single(reported); - Assert.Equal(ParserErrorKinds.MissingArgument, missing.Kind); - Assert.Equal("IncrementOfConnections", missing.Hint); + var missing = reported.ShouldHaveSingleItem(); + missing.Kind.ShouldBe(ParserErrorKinds.MissingArgument); + missing.Hint.ShouldBe("IncrementOfConnections"); } [Fact] @@ -117,10 +116,10 @@ public void MultiValueCollections() { var result = parser.Parse(Args4); - Assert.False(parser.HasErrors); - Assert.Equal(["File1", "File2", "File3"], result.Files!); - Assert.Equal([1, 3, 2], result.Priorities!); - Assert.Equal([true, true, false, false, true], result.ProcessPriorities!); + parser.HasErrors.ShouldBeFalse(); + result.Files!.ShouldBe(["File1", "File2", "File3"]); + result.Priorities!.ShouldBe([1, 3, 2]); + result.ProcessPriorities!.ShouldBe([true, true, false, false, true]); } [Fact] @@ -130,9 +129,9 @@ public void MultipleUniqueCollectionRejectsDuplicates() { var result = parser.Parse(Args7, (kind, _) => kindOfError = kind); - Assert.True(parser.HasErrors); - Assert.Equal(ParserErrorKinds.CollectionValuesAreNotUnique, kindOfError); - Assert.Null(result.Files); + parser.HasErrors.ShouldBeTrue(); + kindOfError.ShouldBe(ParserErrorKinds.CollectionValuesAreNotUnique); + result.Files.ShouldBeNull(); } [Fact] @@ -142,8 +141,8 @@ public void UnknownOptionIsReported() { parser.Parse(Args8, (kind, _) => kindOfError = kind); - Assert.True(parser.HasErrors); - Assert.Equal(ParserErrorKinds.UnknownArgument, kindOfError); + parser.HasErrors.ShouldBeTrue(); + kindOfError.ShouldBe(ParserErrorKinds.UnknownArgument); } [Fact] @@ -152,8 +151,8 @@ public void FlagWithoutValueIsTrue() { var result = parser.Parse(Args9); - Assert.False(parser.HasErrors); - Assert.True(result.FlagWhenFound); + parser.HasErrors.ShouldBeFalse(); + result.FlagWhenFound.ShouldBeTrue(); } [Fact] @@ -162,8 +161,8 @@ public void GuidWithInlineQuotedValue() { var result = parser.Parse(Args5); - Assert.False(parser.HasErrors); - Assert.NotEqual(Guid.Empty, result.ProgramId); + parser.HasErrors.ShouldBeFalse(); + result.ProgramId.ShouldNotBe(Guid.Empty); } [Fact] @@ -172,8 +171,8 @@ public void EnumByName() { var result = parser.Parse(Args6); - Assert.False(parser.HasErrors); - Assert.Equal(RenameMode.ZipFiles, result.Mode); + parser.HasErrors.ShouldBeFalse(); + result.Mode.ShouldBe(RenameMode.ZipFiles); } [Fact] @@ -182,9 +181,9 @@ public void MissingRequiredFieldsAreReported() { parser.Parse(Args6); - Assert.True(parser.HasErrors); - Assert.Contains(parser.Errors, e => e.Kind == ParserErrorKinds.MissingArgument); - Assert.Contains(parser.Errors, e => e.Kind == ParserErrorKinds.UnknownArgument); + parser.HasErrors.ShouldBeTrue(); + parser.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.MissingArgument); + parser.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.UnknownArgument); } [Fact] @@ -193,29 +192,29 @@ public void AllSupportedTypes() { var result = parser.ParseArguments(Args99); - Assert.Empty(result.Errors); + result.Errors.ShouldBeEmpty(); var value = result.Value; - Assert.Equal(1, value.Int16Field); - Assert.Equal(1, value.Int32Field); - Assert.Equal(1, value.Int64Field); - Assert.Equal((ushort) 1, value.UInt16Field); - Assert.Equal((uint) 1, value.UInt32Field); - Assert.Equal((ulong) 1, value.UInt64Field); - Assert.Equal(1.0m, value.DecimalField); - Assert.Equal(2.0f, value.SingleField); - Assert.Equal(3.0d, value.DoubleField); - Assert.Equal(DateTime.UtcNow.Date, value.DateTimeField); - Assert.True(value.BoolField); - Assert.Equal("A long journey", value.StringField); - Assert.Equal('A', value.CharField); - Assert.NotEqual(Guid.Empty, value.GuidField); - Assert.Equal(RenameMode.BakFiles, value.EnumField); - Assert.Equal(IPAddress.Parse("127.0.0.1"), value.IPAddressField); - Assert.Equal(new DateTime(2020, 4, 2), value.DateTimeFieldWithCultureInfo.Date); - Assert.Equal(3.14159d, value.DoubleFieldWithCultureInfo); - Assert.Equal((byte) 255, value.ByteField); - Assert.Equal((sbyte) -128, value.SByteField); + value.Int16Field.ShouldBe((short) 1); + value.Int32Field.ShouldBe(1); + value.Int64Field.ShouldBe(1); + value.UInt16Field.ShouldBe((ushort) 1); + value.UInt32Field.ShouldBe((uint) 1); + value.UInt64Field.ShouldBe((ulong) 1); + value.DecimalField.ShouldBe(1.0m); + value.SingleField.ShouldBe(2.0f); + value.DoubleField.ShouldBe(3.0d); + value.DateTimeField.ShouldBe(DateTime.UtcNow.Date); + value.BoolField.ShouldBeTrue(); + value.StringField.ShouldBe("A long journey"); + value.CharField.ShouldBe('A'); + value.GuidField.ShouldNotBe(Guid.Empty); + value.EnumField.ShouldBe(RenameMode.BakFiles); + value.IPAddressField.ShouldBe(IPAddress.Parse("127.0.0.1")); + value.DateTimeFieldWithCultureInfo.Date.ShouldBe(new DateTime(2020, 4, 2)); + value.DoubleFieldWithCultureInfo.ShouldBe(3.14159d); + value.ByteField.ShouldBe((byte) 255); + value.SByteField.ShouldBe((sbyte) -128); } [Fact] @@ -224,32 +223,32 @@ public void HelpTextsListEveryArgument() { var helpTexts = parser.GetHelpTexts().ToList(); - Assert.Equal(3, helpTexts.Count); + helpTexts.Count.ShouldBe(3); } [Fact] public void UnsupportedMemberTypeThrowsOnFirstUse() { var parser = new CommandLineParser(WithSlashPrefix); - Assert.Throws(() => parser.Parse(["/Anything", "x"])); + Should.Throw(() => parser.Parse(["/Anything", "x"])); } [Fact] public void ShortNameWithEveryPrefix() { var parser = new CommandLineParser(WithSlashPrefix); - Assert.True(parser.Parse(Args10).FlagWhenFound); - Assert.True(parser.Parse(Args11).FlagWhenFound); - Assert.True(parser.Parse(Args12).FlagWhenFound); + parser.Parse(Args10).FlagWhenFound.ShouldBeTrue(); + parser.Parse(Args11).FlagWhenFound.ShouldBeTrue(); + parser.Parse(Args12).FlagWhenFound.ShouldBeTrue(); } [Fact] public void MemberNameLongNameAndShortNameAddressTheSameField() { var parser = new CommandLineParser(WithSlashPrefix); - Assert.Equal("Special_Out1.txt", parser.Parse(Args13).OutFileName); - Assert.Equal("Special_Out1.txt", parser.Parse(Args14).OutFileName); - Assert.Equal("Special_Out1.txt", parser.Parse(Args15).OutFileName); + parser.Parse(Args13).OutFileName.ShouldBe("Special_Out1.txt"); + parser.Parse(Args14).OutFileName.ShouldBe("Special_Out1.txt"); + parser.Parse(Args15).OutFileName.ShouldBe("Special_Out1.txt"); } [Fact] @@ -258,8 +257,8 @@ public void DefaultValueAppliesWhenFieldNotGiven() { var result = parser.Parse(Args16); - Assert.Equal("default.outfile", result.OutFileName); - Assert.True(parser.HasErrors); + result.OutFileName.ShouldBe("default.outfile"); + parser.HasErrors.ShouldBeTrue(); } [Fact] @@ -268,10 +267,10 @@ public void RepeatedCollectionOption() { var result = parser.Parse(Args17); - Assert.False(parser.HasErrors); - Assert.True(result.Git); - Assert.True(result.MsBuild); - Assert.Equal(["P:/Dev/Base", "P:/Dev/Products"], result.RootDirectories!); + parser.HasErrors.ShouldBeFalse(); + result.Git.ShouldBeTrue(); + result.MsBuild.ShouldBeTrue(); + result.RootDirectories!.ShouldBe(["P:/Dev/Base", "P:/Dev/Products"]); } [Fact] @@ -280,9 +279,9 @@ public void CollectionWithSingleOptionAndSeveralValues() { var result = parser.Parse(Args18); - Assert.False(parser.HasErrors); - Assert.True(result.Git); - Assert.True(result.MsBuild); - Assert.Equal(["P:/Dev/Base", "P:/Dev/Products"], result.RootDirectories!); + parser.HasErrors.ShouldBeFalse(); + result.Git.ShouldBeTrue(); + result.MsBuild.ShouldBeTrue(); + result.RootDirectories!.ShouldBe(["P:/Dev/Base", "P:/Dev/Products"]); } } diff --git a/holonsoft.CmdLineParser.Tests/CompletionScriptTests.cs b/holonsoft.CmdLineParser.Tests/CompletionScriptTests.cs index af63420..057007b 100644 --- a/holonsoft.CmdLineParser.Tests/CompletionScriptTests.cs +++ b/holonsoft.CmdLineParser.Tests/CompletionScriptTests.cs @@ -1,10 +1,9 @@ using holonsoft.CmdLineParser.Abstractions; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class CompletionScriptTests { - public class Args { +public sealed class CompletionScriptTests { + public sealed class Args { [Argument(ArgumentTypes.Required, ShortName = "c", HelpText = "Connections [max 10] don't exceed.")] public int Connections; @@ -21,65 +20,65 @@ public class Args { public void BashScriptOffersAllVisibleNames() { var script = new CommandLineParser().GetCompletionScript(CompletionShell.Bash, "my-tool"); - Assert.Contains("_my_tool_completions() {", script); - Assert.Contains("complete -F _my_tool_completions my-tool", script); - Assert.Contains("--Chatty --verbose -v -c --Connections", script); - Assert.DoesNotContain("Secret", script); + script.ShouldContain("_my_tool_completions() {", Case.Sensitive); + script.ShouldContain("complete -F _my_tool_completions my-tool", Case.Sensitive); + script.ShouldContain("--Chatty --verbose -v -c --Connections", Case.Sensitive); + script.ShouldNotContain("Secret", Case.Sensitive); } [Fact] public void ZshScriptCarriesDescriptionsWithoutBrackets() { var script = new CommandLineParser().GetCompletionScript(CompletionShell.Zsh, "tool"); - Assert.StartsWith("#compdef tool", script); - Assert.Contains("_arguments", script); - Assert.Contains("'--Connections[Connections (max 10) don t exceed.]'", script); - Assert.Contains("'-v[]'", script); - Assert.DoesNotContain("Secret", script); + script.ShouldStartWith("#compdef tool", Case.Sensitive); + script.ShouldContain("_arguments", Case.Sensitive); + script.ShouldContain("'--Connections[Connections (max 10) don t exceed.]'", Case.Sensitive); + script.ShouldContain("'-v[]'", Case.Sensitive); + script.ShouldNotContain("Secret", Case.Sensitive); } [Fact] public void PowerShellScriptRegistersANativeCompleter() { var script = new CommandLineParser().GetCompletionScript(CompletionShell.PowerShell, "tool"); - Assert.Contains("Register-ArgumentCompleter -Native -CommandName 'tool'", script); - Assert.Contains("@('--Chatty', '--verbose', '-v', '-c', '--Connections')", script); - Assert.Contains("CompletionResult", script); + script.ShouldContain("Register-ArgumentCompleter -Native -CommandName 'tool'", Case.Sensitive); + script.ShouldContain("@('--Chatty', '--verbose', '-v', '-c', '--Connections')", Case.Sensitive); + script.ShouldContain("CompletionResult", Case.Sensitive); } [Fact] public void BashScriptWithVerbsSwitchesOnTheFirstWord() { var script = Verbs().GetCompletionScript(CompletionShell.Bash, "tool"); - Assert.Contains("if [ \"$COMP_CWORD\" -eq 1 ]; then", script); - Assert.Contains("words=\"build test --help -h -?\"", script); - Assert.Contains("build) words=\"--Projects -r --Release\" ;;", script); - Assert.Contains("test) words=\"--Filter\" ;;", script); + script.ShouldContain("if [ \"$COMP_CWORD\" -eq 1 ]; then", Case.Sensitive); + script.ShouldContain("words=\"build test --help -h -?\"", Case.Sensitive); + script.ShouldContain("build) words=\"--Projects -r --Release\" ;;", Case.Sensitive); + script.ShouldContain("test) words=\"--Filter\" ;;", Case.Sensitive); } [Fact] public void ZshScriptWithVerbsDescribesVerbsFirst() { var script = Verbs().GetCompletionScript(CompletionShell.Zsh, "tool"); - Assert.Contains("_describe 'verb' verbs", script); - Assert.Contains("'build:Builds.'", script); - Assert.Contains("build)", script); - Assert.Contains("'--Release[Release configuration.]'", script); + script.ShouldContain("_describe 'verb' verbs", Case.Sensitive); + script.ShouldContain("'build:Builds.'", Case.Sensitive); + script.ShouldContain("build)", Case.Sensitive); + script.ShouldContain("'--Release[Release configuration.]'", Case.Sensitive); } [Fact] public void PowerShellScriptWithVerbsSwitchesOnTheVerb() { var script = Verbs().GetCompletionScript(CompletionShell.PowerShell, "tool"); - Assert.Contains("'build' { @('--Projects', '-r', '--Release') }", script); - Assert.Contains("default { @('build', 'test', '--help', '-h', '-?') }", script); + script.ShouldContain("'build' { @('--Projects', '-r', '--Release') }", Case.Sensitive); + script.ShouldContain("default { @('build', 'test', '--help', '-h', '-?') }", Case.Sensitive); } [Fact] public void InvalidInputIsRejected() { var parser = new CommandLineParser(); - Assert.Throws(() => parser.GetCompletionScript(CompletionShell.Bash, " ")); - Assert.Throws(() => parser.GetCompletionScript((CompletionShell) 42, "tool")); + Should.Throw(() => parser.GetCompletionScript(CompletionShell.Bash, " ")); + Should.Throw(() => parser.GetCompletionScript((CompletionShell) 42, "tool")); } } diff --git a/holonsoft.CmdLineParser.Tests/DefaultArgumentTests.cs b/holonsoft.CmdLineParser.Tests/DefaultArgumentTests.cs index a24c5b1..cf7d40f 100644 --- a/holonsoft.CmdLineParser.Tests/DefaultArgumentTests.cs +++ b/holonsoft.CmdLineParser.Tests/DefaultArgumentTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class DefaultArgumentTests { - public class CollectionDefault { +public sealed class DefaultArgumentTests { + public sealed class CollectionDefault { [Argument(ArgumentTypes.AtMostOnce, ShortName = "v")] public bool Verbose; @@ -16,7 +15,7 @@ public class CollectionDefault { public string[]? Files; } - public class ScalarDefault { + public sealed class ScalarDefault { [DefaultArgument(ArgumentTypes.Required)] public string? Target; @@ -24,7 +23,7 @@ public class ScalarDefault { public bool Force; } - public class NoDefault { + public sealed class NoDefault { [Argument(ArgumentTypes.AtMostOnce)] public int Number; } @@ -33,79 +32,79 @@ public class NoDefault { public void BareValuesGoToTheDefaultArgument() { var result = new CommandLineParser().ParseArguments(["a.txt", "b.txt"]); - Assert.Empty(result.Errors); - Assert.Equal(["a.txt", "b.txt"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Files!.ShouldBe(["a.txt", "b.txt"]); } [Fact] public void BareValuesBetweenOptionsAreGathered() { var result = new CommandLineParser().ParseArguments(["a.txt", "-o", "out.txt", "b.txt", "-v", "c.txt"]); - Assert.Empty(result.Errors); - Assert.Equal("out.txt", result.Value.Output); - Assert.True(result.Value.Verbose); - Assert.Equal(["a.txt", "b.txt", "c.txt"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Output.ShouldBe("out.txt"); + result.Value.Verbose.ShouldBeTrue(); + result.Value.Files!.ShouldBe(["a.txt", "b.txt", "c.txt"]); } [Fact] public void DefaultArgumentCanAlsoBeGivenByName() { var result = new CommandLineParser().ParseArguments(["-Files", "a.txt", "b.txt"]); - Assert.Empty(result.Errors); - Assert.Equal(["a.txt", "b.txt"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Files!.ShouldBe(["a.txt", "b.txt"]); } [Fact] public void NamedAndBareValuesAreMergedForMultipleCollections() { var result = new CommandLineParser().ParseArguments(["-Files", "a.txt", "-v", "b.txt"]); - Assert.Empty(result.Errors); - Assert.Equal(["a.txt", "b.txt"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Files!.ShouldBe(["a.txt", "b.txt"]); } [Fact] public void ValuesAfterEndOfOptionsMarkerAreNeverOptions() { var result = new CommandLineParser().ParseArguments(["-v", "--", "-o", "--not-an-option"]); - Assert.Empty(result.Errors); - Assert.True(result.Value.Verbose); - Assert.Null(result.Value.Output); - Assert.Equal(["-o", "--not-an-option"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Verbose.ShouldBeTrue(); + result.Value.Output.ShouldBeNull(); + result.Value.Files!.ShouldBe(["-o", "--not-an-option"]); } [Fact] public void ScalarDefaultArgumentTakesOneValue() { var result = new CommandLineParser().ParseArguments(["build", "-Force"]); - Assert.Empty(result.Errors); - Assert.Equal("build", result.Value.Target); - Assert.True(result.Value.Force); + result.Errors.ShouldBeEmpty(); + result.Value.Target.ShouldBe("build"); + result.Value.Force.ShouldBeTrue(); } [Fact] public void ScalarDefaultArgumentReportsExtraValues() { var result = new CommandLineParser().ParseArguments(["build", "extra"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnexpectedValue, error.Kind); - Assert.Equal("extra", error.Value); - Assert.Equal("build", result.Value.Target); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnexpectedValue); + error.Value.ShouldBe("extra"); + result.Value.Target.ShouldBe("build"); } [Fact] public void RequiredDefaultArgumentMissingIsReported() { var result = new CommandLineParser().ParseArguments(["-Force"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingArgument, error.Kind); - Assert.Equal("Target", error.ArgumentName); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingArgument); + error.ArgumentName.ShouldBe("Target"); } [Fact] public void WithoutDefaultArgumentBareValuesAreUnexpected() { var result = new CommandLineParser().ParseArguments(["-Number", "1", "x", "y"]); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.UnexpectedValue, e.Kind)); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.UnexpectedValue); } } diff --git a/holonsoft.CmdLineParser.Tests/DictionaryTests.cs b/holonsoft.CmdLineParser.Tests/DictionaryTests.cs index 60947b2..7df4abd 100644 --- a/holonsoft.CmdLineParser.Tests/DictionaryTests.cs +++ b/holonsoft.CmdLineParser.Tests/DictionaryTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class DictionaryTests { - public class Args { +public sealed class DictionaryTests { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, ShortName = "D", HelpText = "Properties.")] public Dictionary Properties = new(); @@ -19,19 +18,19 @@ public class Args { public Dictionary? Enums; } - public class SharedInstance { + public sealed class SharedInstance { public static readonly Dictionary Shared = new(); [Argument(ArgumentTypes.AtMostOnce)] public Dictionary Props { get; set; } = Shared; } - public class IntKeys { + public sealed class IntKeys { [Argument(ArgumentTypes.AtMostOnce)] public Dictionary? Map; } - public class InterfaceMember { + public sealed class InterfaceMember { [Argument(ArgumentTypes.AtMostOnce)] public IDictionary? Map; } @@ -42,26 +41,26 @@ public class InterfaceMember { public void SeveralPairsAfterOneOption() { var result = Parse("-D", "a=1", "b=2"); - Assert.Empty(result.Errors); - Assert.Equal(2, result.Value.Properties.Count); - Assert.Equal("1", result.Value.Properties["a"]); - Assert.Equal("2", result.Value.Properties["b"]); + result.Errors.ShouldBeEmpty(); + result.Value.Properties.Count.ShouldBe(2); + result.Value.Properties["a"].ShouldBe("1"); + result.Value.Properties["b"].ShouldBe("2"); } [Fact] public void InlineAndRepeatedOccurrencesWithTypedValues() { var result = Parse("-n:x=1", "-n", "y=2", "--Numbers=z=3"); - Assert.Empty(result.Errors); - Assert.Equal(new Dictionary { ["x"] = 1, ["y"] = 2, ["z"] = 3 }, result.Value.Numbers); + result.Errors.ShouldBeEmpty(); + result.Value.Numbers.ShouldBe(new Dictionary { ["x"] = 1, ["y"] = 2, ["z"] = 3 }, ignoreOrder: true); } [Fact] public void NullDictionaryIsCreated() { var result = Parse("-e", "a=One"); - Assert.Empty(result.Errors); - Assert.Equal(TypeConversionTests.ByteEnum.One, result.Value.Enums!["a"]); + result.Errors.ShouldBeEmpty(); + result.Value.Enums!["a"].ShouldBe(TypeConversionTests.ByteEnum.One); } [Fact] @@ -70,17 +69,17 @@ public void InitializedDictionaryIsReused() { var result = new CommandLineParser().ParseArguments(["-Props", "k=v"]); - Assert.Empty(result.Errors); - Assert.Same(SharedInstance.Shared, result.Value.Props); - Assert.Equal("v", SharedInstance.Shared["k"]); + result.Errors.ShouldBeEmpty(); + result.Value.Props.ShouldBeSameAs(SharedInstance.Shared); + SharedInstance.Shared["k"].ShouldBe("v"); } [Fact] public void AtMostOnceDictionaryGivenTwiceIsDuplicate() { var result = Parse("-D", "a=1", "-D", "b=2"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.DuplicateArgument, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.DuplicateArgument); } [Theory] @@ -89,67 +88,67 @@ public void AtMostOnceDictionaryGivenTwiceIsDuplicate() { public void PairWithoutKeyOrSeparatorIsInvalid(string pair) { var result = Parse("-D", pair); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Equal(pair, error.Value); - Assert.Contains("key=value", error.Message); - Assert.Empty(result.Value.Properties); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + error.Value.ShouldBe(pair); + error.Message.ShouldContain("key=value", Case.Sensitive); + result.Value.Properties.ShouldBeEmpty(); } [Fact] public void ValueConversionErrorLeavesDictionaryUntouched() { var result = Parse("-n", "x=abc", "y=2"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Null(result.Value.Numbers); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + result.Value.Numbers.ShouldBeNull(); } [Fact] public void DuplicateKeysLastWinsWithoutUnique() { var result = Parse("-D", "a=1", "a=2"); - Assert.Empty(result.Errors); - Assert.Equal("2", result.Value.Properties["a"]); + result.Errors.ShouldBeEmpty(); + result.Value.Properties["a"].ShouldBe("2"); } [Fact] public void UniqueRejectsDuplicateKeys() { var result = Parse("-u", "a=1", "-u", "a=2"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.CollectionValuesAreNotUnique, error.Kind); - Assert.Null(result.Value.Unique); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.CollectionValuesAreNotUnique); + result.Value.Unique.ShouldBeNull(); } [Fact] public void ValueMayContainEqualsAndKeysAreTrimmed() { var result = Parse("-D", "conn=a=b", " key =v"); - Assert.Empty(result.Errors); - Assert.Equal("a=b", result.Value.Properties["conn"]); - Assert.Equal("v", result.Value.Properties["key"]); + result.Errors.ShouldBeEmpty(); + result.Value.Properties["conn"].ShouldBe("a=b"); + result.Value.Properties["key"].ShouldBe("v"); } [Fact] public void EmptyValueIsAllowed() { var result = Parse("-D", "a="); - Assert.Empty(result.Errors); - Assert.Equal(string.Empty, result.Value.Properties["a"]); + result.Errors.ShouldBeEmpty(); + result.Value.Properties["a"].ShouldBe(string.Empty); } [Fact] public void MissingPairsIsMissingValue() { var result = Parse("-D"); - Assert.Equal(ParserErrorKinds.MissingValue, Assert.Single(result.Errors).Kind); + result.Errors.ShouldHaveSingleItem().Kind.ShouldBe(ParserErrorKinds.MissingValue); } [Fact] public void OnlyStringKeyedConcreteDictionariesAreSupported() { - Assert.Throws(() => new CommandLineParser().Parse([])); - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] @@ -157,8 +156,8 @@ public void HelpTreatsDictionariesAsCollections() { var parser = new CommandLineParser(); var entry = parser.GetHelpEntries().Single(e => e.Name == "Properties"); - Assert.True(entry.IsCollection); - Assert.Equal("string", entry.TypeDisplayName); - Assert.Contains("-D, --Properties ...", parser.GetConsoleFormattedHelpTexts(100)); + entry.IsCollection.ShouldBeTrue(); + entry.TypeDisplayName.ShouldBe("string"); + parser.GetConsoleFormattedHelpTexts(100).ShouldContain("-D, --Properties ...", Case.Sensitive); } } diff --git a/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs b/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs index 766f2a1..d5ab81e 100644 --- a/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs +++ b/holonsoft.CmdLineParser.Tests/EdgeCaseTests.cs @@ -1,12 +1,11 @@ using System.Globalization; using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class EdgeCaseTests { - public class Args { +public sealed class EdgeCaseTests { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, ShortName = "v")] public bool Verbose; @@ -20,12 +19,12 @@ public class Args { public string[]? Rest; } - public class NoDefault { + public sealed class NoDefault { [Argument(ArgumentTypes.AtMostOnce)] public int Number; } - public class ExclusiveArgs { + public sealed class ExclusiveArgs { [Argument(ArgumentTypes.Exclusive)] public bool Version; @@ -36,22 +35,22 @@ public class ExclusiveArgs { public string[]? Files; } - public class RequiredWithDefault { + public sealed class RequiredWithDefault { [Argument(ArgumentTypes.Required, DefaultValue = 5)] public int Number; } - public class LongNameArgs { + public sealed class LongNameArgs { [Argument(ArgumentTypes.Required, LongName = "run-mode")] public string? Mode; } - public class SelfNamed { + public sealed class SelfNamed { [Argument(ArgumentTypes.AtMostOnce, ShortName = "x", LongName = "x")] public int x; } - public class LongNameCollision { + public sealed class LongNameCollision { [Argument(ArgumentTypes.AtMostOnce, LongName = "Second")] public int First; @@ -59,32 +58,32 @@ public class LongNameCollision { public int Second; } - public class EmptyShortName { + public sealed class EmptyShortName { [Argument(ArgumentTypes.AtMostOnce, ShortName = "")] public int Number; } - public class OwnUpperCaseH { + public sealed class OwnUpperCaseH { [Argument(ArgumentTypes.AtMostOnce, ShortName = "H")] public int Height; } - public class UnsupportedArray { + public sealed class UnsupportedArray { [Argument(ArgumentTypes.AtMostOnce)] public object[]? Things; } - public class ReflectionArray { + public sealed class ReflectionArray { [Argument(ArgumentTypes.AtMostOnce)] public TypeConversionTests.Point[]? Points; } - public class NullableWithConverter { + public sealed class NullableWithConverter { [Argument(ArgumentTypes.AtMostOnce)] public int? Number; } - public class StringDefaults { + public sealed class StringDefaults { [Argument(ArgumentTypes.AtMostOnce, DefaultValue = "Two")] public TypeConversionTests.ByteEnum Enum; @@ -92,16 +91,16 @@ public class StringDefaults { public bool Flag; } - public class Empty { + public sealed class Empty { public int NotAnArgument; } - public class LongMemberName { + public sealed class LongMemberName { [Argument(ArgumentTypes.AtMostOnce, HelpText = "Help.")] public string? ThisIsAnExtraordinarilyLongArgumentNameForTestingPurposes; } - public class FormattedDefaults { + public sealed class FormattedDefaults { [Argument(ArgumentTypes.AtMostOnce, DefaultValue = 1.5)] public double Ratio; @@ -118,138 +117,138 @@ public class FormattedDefaults { public void InlineValueOnBool(string argument, bool expected) { var result = Parse(argument); - Assert.Empty(result.Errors); - Assert.Equal(expected, result.Value.Verbose); + result.Errors.ShouldBeEmpty(); + result.Value.Verbose.ShouldBe(expected); } [Fact] public void InlineValueOnHelpIsIgnored() { var result = Parse("--help:anything"); - Assert.True(result.HelpRequested); - Assert.Empty(result.Errors); + result.HelpRequested.ShouldBeTrue(); + result.Errors.ShouldBeEmpty(); } [Fact] public void UnknownArgumentWithInlineValue() { var result = Parse("-unknown:x", "y"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnknownArgument, error.Kind); - Assert.Equal("unknown", error.ArgumentName); - Assert.Null(result.Value.Rest); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnknownArgument); + error.ArgumentName.ShouldBe("unknown"); + result.Value.Rest.ShouldBeNull(); } [Fact] public void EndOfOptionsMarkerStopsCollectingForTheCurrentArgument() { var result = Parse("-Items", "a", "--", "b"); - Assert.Empty(result.Errors); - Assert.Equal(["a"], result.Value.Items!); - Assert.Equal(["b"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Items!.ShouldBe(["a"]); + result.Value.Rest!.ShouldBe(["b"]); } [Fact] public void SecondEndOfOptionsMarkerIsAValue() { var result = Parse("--", "--", "x"); - Assert.Empty(result.Errors); - Assert.Equal(["--", "x"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Rest!.ShouldBe(["--", "x"]); } [Fact] public void LeadingWhitespaceMakesAValue() { var result = new CommandLineParser().ParseArguments([" -Number"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnexpectedValue, error.Kind); - Assert.Equal(" -Number", error.Value); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnexpectedValue); + error.Value.ShouldBe(" -Number"); } [Fact] public void NonAsciiValuesPassThrough() { var result = Parse("-Text", "Grüße 日本語 🚀", "Ærø"); - Assert.Empty(result.Errors); - Assert.Equal("Grüße 日本語 🚀", result.Value.Text); - Assert.Equal(["Ærø"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Text.ShouldBe("Grüße 日本語 🚀"); + result.Value.Rest!.ShouldBe(["Ærø"]); } [Fact] public void ExclusiveConflictsWithDefaultArgumentValues() { var result = new CommandLineParser().ParseArguments(["-Version", "file.txt"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ExclusiveArgumentConflict, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ExclusiveArgumentConflict); } [Fact] public void ExclusiveStillAppliesDefaultsOfOtherArguments() { var result = new CommandLineParser().ParseArguments(["-Version"]); - Assert.Empty(result.Errors); - Assert.True(result.Value.Version); - Assert.Equal(3, result.Value.Count); + result.Errors.ShouldBeEmpty(); + result.Value.Version.ShouldBeTrue(); + result.Value.Count.ShouldBe(3); } [Fact] public void ExclusiveTogetherWithHelpIsNotAConflict() { var result = new CommandLineParser().ParseArguments(["-Version", "--help"]); - Assert.Empty(result.Errors); - Assert.True(result.HelpRequested); + result.Errors.ShouldBeEmpty(); + result.HelpRequested.ShouldBeTrue(); } [Fact] public void RequiredWithDefaultValueIsAProgrammingError() { var parser = new CommandLineParser(); - var exception = Assert.Throws(() => parser.Parse([])); + var exception = Should.Throw(() => parser.Parse([])); - Assert.Contains("RequiredWithDefault.Number", exception.Message); - Assert.Contains("Required", exception.Message); - Assert.Throws(() => parser.GetHelpEntries()); + exception.Message.ShouldContain("RequiredWithDefault.Number", Case.Sensitive); + exception.Message.ShouldContain("Required", Case.Sensitive); + Should.Throw(() => parser.GetHelpEntries()); } [Fact] public void MissingArgumentUsesLongNameAsDisplayName() { var result = new CommandLineParser().ParseArguments([]); - var error = Assert.Single(result.Errors); - Assert.Equal("run-mode", error.ArgumentName); - Assert.Contains("run-mode", error.Message); + var error = result.Errors.ShouldHaveSingleItem(); + error.ArgumentName.ShouldBe("run-mode"); + error.Message.ShouldContain("run-mode", Case.Sensitive); } [Fact] public void AliasesEqualToTheOwnMemberNameAreAllowed() { var result = new CommandLineParser().ParseArguments(["-x", "7"]); - Assert.Empty(result.Errors); - Assert.Equal(7, result.Value.x); + result.Errors.ShouldBeEmpty(); + result.Value.x.ShouldBe(7); } [Fact] public void LongNameCollidingWithAnotherMemberNameIsAProgrammingError() { - var exception = Assert.Throws(() => new CommandLineParser().Parse([])); + var exception = Should.Throw(() => new CommandLineParser().Parse([])); - Assert.Contains("'Second'", exception.Message); + exception.Message.ShouldContain("'Second'", Case.Sensitive); } [Fact] public void EmptyShortNameMeansNoShortName() { var parser = new CommandLineParser(); - var entry = Assert.Single(parser.GetHelpEntries()); - Assert.Null(entry.ShortName); - Assert.Equal(4, parser.Parse(["-Number", "4"]).Number); + var entry = parser.GetHelpEntries().ShouldHaveSingleItem(); + entry.ShortName.ShouldBeNull(); + parser.Parse(["-Number", "4"]).Number.ShouldBe(4); } [Fact] public void IgnoreCaseAppliesToHelpNames() { var parser = new CommandLineParser(new CommandLineParserOptions { IgnoreCase = true }); - Assert.True(parser.ParseArguments(["--HELP"]).HelpRequested); - Assert.False(new CommandLineParser().ParseArguments(["--HELP"]).HelpRequested); + parser.ParseArguments(["--HELP"]).HelpRequested.ShouldBeTrue(); + new CommandLineParser().ParseArguments(["--HELP"]).HelpRequested.ShouldBeFalse(); } [Fact] @@ -258,9 +257,9 @@ public void UserDefinedNameWinsOverHelpNameUnderIgnoreCase() { var result = parser.ParseArguments(["-h", "5"]); - Assert.False(result.HelpRequested); - Assert.Empty(result.Errors); - Assert.Equal(5, result.Value.Height); + result.HelpRequested.ShouldBeFalse(); + result.Errors.ShouldBeEmpty(); + result.Value.Height.ShouldBe(5); } [Fact] @@ -270,10 +269,10 @@ public void NullOptionArraysAreTolerated() { var result = parser.ParseArguments(["-Number:5", "--help"]); - Assert.False(result.HelpRequested); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.UnknownArgument, e.Kind)); - Assert.Equal("Number:5", result.Errors[0].ArgumentName); + result.HelpRequested.ShouldBeFalse(); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.UnknownArgument); + result.Errors[0].ArgumentName.ShouldBe("Number:5"); } [Fact] @@ -281,22 +280,22 @@ public void WhitespaceHelpNamesAreIgnored() { var options = new CommandLineParserOptions { HelpArgumentNames = [" ", "", "help"] }; var parser = new CommandLineParser(options); - Assert.True(parser.ParseArguments(["--help"]).HelpRequested); - Assert.Contains(parser.ParseArguments([" "]).Errors, e => e.Kind == ParserErrorKinds.UnexpectedValue); + parser.ParseArguments(["--help"]).HelpRequested.ShouldBeTrue(); + parser.ParseArguments([" "]).Errors.ShouldContain(e => e.Kind == ParserErrorKinds.UnexpectedValue); } [Fact] public void ArrayOfUnsupportedTypeIsNotSupported() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void ArrayOfReflectionParsedType() { var result = new CommandLineParser().ParseArguments(["-Points", "1,2", "3,4"]); - Assert.Empty(result.Errors); - Assert.Equal([1, 3], result.Value.Points!.Select(p => p.X)); - Assert.Equal([2, 4], result.Value.Points!.Select(p => p.Y)); + result.Errors.ShouldBeEmpty(); + result.Value.Points!.Select(p => p.X).ShouldBe([1, 3]); + result.Value.Points!.Select(p => p.Y).ShouldBe([2, 4]); } [Fact] @@ -304,37 +303,37 @@ public void CustomConverterAppliesToNullableValueTypes() { var options = new CommandLineParserOptions().AddConverter((value, _) => value.Length); var result = new CommandLineParser(options).ParseArguments(["-Number", "abc"]); - Assert.Empty(result.Errors); - Assert.Equal(3, result.Value.Number); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(3); } [Fact] public void StringDefaultsConvertToEnumAndBool() { var result = new CommandLineParser().ParseArguments([]); - Assert.Empty(result.Errors); - Assert.Equal(TypeConversionTests.ByteEnum.Two, result.Value.Enum); - Assert.True(result.Value.Flag); + result.Errors.ShouldBeEmpty(); + result.Value.Enum.ShouldBe(TypeConversionTests.ByteEnum.Two); + result.Value.Flag.ShouldBeTrue(); } [Fact] public void AddConverterValidatesAndIsFluent() { var options = new CommandLineParserOptions(); - Assert.Throws(() => options.AddConverter(null!)); - Assert.Same(options, options.AddConverter((value, _) => value.Length)); + Should.Throw(() => options.AddConverter(null!)); + options.AddConverter((value, _) => value.Length).ShouldBeSameAs(options); } [Fact] public void TypeWithoutArgumentsParsesAndHasNoHelp() { var parser = new CommandLineParser(); - Assert.Empty(parser.GetHelpEntries()); - Assert.Equal(string.Empty, parser.GetConsoleFormattedHelpTexts(80)); - Assert.Empty(parser.ParseArguments([]).Errors); + parser.GetHelpEntries().ShouldBeEmpty(); + parser.GetConsoleFormattedHelpTexts(80).ShouldBe(string.Empty); + parser.ParseArguments([]).Errors.ShouldBeEmpty(); - var error = Assert.Single(parser.ParseArguments(["x"]).Errors); - Assert.Equal(ParserErrorKinds.UnexpectedValue, error.Kind); + var error = parser.ParseArguments(["x"]).Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnexpectedValue); } [Fact] @@ -342,9 +341,9 @@ public void OverlongNameColumnMovesDescriptionToNextLine() { var help = new CommandLineParser().GetConsoleFormattedHelpTexts(40); var lines = help.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - Assert.Equal(2, lines.Length); - Assert.Equal(" --ThisIsAnExtraordinarilyLongArgumentNameForTestingPurposes ", lines[0]); - Assert.Equal(new string(' ', 20) + "Help.", lines[1]); + lines.Length.ShouldBe(2); + lines[0].ShouldBe(" --ThisIsAnExtraordinarilyLongArgumentNameForTestingPurposes "); + lines[1].ShouldBe(new string(' ', 20) + "Help."); } [Fact] @@ -355,8 +354,8 @@ public void DefaultValuesInHelpUseInvariantCulture() { try { var help = new CommandLineParser().GetConsoleFormattedHelpTexts(80); - Assert.Contains("Default: 1.5", help); - Assert.Contains("Default: a b", help); + help.ShouldContain("Default: 1.5", Case.Sensitive); + help.ShouldContain("Default: a b", Case.Sensitive); } finally { CultureInfo.CurrentCulture = previous; } diff --git a/holonsoft.CmdLineParser.Tests/EnvironmentVariableTests.cs b/holonsoft.CmdLineParser.Tests/EnvironmentVariableTests.cs index 37dd682..275b210 100644 --- a/holonsoft.CmdLineParser.Tests/EnvironmentVariableTests.cs +++ b/holonsoft.CmdLineParser.Tests/EnvironmentVariableTests.cs @@ -1,17 +1,16 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class EnvironmentVariableTests { +public sealed class EnvironmentVariableTests { private const string NumberVariable = "CMDLINEPARSER_TEST_NUMBER"; private const string RequiredVariable = "CMDLINEPARSER_TEST_REQUIRED"; private const string ListVariable = "CMDLINEPARSER_TEST_LIST"; private const string DefaultVariable = "CMDLINEPARSER_TEST_DEFAULT"; private const string DictionaryVariable = "CMDLINEPARSER_TEST_DICT"; - public class Args { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, EnvironmentVariable = NumberVariable, HelpText = "A number.")] public int Number; @@ -43,8 +42,8 @@ public void ScalarComesFromEnvironmentWhenAbsent() { var result = new CommandLineParser().ParseArguments(["-Required", "x"]); - Assert.Empty(result.Errors); - Assert.Equal(42, result.Value.Number); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(42); } [Fact] @@ -53,7 +52,7 @@ public void CommandLineWinsOverEnvironment() { var result = new CommandLineParser().ParseArguments(["-Required", "x", "-Number", "1"]); - Assert.Equal(1, result.Value.Number); + result.Value.Number.ShouldBe(1); } [Fact] @@ -62,8 +61,8 @@ public void RequiredArgumentIsSatisfiedByEnvironment() { var result = new CommandLineParser().ParseArguments([]); - Assert.Empty(result.Errors); - Assert.Equal("from env", result.Value.Required); + result.Errors.ShouldBeEmpty(); + result.Value.Required.ShouldBe("from env"); } [Fact] @@ -72,7 +71,7 @@ public void MissingRequiredIsStillReportedWithoutEnvironment() { var result = new CommandLineParser().ParseArguments([]); - Assert.Contains(result.Errors, e => e.Kind == ParserErrorKinds.MissingArgument && e.ArgumentName == "Required"); + result.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.MissingArgument && e.ArgumentName == "Required"); } [Fact] @@ -81,8 +80,8 @@ public void CollectionsAreSplitAtPathSeparator() { var result = new CommandLineParser().ParseArguments(["-Required", "x"]); - Assert.Empty(result.Errors); - Assert.Equal(["a", "b", "c"], result.Value.List!); + result.Errors.ShouldBeEmpty(); + result.Value.List!.ShouldBe(["a", "b", "c"]); } [Fact] @@ -91,9 +90,9 @@ public void DictionariesComeFromEnvironmentToo() { var result = new CommandLineParser().ParseArguments(["-Required", "x"]); - Assert.Empty(result.Errors); - Assert.Equal("1", result.Value.Dictionary["a"]); - Assert.Equal("2", result.Value.Dictionary["b"]); + result.Errors.ShouldBeEmpty(); + result.Value.Dictionary["a"].ShouldBe("1"); + result.Value.Dictionary["b"].ShouldBe("2"); } [Fact] @@ -102,19 +101,19 @@ public void InvalidEnvironmentValueIsReportedAgainstTheArgument() { var result = new CommandLineParser().ParseArguments(["-Required", "x"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Equal("Number", error.ArgumentName); - Assert.Equal("abc", error.Value); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + error.ArgumentName.ShouldBe("Number"); + error.Value.ShouldBe("abc"); } [Fact] public void EnvironmentBeatsDefaultValue() { using (Set(DefaultVariable, "3")) { - Assert.Equal(3, new CommandLineParser().Parse(["-Required", "x"]).WithDefault); + new CommandLineParser().Parse(["-Required", "x"]).WithDefault.ShouldBe(3); } - Assert.Equal(7, new CommandLineParser().Parse(["-Required", "x"]).WithDefault); + new CommandLineParser().Parse(["-Required", "x"]).WithDefault.ShouldBe(7); } [Fact] @@ -123,8 +122,8 @@ public void EmptyEnvironmentValueIsIgnored() { var result = new CommandLineParser().ParseArguments(["-Required", "x"]); - Assert.Empty(result.Errors); - Assert.Equal(0, result.Value.Number); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(0); } [Fact] @@ -132,14 +131,14 @@ public void EnvironmentCanBeDisabled() { using var _ = Set(NumberVariable, "42"); var parser = new CommandLineParser(new CommandLineParserOptions { UseEnvironmentVariables = false }); - Assert.Equal(0, parser.Parse(["-Required", "x"]).Number); + parser.Parse(["-Required", "x"]).Number.ShouldBe(0); } [Fact] public void HelpMentionsTheVariable() { var parser = new CommandLineParser(); - Assert.Equal(NumberVariable, parser.GetHelpEntries().Single(e => e.Name == "Number").EnvironmentVariable); - Assert.Contains("(env: " + NumberVariable + ")", parser.GetConsoleFormattedHelpTexts(120)); + parser.GetHelpEntries().Single(e => e.Name == "Number").EnvironmentVariable.ShouldBe(NumberVariable); + parser.GetConsoleFormattedHelpTexts(120).ShouldContain("(env: " + NumberVariable + ")", Case.Sensitive); } } diff --git a/holonsoft.CmdLineParser.Tests/ErrorReportingTests.cs b/holonsoft.CmdLineParser.Tests/ErrorReportingTests.cs index 7815d09..1224955 100644 --- a/holonsoft.CmdLineParser.Tests/ErrorReportingTests.cs +++ b/holonsoft.CmdLineParser.Tests/ErrorReportingTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ErrorReportingTests { - public class Args { +public sealed class ErrorReportingTests { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, ShortName = "n")] public int Number; @@ -25,76 +24,76 @@ public class Args { public void InvalidValueIsReportedInsteadOfThrowing() { var result = Parse("-n", "abc"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Equal("n", error.ArgumentName); - Assert.Equal("abc", error.Value); - Assert.Contains("abc", error.Message); - Assert.Equal(0, result.Value.Number); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + error.ArgumentName.ShouldBe("n"); + error.Value.ShouldBe("abc"); + error.Message.ShouldContain("abc", Case.Sensitive); + result.Value.Number.ShouldBe(0); } [Fact] public void OverflowIsInvalidValueNotSilentTruncation() { var result = Parse("-b", "300"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Equal(0, result.Value.Small); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + result.Value.Small.ShouldBe((byte) 0); } [Fact] public void InvalidCollectionElementReportsEachBadValue() { var result = Parse("-l", "1", "x", "y"); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.InvalidValue, e.Kind)); - Assert.Null(result.Value.List); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.InvalidValue); + result.Value.List.ShouldBeNull(); } [Fact] public void MissingValueForScalarAtEnd() { var result = Parse("-n"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingValue, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingValue); } [Fact] public void MissingValueForScalarFollowedByOption() { var result = Parse("-n", "-t", "x"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingValue, error.Kind); - Assert.Equal("n", error.ArgumentName); - Assert.Equal("x", result.Value.Text); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingValue); + error.ArgumentName.ShouldBe("n"); + result.Value.Text.ShouldBe("x"); } [Fact] public void MissingValueForCollection() { var result = Parse("-l"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingValue, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingValue); } [Fact] public void ValueWithoutDefaultArgumentIsUnexpected() { var result = Parse("-n", "1", "stray"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnexpectedValue, error.Kind); - Assert.Equal("stray", error.Value); - Assert.Equal(string.Empty, error.ArgumentName); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnexpectedValue); + error.Value.ShouldBe("stray"); + error.ArgumentName.ShouldBe(string.Empty); } [Fact] public void UnknownArgumentSwallowsItsValues() { var result = Parse("-unknown", "a", "b", "-n", "1"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnknownArgument, error.Kind); - Assert.Equal("unknown", error.ArgumentName); - Assert.Equal(1, result.Value.Number); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnknownArgument); + error.ArgumentName.ShouldBe("unknown"); + result.Value.Number.ShouldBe(1); } [Fact] @@ -104,25 +103,25 @@ public void ErrorReporterReceivesArgumentNameOrValueAsHint() { parser.Parse(["-n", "abc", "stray"], (kind, hint) => hints.Add((kind, hint))); - Assert.Contains((ParserErrorKinds.InvalidValue, "n"), hints); - Assert.Contains((ParserErrorKinds.UnexpectedValue, "stray"), hints); + hints.ShouldContain((ParserErrorKinds.InvalidValue, "n")); + hints.ShouldContain((ParserErrorKinds.UnexpectedValue, "stray")); } [Fact] public void ResultFlagsAreConsistent() { var ok = Parse("-n", "1"); - Assert.True(ok.IsSuccess); - Assert.False(ok.HasErrors); - Assert.False(ok.HelpRequested); + ok.IsSuccess.ShouldBeTrue(); + ok.HasErrors.ShouldBeFalse(); + ok.HelpRequested.ShouldBeFalse(); var failed = Parse("-n", "x"); - Assert.False(failed.IsSuccess); - Assert.True(failed.HasErrors); + failed.IsSuccess.ShouldBeFalse(); + failed.HasErrors.ShouldBeTrue(); var help = Parse("--help"); - Assert.False(help.IsSuccess); - Assert.False(help.HasErrors); - Assert.True(help.HelpRequested); + help.IsSuccess.ShouldBeFalse(); + help.HasErrors.ShouldBeFalse(); + help.HelpRequested.ShouldBeTrue(); } [Fact] @@ -130,34 +129,34 @@ public void ParserPropertiesMirrorLastCall() { var parser = new CommandLineParser(); parser.ParseArguments(["-n", "x"]); - Assert.True(parser.HasErrors); - Assert.Single(parser.Errors); + parser.HasErrors.ShouldBeTrue(); + parser.Errors.ShouldHaveSingleItem(); parser.ParseArguments(["-n", "1"]); - Assert.False(parser.HasErrors); - Assert.Empty(parser.Errors); + parser.HasErrors.ShouldBeFalse(); + parser.Errors.ShouldBeEmpty(); } [Fact] public void ErrorToStringIsTheMessage() { var error = Parse("-n", "abc").Errors[0]; - Assert.Equal(error.Message, error.ToString()); + error.ToString().ShouldBe(error.Message); } [Fact] public void NullArgumentsThrow() { var parser = new CommandLineParser(); - Assert.Throws(() => parser.Parse(null!)); - Assert.Throws(() => parser.Parse(["-n", null!])); + Should.Throw(() => parser.Parse(null!)); + Should.Throw(() => parser.Parse(["-n", null!])); } [Fact] public void EmptyStringIsAValue() { var result = Parse("-t", ""); - Assert.Empty(result.Errors); - Assert.Equal(string.Empty, result.Value.Text); + result.Errors.ShouldBeEmpty(); + result.Value.Text.ShouldBe(string.Empty); } } diff --git a/holonsoft.CmdLineParser.Tests/ExclusiveGroupTests.cs b/holonsoft.CmdLineParser.Tests/ExclusiveGroupTests.cs index 9c85b37..ee69713 100644 --- a/holonsoft.CmdLineParser.Tests/ExclusiveGroupTests.cs +++ b/holonsoft.CmdLineParser.Tests/ExclusiveGroupTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ExclusiveGroupTests { - public class Args { +public sealed class ExclusiveGroupTests { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, ExclusiveGroup = "input")] public string? File; @@ -31,43 +30,43 @@ public class Args { public void OneMemberPerGroupIsFine() { var result = Parse("-File", "x", "-Fast", "-Verbose"); - Assert.Empty(result.Errors); + result.Errors.ShouldBeEmpty(); } [Fact] public void TwoMembersOfAGroupConflict() { var result = Parse("-File", "x", "-Stdin"); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ExclusiveArgumentConflict, error.Kind); - Assert.Equal("File", error.ArgumentName); - Assert.Contains("'File'", error.Message); - Assert.Contains("'Stdin'", error.Message); - Assert.Contains("input", error.Message); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ExclusiveArgumentConflict); + error.ArgumentName.ShouldBe("File"); + error.Message.ShouldContain("'File'", Case.Sensitive); + error.Message.ShouldContain("'Stdin'", Case.Sensitive); + error.Message.ShouldContain("input", Case.Sensitive); } [Fact] public void ThreeMembersProduceOneErrorListingAll() { var result = Parse("-Stdin", "-File", "x", "--from-url", "u"); - var error = Assert.Single(result.Errors); - Assert.Contains("'from-url'", error.Message); - Assert.Equal("Stdin", error.ArgumentName); + var error = result.Errors.ShouldHaveSingleItem(); + error.Message.ShouldContain("'from-url'", Case.Sensitive); + error.ArgumentName.ShouldBe("Stdin"); } [Fact] public void EachViolatedGroupIsReported() { var result = Parse("-File", "x", "-Stdin", "-Fast", "-Safe"); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.ExclusiveArgumentConflict, e.Kind)); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.ExclusiveArgumentConflict); } [Fact] public void ValuesAreStillAssignedDespiteTheConflict() { var result = Parse("-File", "x", "-Stdin"); - Assert.Equal("x", result.Value.File); - Assert.True(result.Value.Stdin); + result.Value.File.ShouldBe("x"); + result.Value.Stdin.ShouldBeTrue(); } } diff --git a/holonsoft.CmdLineParser.Tests/FuzzTests.cs b/holonsoft.CmdLineParser.Tests/FuzzTests.cs index c21f264..43c4c37 100644 --- a/holonsoft.CmdLineParser.Tests/FuzzTests.cs +++ b/holonsoft.CmdLineParser.Tests/FuzzTests.cs @@ -1,5 +1,4 @@ using holonsoft.CmdLineParser.Internal; -using Xunit; namespace holonsoft.CmdLineParser.Tests; @@ -7,7 +6,7 @@ namespace holonsoft.CmdLineParser.Tests; /// Throws random token sequences at the parsers. The contract under test: user input never causes an exception, /// only reported errors. Seeds are fixed so a failure can be reproduced. /// -public class FuzzTests { +public sealed class FuzzTests { private const int Iterations = 1500; private static readonly string[] Fragments = [ @@ -55,9 +54,9 @@ public void ParsersNeverThrowOnRandomInput(int seed) { foreach (var parse in parsers) { try { - Assert.NotNull(parse(args)); + parse(args).ShouldNotBeNull(); } catch (Exception ex) { - Assert.Fail($"Seed {seed}, iteration {i}, args [{string.Join(" | ", args)}]: {ex}"); + throw new ShouldAssertException($"Seed {seed}, iteration {i}, args [{string.Join(" | ", args)}]: {ex}", ex); } } } @@ -73,8 +72,8 @@ public void LexerNeverThrowsOnRandomStrings() { var token = lexer.Classify(raw, afterEndOfOptions: random.Next(4) == 0); - Assert.NotNull(token.Name); - Assert.True(token.Kind != LexedTokenKind.Value || token.Value is not null); + token.Name.ShouldNotBeNull(); + (token.Kind != LexedTokenKind.Value || token.Value is not null).ShouldBeTrue(); } } @@ -86,12 +85,12 @@ public void HelpAndCompletionNeverThrowForRandomWidths() { for (var i = 0; i < 200; i++) { var width = random.Next(1, 300); - Assert.NotNull(parser.GetConsoleFormattedHelpTexts(width)); - Assert.NotNull(parser.GetConsoleFormattedHelpTexts("tool", width)); + parser.GetConsoleFormattedHelpTexts(width).ShouldNotBeNull(); + parser.GetConsoleFormattedHelpTexts("tool", width).ShouldNotBeNull(); } foreach (var shell in Enum.GetValues()) { - Assert.NotEmpty(parser.GetCompletionScript(shell, "tool")); + parser.GetCompletionScript(shell, "tool").ShouldNotBeNullOrEmpty(); } } } diff --git a/holonsoft.CmdLineParser.Tests/HelpPolishTests.cs b/holonsoft.CmdLineParser.Tests/HelpPolishTests.cs index dbccf35..a533ca6 100644 --- a/holonsoft.CmdLineParser.Tests/HelpPolishTests.cs +++ b/holonsoft.CmdLineParser.Tests/HelpPolishTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class HelpPolishTests { +public sealed class HelpPolishTests { [CommandLineDescription("Counts things in files.")] - public class Args { + public sealed class Args { [Argument(ArgumentTypes.Required, ShortName = "c", HelpText = "Connections.")] public int Connections; @@ -31,7 +30,7 @@ public class Args { public string[]? Files; } - public class RequiredOnly { + public sealed class RequiredOnly { [Argument(ArgumentTypes.Required)] public int Count; @@ -42,7 +41,7 @@ public class RequiredOnly { public string? Target; } - public class AliasClash { + public sealed class AliasClash { [Argument(ArgumentTypes.AtMostOnce, Aliases = new[] { "x" })] public int First; @@ -54,10 +53,10 @@ public class AliasClash { public void HiddenArgumentsAreParsedButNotListed() { var parser = new CommandLineParser(); - Assert.DoesNotContain(parser.GetHelpEntries(), e => e.Name == "Secret"); - Assert.DoesNotContain("Secret", parser.GetConsoleFormattedHelpTexts(100)); - Assert.DoesNotContain("Secret", parser.GetCompletionScript(CompletionShell.Bash, "tool")); - Assert.True(parser.Parse(["-c", "1", "-Secret"]).Secret); + parser.GetHelpEntries().ShouldNotContain(e => e.Name == "Secret"); + parser.GetConsoleFormattedHelpTexts(100).ShouldNotContain("Secret", Case.Sensitive); + parser.GetCompletionScript(CompletionShell.Bash, "tool").ShouldNotContain("Secret", Case.Sensitive); + parser.Parse(["-c", "1", "-Secret"]).Secret.ShouldBeTrue(); } [Fact] @@ -71,39 +70,39 @@ public void CategoriesRenderAsSortedSectionsAfterUncategorizedEntries() { var source = Array.FindIndex(lines, l => l.Contains("--Source")); var quiet = Array.FindIndex(lines, l => l.Contains("--Quiet")); - Assert.True(connections < input, "uncategorized entries come first"); - Assert.True(input < source && source < output, "Input section holds Source and precedes Output"); - Assert.True(output < quiet, "Output section holds Quiet"); - Assert.Equal(string.Empty, lines[output - 1]); + (connections < input).ShouldBeTrue("uncategorized entries come first"); + (input < source && source < output).ShouldBeTrue("Input section holds Source and precedes Output"); + (output < quiet).ShouldBeTrue("Output section holds Quiet"); + lines[output - 1].ShouldBe(string.Empty); } [Fact] public void AliasesAreListedAndParsed() { var parser = new CommandLineParser(); - Assert.Contains("--Source, --src, -s ", parser.GetConsoleFormattedHelpTexts(100)); - Assert.Equal(["src", "s"], parser.GetHelpEntries().Single(e => e.Name == "Source").Aliases); - Assert.Equal("x", parser.Parse(["-c", "1", "--src", "x"]).Source); - Assert.Equal("y", parser.Parse(["-c", "1", "-s", "y"]).Source); + parser.GetConsoleFormattedHelpTexts(100).ShouldContain("--Source, --src, -s ", Case.Sensitive); + parser.GetHelpEntries().Single(e => e.Name == "Source").Aliases.ShouldBe(["src", "s"]); + parser.Parse(["-c", "1", "--src", "x"]).Source.ShouldBe("x"); + parser.Parse(["-c", "1", "-s", "y"]).Source.ShouldBe("y"); } [Fact] public void AliasClashIsAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void GroupIsMentionedInHelp() { var parser = new CommandLineParser(); - Assert.Contains("(group: mode)", parser.GetConsoleFormattedHelpTexts(100)); - Assert.Equal("mode", parser.GetHelpEntries().Single(e => e.Name == "Fast").ExclusiveGroup); + parser.GetConsoleFormattedHelpTexts(100).ShouldContain("(group: mode)", Case.Sensitive); + parser.GetHelpEntries().Single(e => e.Name == "Fast").ExclusiveGroup.ShouldBe("mode"); } [Fact] public void UsageLineListsRequiredArgumentsAndDefaultArgument() { - Assert.Equal("tool [options] --Connections [...]", new CommandLineParser().GetUsage("tool")); - Assert.Equal("tool --Count --Force ", new CommandLineParser().GetUsage("tool")); + new CommandLineParser().GetUsage("tool").ShouldBe("tool [options] --Connections [...]"); + new CommandLineParser().GetUsage("tool").ShouldBe("tool --Count --Force "); } [Fact] @@ -112,26 +111,26 @@ public void FullHelpStartsWithDescriptionAndUsage() { var help = parser.GetConsoleFormattedHelpTexts("tool", 100); var lines = help.Split(Environment.NewLine); - Assert.Equal("Counts things in files.", parser.Description); - Assert.Equal("Counts things in files.", lines[0]); - Assert.Equal(string.Empty, lines[1]); - Assert.Equal("Usage: tool [options] --Connections [...]", lines[2]); - Assert.Equal(string.Empty, lines[3]); - Assert.Contains(lines, l => l.Contains("--Connections ")); + parser.Description.ShouldBe("Counts things in files."); + lines[0].ShouldBe("Counts things in files."); + lines[1].ShouldBe(string.Empty); + lines[2].ShouldBe("Usage: tool [options] --Connections [...]"); + lines[3].ShouldBe(string.Empty); + lines.ShouldContain(l => l.Contains("--Connections ")); } [Fact] public void FullHelpWithoutDescriptionStartsWithUsage() { var parser = new CommandLineParser(); - Assert.Null(parser.Description); - Assert.StartsWith("Usage: tool", parser.GetConsoleFormattedHelpTexts("tool", 100)); + parser.Description.ShouldBeNull(); + parser.GetConsoleFormattedHelpTexts("tool", 100).ShouldStartWith("Usage: tool", Case.Sensitive); } [Fact] public void FullHelpWrapsALongDescription() { var help = new CommandLineParser().GetConsoleFormattedHelpTexts("tool", 20); - Assert.All(help.Split(Environment.NewLine).TakeWhile(l => l.Length > 0), l => Assert.True(l.Length <= 24, l)); + help.Split(Environment.NewLine).TakeWhile(l => l.Length > 0).ShouldAllBe(l => l.Length <= 24); } } diff --git a/holonsoft.CmdLineParser.Tests/HelpTests.cs b/holonsoft.CmdLineParser.Tests/HelpTests.cs index 358ad8b..2942e7d 100644 --- a/holonsoft.CmdLineParser.Tests/HelpTests.cs +++ b/holonsoft.CmdLineParser.Tests/HelpTests.cs @@ -1,16 +1,15 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class HelpTests { +public sealed class HelpTests { public enum Mode { Fast, Safe, } - public class Args { + public sealed class Args { [Argument(ArgumentTypes.Required, ShortName = "c", HelpText = "Number of connections to open when the program starts, must be positive and should not exceed the pool size.")] public int Connections; @@ -30,7 +29,7 @@ public class Args { public string? NoHelpText; } - public class ArgsWithOwnHelp { + public sealed class ArgsWithOwnHelp { [Argument(ArgumentTypes.AtMostOnce, ShortName = "h")] public int Height; @@ -42,23 +41,23 @@ public class ArgsWithOwnHelp { public void EntriesAreSortedByNameAndCarryMetadata() { var entries = new CommandLineParser().GetHelpEntries(); - Assert.Equal(["Connections", "Files", "Mode", "NoHelpText", "Verbose", "Version"], entries.Select(e => e.Name)); + entries.Select(e => e.Name).ShouldBe(["Connections", "Files", "Mode", "NoHelpText", "Verbose", "Version"]); var connections = entries.Single(e => e.Name == "Connections"); - Assert.True(connections.IsRequired); - Assert.Equal("c", connections.ShortName); - Assert.Equal("int", connections.TypeDisplayName); + connections.IsRequired.ShouldBeTrue(); + connections.ShortName.ShouldBe("c"); + connections.TypeDisplayName.ShouldBe("int"); var mode = entries.Single(e => e.Name == "Mode"); - Assert.Equal("run-mode", mode.LongName); - Assert.Equal("Fast|Safe", mode.TypeDisplayName); - Assert.Equal(Mode.Safe, mode.DefaultValue); + mode.LongName.ShouldBe("run-mode"); + mode.TypeDisplayName.ShouldBe("Fast|Safe"); + mode.DefaultValue.ShouldBe(Mode.Safe); var files = entries.Single(e => e.Name == "Files"); - Assert.True(files.IsDefaultArgument); - Assert.True(files.IsCollection); + files.IsDefaultArgument.ShouldBeTrue(); + files.IsCollection.ShouldBeTrue(); - Assert.True(entries.Single(e => e.Name == "Version").IsExclusive); + entries.Single(e => e.Name == "Version").IsExclusive.ShouldBeTrue(); } [Fact] @@ -66,25 +65,25 @@ public void LegacyHelpTextsUseEmptyStringsForMissingParts() { var texts = new CommandLineParser().GetHelpTexts().ToList(); var noHelp = texts.Single(t => t.FieldName == "NoHelpText"); - Assert.Equal(string.Empty, noHelp.ShortName); - Assert.Equal(string.Empty, noHelp.LongName); - Assert.Equal(string.Empty, noHelp.HelpText); + noHelp.ShortName.ShouldBe(string.Empty); + noHelp.LongName.ShouldBe(string.Empty); + noHelp.HelpText.ShouldBe(string.Empty); } [Fact] public void FormattedHelpContainsNamesTypesAndMarkers() { var help = new CommandLineParser().GetConsoleFormattedHelpTexts(100); - Assert.Contains("-c, --Connections ", help); - Assert.Contains("(required)", help); - Assert.Contains("-m, --run-mode ", help); - Assert.Contains("Default: Safe", help); - Assert.Contains("-v, --Verbose ", help); - Assert.DoesNotContain("--Verbose <", help); - Assert.Contains("(exclusive)", help); - Assert.Contains("--Files ...", help); - Assert.Contains("(default argument", help); - Assert.Contains("--NoHelpText ", help); + help.ShouldContain("-c, --Connections ", Case.Sensitive); + help.ShouldContain("(required)", Case.Sensitive); + help.ShouldContain("-m, --run-mode ", Case.Sensitive); + help.ShouldContain("Default: Safe", Case.Sensitive); + help.ShouldContain("-v, --Verbose ", Case.Sensitive); + help.ShouldNotContain("--Verbose <", Case.Sensitive); + help.ShouldContain("(exclusive)", Case.Sensitive); + help.ShouldContain("--Files ...", Case.Sensitive); + help.ShouldContain("(default argument", Case.Sensitive); + help.ShouldContain("--NoHelpText ", Case.Sensitive); } [Fact] @@ -93,26 +92,26 @@ public void FormattedHelpWrapsLongTextWithinWidth() { var help = new CommandLineParser().GetConsoleFormattedHelpTexts(width); var lines = help.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - Assert.All(lines, line => Assert.True(line.Length <= width, $"Line too long: '{line}'")); - Assert.Contains(lines, line => line.Contains("pool size")); - Assert.True(lines.Length > 6, "long help text should wrap into continuation lines"); + lines.ShouldAllBe(line => line.Length <= width); + lines.ShouldContain(line => line.Contains("pool size")); + (lines.Length > 6).ShouldBeTrue("long help text should wrap into continuation lines"); } [Fact] public void FormattedHelpTreatsTinyWidthsAsTwenty() { var help = new CommandLineParser().GetConsoleFormattedHelpTexts(5); - Assert.NotEmpty(help); - Assert.Throws(() => new CommandLineParser().GetConsoleFormattedHelpTexts(0)); + help.ShouldNotBeEmpty(); + Should.Throw(() => new CommandLineParser().GetConsoleFormattedHelpTexts(0)); } [Fact] public void WrapSplitsAtWordBoundaries() { var lines = CommandLineParser.Wrap("aaa bbb ccc ddd", 7); - Assert.Equal(["aaa bbb", "ccc ddd"], lines); - Assert.Empty(CommandLineParser.Wrap(" ", 7)); - Assert.Equal(["averyveryverylongword", "x"], CommandLineParser.Wrap("averyveryverylongword x", 5)); + lines.ShouldBe(["aaa bbb", "ccc ddd"]); + CommandLineParser.Wrap(" ", 7).ShouldBeEmpty(); + CommandLineParser.Wrap("averyveryverylongword x", 5).ShouldBe(["averyveryverylongword", "x"]); } [Theory] @@ -123,17 +122,17 @@ public void WrapSplitsAtWordBoundaries() { public void BuiltInHelpSuppressesMissingRequiredErrors(string helpArgument) { var result = new CommandLineParser(new CommandLineParserOptions { AllowSlashPrefix = true }).ParseArguments([helpArgument]); - Assert.True(result.HelpRequested); - Assert.Empty(result.Errors); + result.HelpRequested.ShouldBeTrue(); + result.Errors.ShouldBeEmpty(); } [Fact] public void UserDefinedNameWinsOverBuiltInHelp() { var result = new CommandLineParser().ParseArguments(["-h", "5", "-Width", "1"]); - Assert.False(result.HelpRequested); - Assert.Empty(result.Errors); - Assert.Equal(5, result.Value.Height); + result.HelpRequested.ShouldBeFalse(); + result.Errors.ShouldBeEmpty(); + result.Value.Height.ShouldBe(5); } [Fact] @@ -142,15 +141,15 @@ public void AutoHelpCanBeDisabled() { var result = parser.ParseArguments(["--help"]); - Assert.False(result.HelpRequested); - Assert.Contains(result.Errors, e => e.Kind == ParserErrorKinds.UnknownArgument && e.ArgumentName == "help"); + result.HelpRequested.ShouldBeFalse(); + result.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.UnknownArgument && e.ArgumentName == "help"); } [Fact] public void HelpNamesAreConfigurable() { var parser = new CommandLineParser(new CommandLineParserOptions { HelpArgumentNames = ["hilfe"] }); - Assert.True(parser.ParseArguments(["--hilfe"]).HelpRequested); - Assert.False(parser.ParseArguments(["--help"]).HelpRequested); + parser.ParseArguments(["--hilfe"]).HelpRequested.ShouldBeTrue(); + parser.ParseArguments(["--help"]).HelpRequested.ShouldBeFalse(); } } diff --git a/holonsoft.CmdLineParser.Tests/MessageFormatterTests.cs b/holonsoft.CmdLineParser.Tests/MessageFormatterTests.cs index 1e1bafb..7a98722 100644 --- a/holonsoft.CmdLineParser.Tests/MessageFormatterTests.cs +++ b/holonsoft.CmdLineParser.Tests/MessageFormatterTests.cs @@ -1,12 +1,11 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; using holonsoft.CmdLineParser.Abstractions.Validation; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class MessageFormatterTests { - public class Args { +public sealed class MessageFormatterTests { + public sealed class Args { [Argument(ArgumentTypes.Required), ValueRange(1, 5)] public int Number; } @@ -17,32 +16,32 @@ public void FormatterReplacesEveryMessage() { var parser = new CommandLineParser(options); var invalid = parser.ParseArguments(["-Number", "abc"]); - Assert.Equal("[InvalidValue] Number", Assert.Single(invalid.Errors).Message); + invalid.Errors.ShouldHaveSingleItem().Message.ShouldBe("[InvalidValue] Number"); var missing = parser.ParseArguments([]); - Assert.Equal("[MissingArgument] Number", Assert.Single(missing.Errors).Message); + missing.Errors.ShouldHaveSingleItem().Message.ShouldBe("[MissingArgument] Number"); var validation = parser.ParseArguments(["-Number", "9"]); - Assert.Equal("[ValidationFailed] Number", Assert.Single(validation.Errors).Message); + validation.Errors.ShouldHaveSingleItem().Message.ShouldBe("[ValidationFailed] Number"); } [Fact] public void FormatterKeepsKindValueAndName() { var options = new CommandLineParserOptions { MessageFormatter = _ => "Fehler" }; - var error = Assert.Single(new CommandLineParser(options).ParseArguments(["-Number", "abc"]).Errors); + var error = new CommandLineParser(options).ParseArguments(["-Number", "abc"]).Errors.ShouldHaveSingleItem(); - Assert.Equal("Fehler", error.Message); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Equal("Number", error.ArgumentName); - Assert.Equal("abc", error.Value); + error.Message.ShouldBe("Fehler"); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + error.ArgumentName.ShouldBe("Number"); + error.Value.ShouldBe("abc"); } [Fact] public void ReturningNullKeepsTheDefaultMessage() { var options = new CommandLineParserOptions { MessageFormatter = _ => null }; - var error = Assert.Single(new CommandLineParser(options).ParseArguments(["-Number", "abc"]).Errors); + var error = new CommandLineParser(options).ParseArguments(["-Number", "abc"]).Errors.ShouldHaveSingleItem(); - Assert.Contains("is not valid for argument", error.Message); + error.Message.ShouldContain("is not valid for argument", Case.Sensitive); } [Fact] @@ -52,8 +51,8 @@ public void FormatterIsNotCalledWithoutErrors() { var result = new CommandLineParser(options).ParseArguments(["-Number", "3"]); - Assert.True(result.IsSuccess); - Assert.Equal(0, calls); + result.IsSuccess.ShouldBeTrue(); + calls.ShouldBe(0); } [Fact] @@ -63,6 +62,6 @@ public void FormatterSeesTheDefaultMessage() { new CommandLineParser(options).ParseArguments(["-Number", "abc"]); - Assert.Contains("'abc'", seen); + seen.ShouldNotBeNull().ShouldContain("'abc'", Case.Sensitive); } } diff --git a/holonsoft.CmdLineParser.Tests/ParserBehaviorTests.cs b/holonsoft.CmdLineParser.Tests/ParserBehaviorTests.cs index 8dbcc74..2ff6f0a 100644 --- a/holonsoft.CmdLineParser.Tests/ParserBehaviorTests.cs +++ b/holonsoft.CmdLineParser.Tests/ParserBehaviorTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ParserBehaviorTests { - public class Args { +public sealed class ParserBehaviorTests { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce, ShortName = "n", DefaultValue = 1)] public int Number; @@ -16,7 +15,7 @@ public class Args { public string[]? Items; } - public class DuplicateNames { + public sealed class DuplicateNames { [Argument(ArgumentTypes.AtMostOnce, ShortName = "x")] public int First; @@ -24,7 +23,7 @@ public class DuplicateNames { public int Second; } - public class CaseCollision { + public sealed class CaseCollision { [Argument(ArgumentTypes.AtMostOnce, ShortName = "v")] public bool Verbose; @@ -32,7 +31,7 @@ public class CaseCollision { public bool Version; } - public class TwoDefaults { + public sealed class TwoDefaults { [DefaultArgument(ArgumentTypes.AtMostOnce)] public string? First; @@ -40,7 +39,7 @@ public class TwoDefaults { public string? Second; } - public class MultiDimensional { + public sealed class MultiDimensional { [Argument(ArgumentTypes.AtMostOnce)] public int[,]? Matrix; } @@ -52,11 +51,11 @@ public void EveryParseCreatesAFreshInstance() { var first = parser.Parse(["-n", "5", "-Text", "a", "-Items", "x"]); var second = parser.Parse([]); - Assert.NotSame(first, second); - Assert.Equal(5, first.Number); - Assert.Equal(1, second.Number); - Assert.Null(second.Text); - Assert.Null(second.Items); + second.ShouldNotBeSameAs(first); + first.Number.ShouldBe(5); + second.Number.ShouldBe(1); + second.Text.ShouldBeNull(); + second.Items.ShouldBeNull(); } [Fact] @@ -72,15 +71,15 @@ public void ParserIsSafeForConcurrentUse() { } }); - Assert.Equal(0, failures); + failures.ShouldBe(0); } [Fact] public void NamesAreCaseSensitiveByDefault() { var result = new CommandLineParser().ParseArguments(["-number", "5"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnknownArgument, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnknownArgument); } [Fact] @@ -89,54 +88,54 @@ public void IgnoreCaseOptionMatchesAnyCasing() { var result = parser.ParseArguments(["-NUMBER", "5", "-N", "6"]); - Assert.Contains(result.Errors, e => e.Kind == ParserErrorKinds.DuplicateArgument); - Assert.Equal(5, result.Value.Number); + result.Errors.ShouldContain(e => e.Kind == ParserErrorKinds.DuplicateArgument); + result.Value.Number.ShouldBe(5); } [Fact] public void IgnoreCaseDetectsCollidingNames() { var parser = new CommandLineParser(new CommandLineParserOptions { IgnoreCase = true }); - Assert.Throws(() => parser.Parse([])); - Assert.Empty(new CommandLineParser().ParseArguments(["-v", "-V"]).Errors); + Should.Throw(() => parser.Parse([])); + new CommandLineParser().ParseArguments(["-v", "-V"]).Errors.ShouldBeEmpty(); } [Fact] public void DuplicateNamesAreAProgrammingError() { - var exception = Assert.Throws(() => new CommandLineParser().Parse([])); + var exception = Should.Throw(() => new CommandLineParser().Parse([])); - Assert.Contains("'x'", exception.Message); + exception.Message.ShouldContain("'x'", Case.Sensitive); } [Fact] public void TwoDefaultArgumentsAreAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void MultiDimensionalArraysAreNotSupported() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void NullOptionsThrow() { - Assert.Throws(() => new CommandLineParser(null!)); + Should.Throw(() => new CommandLineParser(null!)); } [Fact] public void OptionsAreExposed() { var options = new CommandLineParserOptions { IgnoreCase = true }; - Assert.Same(options, new CommandLineParser(options).Options); + new CommandLineParser(options).Options.ShouldBeSameAs(options); } [Fact] public void InlineAndSeparateValuesMix() { var result = new CommandLineParser().ParseArguments(["-n=5", "--Text:hello", "-Items:a", "b", "c"]); - Assert.Empty(result.Errors); - Assert.Equal(5, result.Value.Number); - Assert.Equal("hello", result.Value.Text); - Assert.Equal(["a", "b", "c"], result.Value.Items!); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(5); + result.Value.Text.ShouldBe("hello"); + result.Value.Items!.ShouldBe(["a", "b", "c"]); } } diff --git a/holonsoft.CmdLineParser.Tests/PropertyTargetTests.cs b/holonsoft.CmdLineParser.Tests/PropertyTargetTests.cs index b630665..e543b53 100644 --- a/holonsoft.CmdLineParser.Tests/PropertyTargetTests.cs +++ b/holonsoft.CmdLineParser.Tests/PropertyTargetTests.cs @@ -1,10 +1,9 @@ using holonsoft.CmdLineParser.Abstractions; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class PropertyTargetTests { - public class PropertyArgs { +public sealed class PropertyTargetTests { + public sealed class PropertyArgs { [Argument(ArgumentTypes.Required, ShortName = "n")] public string Name { get; set; } = ""; @@ -20,7 +19,7 @@ public class PropertyArgs { public string NotAnArgument { get; set; } = "untouched"; } - public record RecordArgs { + public sealed record RecordArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Level { get; init; } } @@ -30,22 +29,22 @@ public class BaseArgs { public int Inherited { get; set; } } - public class DerivedArgs : BaseArgs { + public sealed class DerivedArgs : BaseArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Own { get; set; } } - public class GetOnlyArgs { + public sealed class GetOnlyArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Broken { get; } } - public class PrivateSetterArgs { + public sealed class PrivateSetterArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Broken { get; private set; } } - public class ReadOnlyFieldArgs { + public sealed class ReadOnlyFieldArgs { [Argument(ArgumentTypes.AtMostOnce)] public readonly int Broken; } @@ -54,43 +53,43 @@ public class ReadOnlyFieldArgs { public void PropertiesReceiveValues() { var result = new CommandLineParser().ParseArguments(["-n", "test", "-c", "3", "-Verbose", "a.txt", "b.txt"]); - Assert.Empty(result.Errors); - Assert.Equal("test", result.Value.Name); - Assert.Equal(3, result.Value.Count); - Assert.True(result.Value.Verbose); - Assert.Equal(["a.txt", "b.txt"], result.Value.Files!); - Assert.Equal("untouched", result.Value.NotAnArgument); + result.Errors.ShouldBeEmpty(); + result.Value.Name.ShouldBe("test"); + result.Value.Count.ShouldBe(3); + result.Value.Verbose.ShouldBeTrue(); + result.Value.Files!.ShouldBe(["a.txt", "b.txt"]); + result.Value.NotAnArgument.ShouldBe("untouched"); } [Fact] public void RecordWithInitProperties() { var result = new CommandLineParser().ParseArguments(["-Level", "9"]); - Assert.Empty(result.Errors); - Assert.Equal(9, result.Value.Level); + result.Errors.ShouldBeEmpty(); + result.Value.Level.ShouldBe(9); } [Fact] public void InheritedPropertiesAreArguments() { var result = new CommandLineParser().ParseArguments(["-Inherited", "1", "-Own", "2"]); - Assert.Empty(result.Errors); - Assert.Equal(1, result.Value.Inherited); - Assert.Equal(2, result.Value.Own); + result.Errors.ShouldBeEmpty(); + result.Value.Inherited.ShouldBe(1); + result.Value.Own.ShouldBe(2); } [Fact] public void GetOnlyPropertyIsAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void PrivateSetterIsAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] public void ReadOnlyFieldIsAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } } diff --git a/holonsoft.CmdLineParser.Tests/ResponseFileTests.cs b/holonsoft.CmdLineParser.Tests/ResponseFileTests.cs index 59efc4f..5558e6d 100644 --- a/holonsoft.CmdLineParser.Tests/ResponseFileTests.cs +++ b/holonsoft.CmdLineParser.Tests/ResponseFileTests.cs @@ -1,11 +1,10 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; using holonsoft.CmdLineParser.Internal; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ResponseFileTests : IDisposable { +public sealed class ResponseFileTests : IDisposable { private readonly string _directory = Path.Combine(Path.GetTempPath(), "CmdLineParserTests", Guid.NewGuid().ToString("N")); public ResponseFileTests() => Directory.CreateDirectory(_directory); @@ -15,7 +14,7 @@ public void Dispose() { GC.SuppressFinalize(this); } - public class Args { + public sealed class Args { [Argument(ArgumentTypes.AtMostOnce)] public int Number; @@ -41,10 +40,10 @@ public void TokensFromFileReplaceTheReference() { var result = new CommandLineParser().ParseArguments(["@" + path, "-Verbose"]); - Assert.Empty(result.Errors); - Assert.Equal(5, result.Value.Number); - Assert.Equal(["a", "b c", "d"], result.Value.Items!); - Assert.True(result.Value.Verbose); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(5); + result.Value.Items!.ShouldBe(["a", "b c", "d"]); + result.Value.Verbose.ShouldBeTrue(); } [Fact] @@ -53,8 +52,8 @@ public void QuotedNegativeNumbersInFilesStayValues() { var result = new CommandLineParser().ParseArguments(["@" + path]); - Assert.Empty(result.Errors); - Assert.Equal(-5, result.Value.Number); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(-5); } [Fact] @@ -64,10 +63,10 @@ public void FilesCanNest() { var result = new CommandLineParser().ParseArguments(["@" + outer]); - Assert.Empty(result.Errors); - Assert.Equal(1, result.Value.Number); - Assert.True(result.Value.Verbose); - Assert.Equal(["x"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(1); + result.Value.Verbose.ShouldBeTrue(); + result.Value.Rest!.ShouldBe(["x"]); } [Fact] @@ -76,10 +75,10 @@ public void MissingFileIsAnErrorNotAnException() { var result = new CommandLineParser().ParseArguments(["@" + path, "-Number", "2"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ResponseFileError, error.Kind); - Assert.Equal(path, error.Value); - Assert.Equal(2, result.Value.Number); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ResponseFileError); + error.Value.ShouldBe(path); + result.Value.Number.ShouldBe(2); } [Fact] @@ -89,34 +88,34 @@ public void SelfReferencingFileStopsAtDepthLimit() { var result = new CommandLineParser().ParseArguments(["@" + path, "-Verbose"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ResponseFileError, error.Kind); - Assert.Contains("nested", error.Message); - Assert.True(result.Value.Verbose); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ResponseFileError); + error.Message.ShouldContain("nested", Case.Sensitive); + result.Value.Verbose.ShouldBeTrue(); } [Fact] public void ReferencesAfterEndOfOptionsAreLiteral() { var result = new CommandLineParser().ParseArguments(["--", "@nofile"]); - Assert.Empty(result.Errors); - Assert.Equal(["@nofile"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Rest!.ShouldBe(["@nofile"]); } [Fact] public void DoubleAtEscapesTheReference() { var result = new CommandLineParser().ParseArguments(["@@literal"]); - Assert.Empty(result.Errors); - Assert.Equal(["@literal"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Rest!.ShouldBe(["@literal"]); } [Fact] public void LoneAtIsAValue() { var result = new CommandLineParser().ParseArguments(["@"]); - Assert.Empty(result.Errors); - Assert.Equal(["@"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Rest!.ShouldBe(["@"]); } [Fact] @@ -125,14 +124,14 @@ public void ResponseFilesCanBeDisabled() { var result = parser.ParseArguments(["@file"]); - Assert.Empty(result.Errors); - Assert.Equal(["@file"], result.Value.Rest!); + result.Errors.ShouldBeEmpty(); + result.Value.Rest!.ShouldBe(["@file"]); } [Fact] public void SplitLineKeepsQuotesForTheLexer() { var tokens = ResponseFileExpander.SplitLine("a \"b c\" -x:\"y z\" d\t\"\""); - Assert.Equal(["a", "\"b c\"", "-x:\"y z\"", "d", "\"\""], tokens); + tokens.ShouldBe(["a", "\"b c\"", "-x:\"y z\"", "d", "\"\""]); } } diff --git a/holonsoft.CmdLineParser.Tests/TypeConversionTests.cs b/holonsoft.CmdLineParser.Tests/TypeConversionTests.cs index 5af3804..7a70676 100644 --- a/holonsoft.CmdLineParser.Tests/TypeConversionTests.cs +++ b/holonsoft.CmdLineParser.Tests/TypeConversionTests.cs @@ -3,11 +3,10 @@ using System.Numerics; using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class TypeConversionTests { +public sealed class TypeConversionTests { public enum ByteEnum : byte { Zero = 0, One = 1, @@ -42,7 +41,7 @@ public sealed class Color { public string Name { get; init; } = ""; } - public class ModernTypes { + public sealed class ModernTypes { [Argument(ArgumentTypes.AtMostOnce)] public DateOnly Date; @@ -80,7 +79,7 @@ public class ModernTypes { public BigInteger Huge; } - public class NullableTypes { + public sealed class NullableTypes { [Argument(ArgumentTypes.AtMostOnce)] public int? Number; @@ -94,7 +93,7 @@ public class NullableTypes { public int?[]? Numbers; } - public class EnumTypes { + public sealed class EnumTypes { [Argument(ArgumentTypes.AtMostOnce)] public ByteEnum Byte; @@ -105,7 +104,7 @@ public class EnumTypes { public ByteEnum[]? Many; } - public class BoolArgs { + public sealed class BoolArgs { [Argument(ArgumentTypes.AtMostOnce, ShortName = "f")] public bool Flag; @@ -116,7 +115,7 @@ public class BoolArgs { public string[]? Files; } - public class CultureArgs { + public sealed class CultureArgs { [Argument(ArgumentTypes.AtMostOnce, Culture = "de-DE")] public double[]? German; @@ -127,7 +126,7 @@ public class CultureArgs { public DateTime[]? Dates; } - public class NegativeArgs { + public sealed class NegativeArgs { [Argument(ArgumentTypes.AtMostOnce, ShortName = "v")] public int Value; @@ -135,7 +134,7 @@ public class NegativeArgs { public double[]? Points; } - public class CustomTypes { + public sealed class CustomTypes { [Argument(ArgumentTypes.AtMostOnce)] public Point? Point; @@ -143,7 +142,7 @@ public class CustomTypes { public Money? Price; } - public class ConverterArgs { + public sealed class ConverterArgs { [Argument(ArgumentTypes.AtMostOnce)] public Color? Color; @@ -154,12 +153,12 @@ public class ConverterArgs { public int Number; } - public class IntOnlyArgs { + public sealed class IntOnlyArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Number; } - public class DefaultValueArgs { + public sealed class DefaultValueArgs { [Argument(ArgumentTypes.AtMostOnce, DefaultValue = "2020-01-02")] public DateTime Date; @@ -176,7 +175,7 @@ public class DefaultValueArgs { public string[]? Names; } - public class BadDefaultArgs { + public sealed class BadDefaultArgs { [Argument(ArgumentTypes.AtMostOnce, DefaultValue = "not a number")] public int Number; } @@ -198,82 +197,82 @@ public void ModernBclTypes() { "-Huge", "123456789012345678901234567890", ]); - Assert.Empty(result.Errors); + result.Errors.ShouldBeEmpty(); var v = result.Value; - Assert.Equal(new DateOnly(2024, 2, 29), v.Date); - Assert.Equal(new TimeOnly(13, 45), v.Time); - Assert.Equal(TimeSpan.FromMinutes(90), v.Duration); - Assert.Equal(new DateTimeOffset(2024, 2, 29, 13, 45, 0, TimeSpan.FromHours(2)), v.Stamp); - Assert.Equal(new Uri("https://example.org/x?y=1"), v.Url); - Assert.Equal(new Version(1, 2, 3, 4), v.Version); - Assert.Equal("b.txt", v.File!.Name); - Assert.Equal("b", v.Directory!.Name); - Assert.Equal(new IPEndPoint(IPAddress.Loopback, 8080), v.EndPoint); - Assert.Equal(Int128.MaxValue, v.Big); - Assert.Equal((Half) 1.5, v.Small); - Assert.Equal(BigInteger.Parse("123456789012345678901234567890", CultureInfo.InvariantCulture), v.Huge); + v.Date.ShouldBe(new DateOnly(2024, 2, 29)); + v.Time.ShouldBe(new TimeOnly(13, 45)); + v.Duration.ShouldBe(TimeSpan.FromMinutes(90)); + v.Stamp.ShouldBe(new DateTimeOffset(2024, 2, 29, 13, 45, 0, TimeSpan.FromHours(2))); + v.Url.ShouldBe(new Uri("https://example.org/x?y=1")); + v.Version.ShouldBe(new Version(1, 2, 3, 4)); + v.File!.Name.ShouldBe("b.txt"); + v.Directory!.Name.ShouldBe("b"); + v.EndPoint.ShouldBe(new IPEndPoint(IPAddress.Loopback, 8080)); + v.Big.ShouldBe(Int128.MaxValue); + v.Small.ShouldBe((Half) 1.5); + v.Huge.ShouldBe(BigInteger.Parse("123456789012345678901234567890", CultureInfo.InvariantCulture)); } [Fact] public void NullableValueTypes() { var result = new CommandLineParser().ParseArguments(["-Number", "5", "-Id", "6f9619ff-8b86-d011-b42d-00c04fc964ff", "-Flag", "-Numbers", "1", "2"]); - Assert.Empty(result.Errors); - Assert.Equal(5, result.Value.Number); - Assert.Equal(Guid.Parse("6f9619ff-8b86-d011-b42d-00c04fc964ff"), result.Value.Id); - Assert.True(result.Value.Flag); - Assert.Equal(new int?[] { 1, 2 }, result.Value.Numbers); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(5); + result.Value.Id.ShouldBe(Guid.Parse("6f9619ff-8b86-d011-b42d-00c04fc964ff")); + result.Value.Flag.ShouldBe(true); + result.Value.Numbers.ShouldBe(new int?[] { 1, 2 }); } [Fact] public void NullableStaysNullWhenNotGiven() { var result = new CommandLineParser().ParseArguments([]); - Assert.Empty(result.Errors); - Assert.Null(result.Value.Number); - Assert.Null(result.Value.Id); - Assert.Null(result.Value.Flag); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBeNull(); + result.Value.Id.ShouldBeNull(); + result.Value.Flag.ShouldBeNull(); } [Fact] public void EnumWithNonIntUnderlyingType() { var result = new CommandLineParser().ParseArguments(["-Byte", "Two"]); - Assert.Empty(result.Errors); - Assert.Equal(ByteEnum.Two, result.Value.Byte); + result.Errors.ShouldBeEmpty(); + result.Value.Byte.ShouldBe(ByteEnum.Two); } [Fact] public void EnumIsCaseInsensitiveAndAcceptsNumbers() { var parser = new CommandLineParser(); - Assert.Equal(ByteEnum.One, parser.Parse(["-Byte", "one"]).Byte); - Assert.Equal(ByteEnum.Two, parser.Parse(["-Byte", "2"]).Byte); - Assert.False(parser.HasErrors); + parser.Parse(["-Byte", "one"]).Byte.ShouldBe(ByteEnum.One); + parser.Parse(["-Byte", "2"]).Byte.ShouldBe(ByteEnum.Two); + parser.HasErrors.ShouldBeFalse(); } [Fact] public void FlagsEnumAcceptsCommaSeparatedNames() { var result = new CommandLineParser().ParseArguments(["-Flags", "A, B"]); - Assert.Empty(result.Errors); - Assert.Equal(Options.A | Options.B, result.Value.Flags); + result.Errors.ShouldBeEmpty(); + result.Value.Flags.ShouldBe(Options.A | Options.B); } [Fact] public void EnumArray() { var result = new CommandLineParser().ParseArguments(["-Many", "One", "two", "0"]); - Assert.Empty(result.Errors); - Assert.Equal([ByteEnum.One, ByteEnum.Two, ByteEnum.Zero], result.Value.Many); + result.Errors.ShouldBeEmpty(); + result.Value.Many.ShouldBe([ByteEnum.One, ByteEnum.Two, ByteEnum.Zero]); } [Fact] public void InvalidEnumNameIsInvalidValue() { var result = new CommandLineParser().ParseArguments(["-Byte", "Three"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); } [Theory] @@ -289,63 +288,63 @@ public void InvalidEnumNameIsInvalidValue() { public void BoolLiterals(string literal, bool expected) { var result = new CommandLineParser().ParseArguments(["-f", literal]); - Assert.Empty(result.Errors); - Assert.Equal(expected, result.Value.Flag); + result.Errors.ShouldBeEmpty(); + result.Value.Flag.ShouldBe(expected); } [Fact] public void BoolFollowedByNonBoolValueIsTrueAndValueGoesToDefaultArgument() { var result = new CommandLineParser().ParseArguments(["-f", "file1", "file2"]); - Assert.Empty(result.Errors); - Assert.True(result.Value.Flag); - Assert.Equal(["file1", "file2"], result.Value.Files!); + result.Errors.ShouldBeEmpty(); + result.Value.Flag.ShouldBeTrue(); + result.Value.Files!.ShouldBe(["file1", "file2"]); } [Fact] public void BoolWithDefaultTrueCanBeSwitchedOff() { var result = new CommandLineParser().ParseArguments(["-OnByDefault", "false"]); - Assert.Empty(result.Errors); - Assert.False(result.Value.OnByDefault); - Assert.True(new CommandLineParser().Parse([]).OnByDefault); + result.Errors.ShouldBeEmpty(); + result.Value.OnByDefault.ShouldBeFalse(); + new CommandLineParser().Parse([]).OnByDefault.ShouldBeTrue(); } [Fact] public void CollectionsRespectCulture() { var result = new CommandLineParser().ParseArguments(["-German", "1,5", "2,5", "-Invariant", "1.5", "2.5", "-Dates", "24.12.2020"]); - Assert.Empty(result.Errors); - Assert.Equal([1.5, 2.5], result.Value.German!); - Assert.Equal([1.5, 2.5], result.Value.Invariant!); - Assert.Equal([new DateTime(2020, 12, 24)], result.Value.Dates!); + result.Errors.ShouldBeEmpty(); + result.Value.German!.ShouldBe([1.5, 2.5]); + result.Value.Invariant!.ShouldBe([1.5, 2.5]); + result.Value.Dates!.ShouldBe([new DateTime(2020, 12, 24)]); } [Fact] public void NegativeNumbersDoNotNeedQuotes() { var result = new CommandLineParser().ParseArguments(["-v", "-5", "-p", "-1.5", "-.5", "2"]); - Assert.Empty(result.Errors); - Assert.Equal(-5, result.Value.Value); - Assert.Equal([-1.5, -0.5, 2], result.Value.Points!); + result.Errors.ShouldBeEmpty(); + result.Value.Value.ShouldBe(-5); + result.Value.Points!.ShouldBe([-1.5, -0.5, 2]); } [Fact] public void QuotedNegativeNumbersStillWork() { var result = new CommandLineParser().ParseArguments(["-v", "\"-5\""]); - Assert.Empty(result.Errors); - Assert.Equal(-5, result.Value.Value); + result.Errors.ShouldBeEmpty(); + result.Value.Value.ShouldBe(-5); } [Fact] public void CustomTypesWithStaticParseAreSupportedByReflection() { var result = new CommandLineParser().ParseArguments(["-Point", "3,4", "-Price", "1.234,50"]); - Assert.Empty(result.Errors); - Assert.Equal(3, result.Value.Point!.X); - Assert.Equal(4, result.Value.Point.Y); - Assert.Equal(1234.50m, result.Value.Price!.Amount); + result.Errors.ShouldBeEmpty(); + result.Value.Point!.X.ShouldBe(3); + result.Value.Point.Y.ShouldBe(4); + result.Value.Price!.Amount.ShouldBe(1234.50m); } [Fact] @@ -355,9 +354,9 @@ public void RegisteredConverterIsUsedForScalarsAndArrays() { var result = parser.ParseArguments(["-Color", "red", "-Palette", "green", "blue"]); - Assert.Empty(result.Errors); - Assert.Equal("RED", result.Value.Color!.Name); - Assert.Equal(["GREEN", "BLUE"], result.Value.Palette!.Select(c => c.Name)); + result.Errors.ShouldBeEmpty(); + result.Value.Color!.Name.ShouldBe("RED"); + result.Value.Palette!.Select(c => c.Name).ShouldBe(["GREEN", "BLUE"]); } [Fact] @@ -367,8 +366,8 @@ public void RegisteredConverterOverridesBuiltIn() { var result = parser.ParseArguments(["-Number", "abcd"]); - Assert.Empty(result.Errors); - Assert.Equal(4, result.Value.Number); + result.Errors.ShouldBeEmpty(); + result.Value.Number.ShouldBe(4); } [Fact] @@ -378,28 +377,28 @@ public void ConverterExceptionBecomesInvalidValue() { var result = parser.ParseArguments(["-Color", "red"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, error.Kind); - Assert.Contains("boom", error.Message); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.InvalidValue); + error.Message.ShouldContain("boom", Case.Sensitive); } [Fact] public void DefaultValuesAreConvertedToMemberType() { var result = new CommandLineParser().ParseArguments([]); - Assert.Empty(result.Errors); - Assert.Equal(new DateTime(2020, 1, 2), result.Value.Date); - Assert.Equal(42L, result.Value.Long); - Assert.Equal(ByteEnum.Two, result.Value.Enum); - Assert.Equal(7, result.Value.Nullable); - Assert.Equal(["a", "b"], result.Value.Names!); + result.Errors.ShouldBeEmpty(); + result.Value.Date.ShouldBe(new DateTime(2020, 1, 2)); + result.Value.Long.ShouldBe(42L); + result.Value.Enum.ShouldBe(ByteEnum.Two); + result.Value.Nullable.ShouldBe(7); + result.Value.Names!.ShouldBe(["a", "b"]); } [Fact] public void UnconvertibleDefaultValueIsAProgrammingError() { var parser = new CommandLineParser(); - Assert.Throws(() => parser.Parse([])); + Should.Throw(() => parser.Parse([])); } [Fact] @@ -407,7 +406,7 @@ public void DefaultCultureOptionAppliesWhenAttributeHasNone() { var options = new CommandLineParserOptions { DefaultCulture = CultureInfo.GetCultureInfo("de-DE") }; var result = new CommandLineParser(options).ParseArguments(["-Invariant", "1,5"]); - Assert.Empty(result.Errors); - Assert.Equal([1.5], result.Value.Invariant!); + result.Errors.ShouldBeEmpty(); + result.Value.Invariant!.ShouldBe([1.5]); } } diff --git a/holonsoft.CmdLineParser.Tests/ValidationTests.cs b/holonsoft.CmdLineParser.Tests/ValidationTests.cs index 6c2617d..5a99c76 100644 --- a/holonsoft.CmdLineParser.Tests/ValidationTests.cs +++ b/holonsoft.CmdLineParser.Tests/ValidationTests.cs @@ -2,12 +2,11 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; using holonsoft.CmdLineParser.Abstractions.Validation; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class ValidationTests { - public class RangeArgs { +public sealed class ValidationTests { + public sealed class RangeArgs { [Argument(ArgumentTypes.AtMostOnce), ValueRange(1, 10)] public int Count; @@ -27,12 +26,12 @@ public class RangeArgs { public int Both; } - public class RangeOnString { + public sealed class RangeOnString { [Argument(ArgumentTypes.AtMostOnce), ValueRange(1, 2)] public string? Text; } - public class AllowedArgs { + public sealed class AllowedArgs { [Argument(ArgumentTypes.AtMostOnce), AllowedValues("red", "green")] public string? Color; @@ -49,7 +48,7 @@ public class AllowedArgs { public long[]? Many; } - public class RegexArgs { + public sealed class RegexArgs { [Argument(ArgumentTypes.AtMostOnce), RegexPattern("[a-z]+")] public string? Name; @@ -57,12 +56,12 @@ public class RegexArgs { public string? Loose; } - public class RegexOnInt { + public sealed class RegexOnInt { [Argument(ArgumentTypes.AtMostOnce), RegexPattern("1")] public int Number; } - public class ExistArgs { + public sealed class ExistArgs { [Argument(ArgumentTypes.AtMostOnce), MustExist(ExistenceKind.File)] public string? File; @@ -73,7 +72,7 @@ public class ExistArgs { public FileInfo? Any; } - public class CrossField : IValidatableArguments { + public sealed class CrossField : IValidatableArguments { [Argument(ArgumentTypes.AtMostOnce)] public string? File; @@ -105,8 +104,8 @@ public IEnumerable Validate() { public void ValueInRangeIsAccepted(string value) { var result = new CommandLineParser().ParseArguments(["-Count", value]); - Assert.Empty(result.Errors); - Assert.Equal(int.Parse(value), result.Value.Count); + result.Errors.ShouldBeEmpty(); + result.Value.Count.ShouldBe(int.Parse(value)); } [Theory] @@ -116,123 +115,123 @@ public void ValueInRangeIsAccepted(string value) { public void ValueOutOfRangeIsReportedAndNotAssigned(string value) { var result = new CommandLineParser().ParseArguments(["-Count", value]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ValidationFailed, error.Kind); - Assert.Equal("Count", error.ArgumentName); - Assert.Equal(value, error.Value); - Assert.Contains("1..10", error.Message); - Assert.Equal(0, result.Value.Count); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ValidationFailed); + error.ArgumentName.ShouldBe("Count"); + error.Value.ShouldBe(value); + error.Message.ShouldContain("1..10", Case.Sensitive); + result.Value.Count.ShouldBe(0); } [Fact] public void DoubleRange() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Ratio", "1.5"]).Errors); - Assert.Contains(parser.ParseArguments(["-Ratio", "1.51"]).Errors, e => e.Kind == ParserErrorKinds.ValidationFailed); + parser.ParseArguments(["-Ratio", "1.5"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Ratio", "1.51"]).Errors.ShouldContain(e => e.Kind == ParserErrorKinds.ValidationFailed); } [Fact] public void CollectionElementsAreValidatedIndividually() { var result = new CommandLineParser().ParseArguments(["-Levels", "1", "4", "2", "9"]); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.ValidationFailed, e.Kind)); - Assert.Equal(["4", "9"], result.Errors.Select(e => e.Value)); - Assert.Null(result.Value.Levels); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.ValidationFailed); + result.Errors.Select(e => e.Value).ShouldBe(["4", "9"]); + result.Value.Levels.ShouldBeNull(); } [Fact] public void CustomMessageWithPlaceholders() { var result = new CommandLineParser().ParseArguments(["-Custom", "7"]); - var error = Assert.Single(result.Errors); - Assert.Equal("Custom must be 1..5, not 7", error.Message); + var error = result.Errors.ShouldHaveSingleItem(); + error.Message.ShouldBe("Custom must be 1..5, not 7"); } [Fact] public void DefaultValuesAreNotValidated() { var result = new CommandLineParser().ParseArguments([]); - Assert.Empty(result.Errors); - Assert.Equal(99, result.Value.WithDefault); + result.Errors.ShouldBeEmpty(); + result.Value.WithDefault.ShouldBe(99); } [Fact] public void SeveralValidatorsReportSeparately() { var result = new CommandLineParser().ParseArguments(["-Both", "11"]); - Assert.Equal(2, result.Errors.Count); - Assert.All(result.Errors, e => Assert.Equal(ParserErrorKinds.ValidationFailed, e.Kind)); + result.Errors.Count.ShouldBe(2); + result.Errors.ShouldAllBe(e => e.Kind == ParserErrorKinds.ValidationFailed); } [Fact] public void RangeOnUnsupportedTypeIsAProgrammingError() { - var exception = Assert.Throws(() => new CommandLineParser().Parse([])); + var exception = Should.Throw(() => new CommandLineParser().Parse([])); - Assert.Contains("ValueRangeAttribute", exception.Message); - Assert.Contains("Text", exception.Message); + exception.Message.ShouldContain("ValueRangeAttribute", Case.Sensitive); + exception.Message.ShouldContain("Text", Case.Sensitive); } [Fact] public void AllowedStrings() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Color", "red"]).Errors); + parser.ParseArguments(["-Color", "red"]).Errors.ShouldBeEmpty(); - var error = Assert.Single(parser.ParseArguments(["-Color", "blue"]).Errors); - Assert.Equal(ParserErrorKinds.ValidationFailed, error.Kind); - Assert.Contains("red, green", error.Message); + var error = parser.ParseArguments(["-Color", "blue"]).Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ValidationFailed); + error.Message.ShouldContain("red, green", Case.Sensitive); } [Fact] public void AllowedStringsIgnoreCaseOnRequest() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Loose", "red"]).Errors); - Assert.Single(parser.ParseArguments(["-Color", "RED"]).Errors); + parser.ParseArguments(["-Loose", "red"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Color", "RED"]).Errors.ShouldHaveSingleItem(); } [Fact] public void AllowedNumbers() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Level", "2"]).Errors); - Assert.Single(parser.ParseArguments(["-Level", "4"]).Errors); - Assert.Empty(parser.ParseArguments(["-Many", "1", "2"]).Errors); - Assert.Single(parser.ParseArguments(["-Many", "3"]).Errors); + parser.ParseArguments(["-Level", "2"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Level", "4"]).Errors.ShouldHaveSingleItem(); + parser.ParseArguments(["-Many", "1", "2"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Many", "3"]).Errors.ShouldHaveSingleItem(); } [Fact] public void AllowedEnumConstantsAndNames() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Mode", "One"]).Errors); - Assert.Empty(parser.ParseArguments(["-Mode", "Two"]).Errors); - Assert.Single(parser.ParseArguments(["-Mode", "Zero"]).Errors); + parser.ParseArguments(["-Mode", "One"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Mode", "Two"]).Errors.ShouldBeEmpty(); + parser.ParseArguments(["-Mode", "Zero"]).Errors.ShouldHaveSingleItem(); } [Fact] public void RegexMustMatchWholeValue() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Name", "abc"]).Errors); + parser.ParseArguments(["-Name", "abc"]).Errors.ShouldBeEmpty(); - var error = Assert.Single(parser.ParseArguments(["-Name", "abc1"]).Errors); - Assert.Equal(ParserErrorKinds.ValidationFailed, error.Kind); - Assert.Contains("[a-z]+", error.Message); + var error = parser.ParseArguments(["-Name", "abc1"]).Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ValidationFailed); + error.Message.ShouldContain("[a-z]+", Case.Sensitive); } [Fact] public void RegexOptionsApply() { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-Loose", "AB"]).Errors); + parser.ParseArguments(["-Loose", "AB"]).Errors.ShouldBeEmpty(); } [Fact] public void RegexOnNonStringIsAProgrammingError() { - Assert.Throws(() => new CommandLineParser().Parse([])); + Should.Throw(() => new CommandLineParser().Parse([])); } [Fact] @@ -244,16 +243,16 @@ public void MustExistChecksTheFileSystem() { try { var parser = new CommandLineParser(); - Assert.Empty(parser.ParseArguments(["-File", file, "-Directory", directory, "-Any", file]).Errors); + parser.ParseArguments(["-File", file, "-Directory", directory, "-Any", file]).Errors.ShouldBeEmpty(); - var error = Assert.Single(parser.ParseArguments(["-File", missing]).Errors); - Assert.Equal(ParserErrorKinds.ValidationFailed, error.Kind); - Assert.Contains("does not exist", error.Message); - Assert.Null(parser.ParseArguments(["-File", missing]).Value.File); + var error = parser.ParseArguments(["-File", missing]).Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ValidationFailed); + error.Message.ShouldContain("does not exist", Case.Sensitive); + parser.ParseArguments(["-File", missing]).Value.File.ShouldBeNull(); - Assert.Single(parser.ParseArguments(["-File", directory]).Errors); - Assert.Single(parser.ParseArguments(["-Directory", file]).Errors); - Assert.Single(parser.ParseArguments(["-Any", missing]).Errors); + parser.ParseArguments(["-File", directory]).Errors.ShouldHaveSingleItem(); + parser.ParseArguments(["-Directory", file]).Errors.ShouldHaveSingleItem(); + parser.ParseArguments(["-Any", missing]).Errors.ShouldHaveSingleItem(); } finally { File.Delete(file); } @@ -263,26 +262,26 @@ public void MustExistChecksTheFileSystem() { public void ObjectValidationRunsWhenEverythingElseIsFine() { var result = new CommandLineParser().ParseArguments(["-Count", "1"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.ValidationFailed, error.Kind); - Assert.Equal(string.Empty, error.ArgumentName); - Assert.Equal("Either --File or --Stdin is required.", error.Message); - Assert.Equal(1, result.Value.ValidateCalls); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.ValidationFailed); + error.ArgumentName.ShouldBe(string.Empty); + error.Message.ShouldBe("Either --File or --Stdin is required."); + result.Value.ValidateCalls.ShouldBe(1); } [Fact] public void ObjectValidationCanReportSeveralProblems() { var result = new CommandLineParser().ParseArguments(["-Count", "-1"]); - Assert.Equal(2, result.Errors.Count); + result.Errors.Count.ShouldBe(2); } [Fact] public void ObjectValidationPasses() { var result = new CommandLineParser().ParseArguments(["-Stdin"]); - Assert.Empty(result.Errors); - Assert.True(result.IsSuccess); + result.Errors.ShouldBeEmpty(); + result.IsSuccess.ShouldBeTrue(); } [Fact] @@ -290,13 +289,13 @@ public void ObjectValidationIsSkippedAfterOtherErrorsAndForHelp() { var parser = new CommandLineParser(); var failed = parser.ParseArguments(["-Count", "x"]); - Assert.Single(failed.Errors); - Assert.Equal(ParserErrorKinds.InvalidValue, failed.Errors[0].Kind); - Assert.Equal(0, failed.Value.ValidateCalls); + failed.Errors.ShouldHaveSingleItem(); + failed.Errors[0].Kind.ShouldBe(ParserErrorKinds.InvalidValue); + failed.Value.ValidateCalls.ShouldBe(0); var help = parser.ParseArguments(["--help"]); - Assert.Empty(help.Errors); - Assert.Equal(0, help.Value.ValidateCalls); + help.Errors.ShouldBeEmpty(); + help.Value.ValidateCalls.ShouldBe(0); } [Fact] @@ -305,6 +304,6 @@ public void ErrorReporterReceivesValidationErrors() { new CommandLineParser().Parse(["-Count", "0"], (kind, _) => kinds.Add(kind)); - Assert.Equal([ParserErrorKinds.ValidationFailed], kinds); + kinds.ShouldBe([ParserErrorKinds.ValidationFailed]); } } diff --git a/holonsoft.CmdLineParser.Tests/VerbParserTests.cs b/holonsoft.CmdLineParser.Tests/VerbParserTests.cs index 019aadd..5101627 100644 --- a/holonsoft.CmdLineParser.Tests/VerbParserTests.cs +++ b/holonsoft.CmdLineParser.Tests/VerbParserTests.cs @@ -1,13 +1,12 @@ using holonsoft.CmdLineParser.Abstractions; using holonsoft.CmdLineParser.Abstractions.Enums; -using Xunit; namespace holonsoft.CmdLineParser.Tests; -public class VerbParserTests { +public sealed class VerbParserTests { [Verb("build", HelpText = "Builds.", Aliases = new[] { "b" })] [CommandLineDescription("Builds the project.")] - public class BuildArgs { + public sealed class BuildArgs { [Argument(ArgumentTypes.AtMostOnce, ShortName = "r", HelpText = "Release configuration.")] public bool Release; @@ -16,12 +15,12 @@ public class BuildArgs { } [Verb("test", HelpText = "Tests.")] - public class TestArgs { + public sealed class TestArgs { [Argument(ArgumentTypes.Required)] public string? Filter; } - public class RunArgs { + public sealed class RunArgs { [Argument(ArgumentTypes.AtMostOnce)] public int Times; } @@ -32,33 +31,33 @@ public class RunArgs { public void FirstTokenSelectsTheVerbAndTheRestIsParsedByIt() { var result = Create().Parse(["build", "-r", "a.csproj"]); - Assert.True(result.IsSuccess); - Assert.Equal("build", result.Verb); - Assert.True(result.Is(out var build)); - Assert.True(build.Release); - Assert.Equal(["a.csproj"], build.Projects!); - Assert.False(result.Is(out _)); + result.IsSuccess.ShouldBeTrue(); + result.Verb.ShouldBe("build"); + result.Is(out var build).ShouldBeTrue(); + build.Release.ShouldBeTrue(); + build.Projects!.ShouldBe(["a.csproj"]); + result.Is(out _).ShouldBeFalse(); } [Fact] public void AliasSelectsTheVerb() { var result = Create().Parse(["b"]); - Assert.Equal("build", result.Verb); - Assert.IsType(result.Value); + result.Verb.ShouldBe("build"); + result.Value.ShouldBeOfType(); } [Fact] public void UnknownVerbIsReported() { var result = Create().Parse(["deploy", "-x"]); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.UnknownVerb, error.Kind); - Assert.Equal("deploy", error.Value); - Assert.Contains("build, test", error.Message); - Assert.Null(result.Verb); - Assert.Null(result.Value); - Assert.False(result.IsSuccess); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.UnknownVerb); + error.Value.ShouldBe("deploy"); + error.Message.ShouldContain("build, test", Case.Sensitive); + result.Verb.ShouldBeNull(); + result.Value.ShouldBeNull(); + result.IsSuccess.ShouldBeFalse(); } [Fact] @@ -66,8 +65,8 @@ public void MissingVerbIsReportedWithoutDefault() { foreach (var args in new[] { Array.Empty(), ["-r"], ["--"] }) { var result = Create().Parse(args); - var error = Assert.Single(result.Errors); - Assert.Equal(ParserErrorKinds.MissingVerb, error.Kind); + var error = result.Errors.ShouldHaveSingleItem(); + error.Kind.ShouldBe(ParserErrorKinds.MissingVerb); } } @@ -75,17 +74,17 @@ public void MissingVerbIsReportedWithoutDefault() { public void DefaultVerbHandlesCommandLinesWithoutVerb() { var parser = Create().Add("run", "Runs.", isDefault: true); - Assert.Equal("run", parser.Parse([]).Verb); + parser.Parse([]).Verb.ShouldBe("run"); var withArgs = parser.Parse(["-Times", "3"]); - Assert.Equal("run", withArgs.Verb); - Assert.Equal(3, Assert.IsType(withArgs.Value).Times); + withArgs.Verb.ShouldBe("run"); + withArgs.Value.ShouldBeOfType().Times.ShouldBe(3); - Assert.Equal("build", parser.Parse(["build"]).Verb); + parser.Parse(["build"]).Verb.ShouldBe("build"); var unknownWord = parser.Parse(["something"]); - Assert.Equal("run", unknownWord.Verb); - Assert.Equal(ParserErrorKinds.UnexpectedValue, Assert.Single(unknownWord.Errors).Kind); + unknownWord.Verb.ShouldBe("run"); + unknownWord.Errors.ShouldHaveSingleItem().Kind.ShouldBe(ParserErrorKinds.UnexpectedValue); } [Theory] @@ -95,78 +94,78 @@ public void DefaultVerbHandlesCommandLinesWithoutVerb() { public void HelpWithoutVerbAsksForTheOverview(string help) { var result = new VerbParser(new CommandLineParserOptions { AllowSlashPrefix = true }).Add().Add().Parse([help]); - Assert.True(result.HelpRequested); - Assert.Null(result.Verb); - Assert.Empty(result.Errors); + result.HelpRequested.ShouldBeTrue(); + result.Verb.ShouldBeNull(); + result.Errors.ShouldBeEmpty(); } [Fact] public void HelpAfterVerbAsksForThatVerbsHelp() { var result = Create().Parse(["test", "--help"]); - Assert.True(result.HelpRequested); - Assert.Equal("test", result.Verb); - Assert.Empty(result.Errors); + result.HelpRequested.ShouldBeTrue(); + result.Verb.ShouldBe("test"); + result.Errors.ShouldBeEmpty(); } [Fact] public void ErrorsOfTheVerbPropagate() { var result = Create().Parse(["test"]); - Assert.Equal("test", result.Verb); - Assert.IsType(result.Value); - Assert.Equal(ParserErrorKinds.MissingArgument, Assert.Single(result.Errors).Kind); - Assert.False(result.IsSuccess); + result.Verb.ShouldBe("test"); + result.Value.ShouldBeOfType(); + result.Errors.ShouldHaveSingleItem().Kind.ShouldBe(ParserErrorKinds.MissingArgument); + result.IsSuccess.ShouldBeFalse(); } [Fact] public void VerbNamesFollowTheIgnoreCaseOption() { - Assert.Equal(ParserErrorKinds.UnknownVerb, Create().Parse(["BUILD"]).Errors[0].Kind); + Create().Parse(["BUILD"]).Errors[0].Kind.ShouldBe(ParserErrorKinds.UnknownVerb); var relaxed = new VerbParser(new CommandLineParserOptions { IgnoreCase = true }).Add(); - Assert.Equal("build", relaxed.Parse(["BUILD"]).Verb); - Assert.Equal("build", relaxed.Parse(["B"]).Verb); + relaxed.Parse(["BUILD"]).Verb.ShouldBe("build"); + relaxed.Parse(["B"]).Verb.ShouldBe("build"); } [Fact] public void RegistrationIsValidated() { - Assert.Throws(() => Create().Add("build")); - Assert.Throws(() => Create().Add("run", aliases: "b")); - Assert.Throws(() => Create().Add("run", isDefault: true).Add("run2", isDefault: true)); - Assert.Throws(() => new VerbParser().Add()); - Assert.Throws(() => new VerbParser().Add("-run")); - Assert.Throws(() => new VerbParser().Add(" run")); - Assert.Throws(() => new VerbParser().Add("")); - Assert.Throws(() => new VerbParser().Parse(["x"])); - Assert.Throws(() => Create().Parse(null!)); - Assert.Throws(() => Create().Parse([null!])); + Should.Throw(() => Create().Add("build")); + Should.Throw(() => Create().Add("run", aliases: "b")); + Should.Throw(() => Create().Add("run", isDefault: true).Add("run2", isDefault: true)); + Should.Throw(() => new VerbParser().Add()); + Should.Throw(() => new VerbParser().Add("-run")); + Should.Throw(() => new VerbParser().Add(" run")); + Should.Throw(() => new VerbParser().Add("")); + Should.Throw(() => new VerbParser().Parse(["x"])); + Should.Throw(() => Create().Parse(null!)); + Should.Throw(() => Create().Parse([null!])); } [Fact] public void VerbsPropertyDescribesRegistrations() { var verbs = Create().Add("run", "Runs.", isDefault: true, "r").Verbs; - Assert.Equal(["build", "test", "run"], verbs.Select(v => v.Name)); - Assert.Equal(["b"], verbs[0].Aliases); - Assert.Equal("Builds.", verbs[0].HelpText); - Assert.Equal(typeof(BuildArgs), verbs[0].ArgumentType); - Assert.True(verbs[2].IsDefault); - Assert.Equal(["r"], verbs[2].Aliases); + verbs.Select(v => v.Name).ShouldBe(["build", "test", "run"]); + verbs[0].Aliases.ShouldBe(["b"]); + verbs[0].HelpText.ShouldBe("Builds."); + verbs[0].ArgumentType.ShouldBe(typeof(BuildArgs)); + verbs[2].IsDefault.ShouldBeTrue(); + verbs[2].Aliases.ShouldBe(["r"]); } [Fact] public void OverviewHelpListsVerbs() { var help = Create().GetConsoleFormattedHelpTexts("tool", 80); - Assert.StartsWith("Usage: tool [arguments]", help); - Assert.Contains("build, b", help); - Assert.Contains("Builds.", help); - Assert.Contains("Tests.", help); - Assert.Contains("tool --help", help); + help.ShouldStartWith("Usage: tool [arguments]", Case.Sensitive); + help.ShouldContain("build, b", Case.Sensitive); + help.ShouldContain("Builds.", Case.Sensitive); + help.ShouldContain("Tests.", Case.Sensitive); + help.ShouldContain("tool --help", Case.Sensitive); var withDefault = Create().Add("run", isDefault: true).GetConsoleFormattedHelpTexts("tool", 80); - Assert.StartsWith("Usage: tool [] [arguments]", withDefault); - Assert.Contains("(default)", withDefault); + withDefault.ShouldStartWith("Usage: tool [] [arguments]", Case.Sensitive); + withDefault.ShouldContain("(default)", Case.Sensitive); } [Fact] @@ -174,12 +173,12 @@ public void VerbHelpUsesTheVerbsParser() { var parser = Create(); var help = parser.GetConsoleFormattedHelpTexts("tool", 80, "build"); - Assert.StartsWith("Builds the project.", help); - Assert.Contains("Usage: tool build [options] [...]", help); - Assert.Contains("-r, --Release", help); + help.ShouldStartWith("Builds the project.", Case.Sensitive); + help.ShouldContain("Usage: tool build [options] [...]", Case.Sensitive); + help.ShouldContain("-r, --Release", Case.Sensitive); - Assert.Equal(help, parser.GetConsoleFormattedHelpTexts("tool", 80, "b")); - Assert.Throws(() => parser.GetConsoleFormattedHelpTexts("tool", 80, "nope")); + parser.GetConsoleFormattedHelpTexts("tool", 80, "b").ShouldBe(help); + Should.Throw(() => parser.GetConsoleFormattedHelpTexts("tool", 80, "nope")); } [Fact] @@ -187,9 +186,9 @@ public void MessageFormatterAppliesToVerbErrors() { var options = new CommandLineParserOptions { MessageFormatter = e => "X:" + e.Kind }; var parser = new VerbParser(options).Add().Add(); - Assert.Equal("X:UnknownVerb", parser.Parse(["nope"]).Errors[0].Message); - Assert.Equal("X:MissingVerb", parser.Parse([]).Errors[0].Message); - Assert.Equal("X:MissingArgument", parser.Parse(["test"]).Errors[0].Message); + parser.Parse(["nope"]).Errors[0].Message.ShouldBe("X:UnknownVerb"); + parser.Parse([]).Errors[0].Message.ShouldBe("X:MissingVerb"); + parser.Parse(["test"]).Errors[0].Message.ShouldBe("X:MissingArgument"); } [Fact] @@ -197,7 +196,7 @@ public void OptionsAreSharedWithVerbs() { var options = new CommandLineParserOptions(); var parser = new VerbParser(options); - Assert.Same(options, parser.Options); - Assert.Throws(() => new VerbParser(null!)); + parser.Options.ShouldBeSameAs(options); + Should.Throw(() => new VerbParser(null!)); } } diff --git a/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj b/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj index ba86f82..49db7bf 100644 --- a/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj +++ b/holonsoft.CmdLineParser.Tests/holonsoft.CmdLineParser.Tests.csproj @@ -11,9 +11,15 @@ + + + + + +