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/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.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/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 c0ead52..49db7bf 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 @@ -11,9 +11,15 @@ + + + + + + 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