From e4da160049dce6d67b790752117d96769338bd8e Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Sun, 17 Feb 2019 10:37:16 +0100 Subject: [PATCH] Improve tests, xml, auto-execute model commands --- .../Command/MultipleCommandTests.cs | 4 +- .../Command/SubCommandTests.cs | 31 +++++++++----- .../CommandLineParserTests.cs | 25 +++++------- .../CustomerReportedTests.cs | 6 +-- .../Parsing/ParserResultTest.cs | 4 +- .../Usage/HelpDisplayCommandTests.cs | 6 +-- CommandLineParser.Tests/XUnitExtensions.cs | 11 +++++ .../Abstractions/Command/ICommandBuilder.cs | 5 +++ .../Command/ICommandConfigurationBuilder.cs | 10 +++++ .../Command/ICommandConfigurationBuilder`.cs | 19 +++++++-- CommandLineParser/CommandLineParser.xml | 40 +++++++++++++++++-- ...mmandLineCommand`TOption`TCommandOption.cs | 14 +++++++ .../Core/Parsing/ParseResult'.cs | 14 ++++++- 13 files changed, 140 insertions(+), 49 deletions(-) diff --git a/CommandLineParser.Tests/Command/MultipleCommandTests.cs b/CommandLineParser.Tests/Command/MultipleCommandTests.cs index 1df52ea..f83c203 100644 --- a/CommandLineParser.Tests/Command/MultipleCommandTests.cs +++ b/CommandLineParser.Tests/Command/MultipleCommandTests.cs @@ -10,7 +10,7 @@ public class MultipleCommandTests [InlineData(new string[] { "cmd1", "-x", "8" }, false)] [InlineData(new string[] { "cmd2", "-x", "8" }, false)] [InlineData(new string[] { }, false)] - public void NonRequiredCommandShouldNotSetResultInErrorStateWhenRequiredOptionsAreMissing(string[] args, bool hasErrors) + public void NonRequiredCommandShouldNotSetResultInErrorStateWhenRequiredOptionsAreMissing(string[] args, bool _) { var parser = new CommandLineParser(); @@ -26,7 +26,7 @@ public void NonRequiredCommandShouldNotSetResultInErrorStateWhenRequiredOptionsA var result = parser.Parse(args); - Assert.False(result.HasErrors); + result.AssertNoErrors(); } private class MultipleCOmmandTestsOptions diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs index 77a76bf..e999338 100644 --- a/CommandLineParser.Tests/Command/SubCommandTests.cs +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -10,19 +10,24 @@ namespace MatthiWare.CommandLine.Tests.Command { public class SubCommandTests { - [Fact] - public void TestSubCommandWorksCorrectly() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestSubCommandWorksCorrectlyInModel(bool autoExecute) { var lock1 = new ManualResetEventSlim(); var lock2 = new ManualResetEventSlim(); - var containerResolver = new CustomInstantiator(lock1, lock2); + var containerResolver = new CustomInstantiator(lock1, lock2, autoExecute); var parser = new CommandLineParser(containerResolver); var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15", "-n", "-1" }); - Assert.False(result.HasErrors); + result.AssertNoErrors(); + + if (!autoExecute) + result.ExecuteCommands(); Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time."); Assert.True(lock2.Wait(1000), "SubCommand didn't execute in time."); @@ -32,19 +37,21 @@ private class CustomInstantiator : IContainerResolver { private readonly ManualResetEventSlim lock1; private readonly ManualResetEventSlim lock2; + private readonly bool autoExecute; - public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2) + public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2, bool autoExecute) { this.lock1 = lock1; this.lock2 = lock2; + this.autoExecute = autoExecute; } public T Resolve() { if (typeof(T) == typeof(MainCommand)) - return (T)Activator.CreateInstance(typeof(T), lock1); + return (T)Activator.CreateInstance(typeof(T), lock1, autoExecute); else if (typeof(T) == typeof(SubCommand)) - return (T)Activator.CreateInstance(typeof(T), lock2); + return (T)Activator.CreateInstance(typeof(T), lock2, autoExecute); else return default; } @@ -54,16 +61,19 @@ public T Resolve() public class MainCommand : Command { private readonly ManualResetEventSlim locker; + private readonly bool autoExecute; - public MainCommand(ManualResetEventSlim locker) + public MainCommand(ManualResetEventSlim locker, bool autoExecute) { this.locker = locker; + this.autoExecute = autoExecute; } public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("main") + .AutoExecute(autoExecute) .Required(); } @@ -78,16 +88,19 @@ public override void OnExecute(MainModel options, SubModel commandOptions) public class SubCommand : Command { private readonly ManualResetEventSlim locker; + private readonly bool autoExecute; - public SubCommand(ManualResetEventSlim locker) + public SubCommand(ManualResetEventSlim locker, bool autoExecute) { this.locker = locker; + this.autoExecute = autoExecute; } public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("sub") + .AutoExecute(autoExecute) .Required(); } diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 3e01dc8..45cc522 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading; -using MatthiWare.CommandLine; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; @@ -59,7 +58,7 @@ public void CommandLineParserUsesContainerCorrectly() var result = parser.Parse(new[] { "app.exe", "my" }); - Assert.False(result.HasErrors); + result.AssertNoErrors(); commandMock.VerifyAll(); containerMock.VerifyAll(); @@ -148,7 +147,7 @@ public void CommandLineParserUsesArgumentFactoryCorrectly() var result = parser.Parse(new[] { "app.exe", "-m" }); - Assert.False(result.HasErrors); + result.AssertNoErrors(); resolverMock.VerifyAll(); argResolverFactory.Verify(); @@ -168,7 +167,7 @@ public void ParseTests() Assert.NotNull(parsed); - Assert.False(parsed.HasErrors); + parsed.AssertNoErrors(); Assert.Equal("test", parsed.Result.Option1); } @@ -222,9 +221,7 @@ public void ParseWithDefaults(string[] args, string result1, string result2, str var parsed = parser.Parse(args); - Assert.NotNull(parsed); - - Assert.False(parsed.HasErrors); + parsed.AssertNoErrors(); Assert.Equal(result1, parsed.Result.Option1); Assert.Equal(result2, parsed.Result.Option2); @@ -246,7 +243,7 @@ public void ParseWithCustomParserInAttributeConfiguredModelTests() var result = parser.Parse(new[] { "app.exe", "-p", "sample" }); - Assert.False(result.HasErrors); + result.AssertNoErrors(); Assert.Same(obj, result.Result.Param); } @@ -278,9 +275,7 @@ public void ParseWithCommandTests() var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m", "my message" }); - Assert.False(parsed.HasErrors); - - Assert.NotNull(parsed); + parsed.AssertNoErrors(); Assert.Equal("test", parsed.Result.Option1); @@ -318,7 +313,7 @@ public void ParseCommandTests(string[] args, string result1, string result2) var result = parser.Parse(args); - Assert.False(result.HasErrors); + result.AssertNoErrors(); Assert.Equal(result1, result.Result.Message); @@ -335,16 +330,14 @@ public void BoolResolverSpecialCaseParsesCorrectly(string[] args, bool expected) { var parser = new CommandLineParser(); - //parser.Configure(opt => opt.Option1) - // .Name("o", "opt") - // .Default("Default message"); - parser.Configure(opt => opt.Option2) .Name("x", "xsomething") .Required(); var result = parser.Parse(args); + result.AssertNoErrors(); + Assert.Equal(expected, result.Result.Option2); } diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index 7d2a8cf..e78f42d 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -60,11 +60,7 @@ public void AutoPrintUsageAndErrorsShouldNotPrintWhenEverythingIsFIne(string ver var parsed = parser.Parse(items.ToArray()); - if (parsed.HasErrors) - { - foreach (var err in parsed.Errors) - throw err; - } + parsed.AssertNoErrors(); void AddItemToArray(string item) { diff --git a/CommandLineParser.Tests/Parsing/ParserResultTest.cs b/CommandLineParser.Tests/Parsing/ParserResultTest.cs index bfd1025..33db239 100644 --- a/CommandLineParser.Tests/Parsing/ParserResultTest.cs +++ b/CommandLineParser.Tests/Parsing/ParserResultTest.cs @@ -46,7 +46,7 @@ public void TestMergeResultOfCommandResultWorks() mockCmdResult.VerifyGet(x => x.HasErrors); - Assert.False(result.HasErrors); + result.AssertNoErrors(); } [Fact] @@ -58,7 +58,7 @@ public void TestMergeResultOfResultWorks() result.MergeResult(obj); - Assert.False(result.HasErrors); + result.AssertNoErrors(); Assert.Empty(result.Errors); diff --git a/CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs b/CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs index 4157698..81a15fd 100644 --- a/CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs +++ b/CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MatthiWare.CommandLine; -using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Usage; using MatthiWare.CommandLine.Core.Attributes; diff --git a/CommandLineParser.Tests/XUnitExtensions.cs b/CommandLineParser.Tests/XUnitExtensions.cs index 443c086..9501f19 100644 --- a/CommandLineParser.Tests/XUnitExtensions.cs +++ b/CommandLineParser.Tests/XUnitExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Linq.Expressions; +using MatthiWare.CommandLine.Abstractions.Parsing; namespace MatthiWare.CommandLine.Tests { @@ -9,5 +10,15 @@ public static LambdaExpression CreateLambda(Expression(this IParserResult result) + { + if (result == null) + throw new NullReferenceException("Parsing result was null"); + + foreach (var err in result.Errors) + throw err; + } + } } diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs index 19aede5..4bd1097 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs @@ -2,6 +2,10 @@ namespace MatthiWare.CommandLine.Abstractions.Command { + /// + /// Generic command builder + /// + /// public interface ICommandBuilder { /// @@ -36,6 +40,7 @@ public interface ICommandBuilder /// /// Configures the execution of the command /// + /// The execution action /// True or false /// ICommandBuilder OnExecuting(Action action); diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs index 6a449af..e3b1c1b 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs @@ -1,5 +1,8 @@ namespace MatthiWare.CommandLine.Abstractions.Command { + /// + /// Command builder + /// public interface ICommandConfigurationBuilder { /// @@ -22,5 +25,12 @@ public interface ICommandConfigurationBuilder /// Command name /// ICommandConfigurationBuilder Name(string name); + + /// + /// Configures if the command should auto execute + /// + /// True for automated execution, false for manual + /// + ICommandConfigurationBuilder AutoExecute(bool autoExecute); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs index 986f3fa..a9f8960 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs @@ -3,6 +3,10 @@ namespace MatthiWare.CommandLine.Abstractions.Command { + /// + /// Builder for a generic command + /// + /// public interface ICommandConfigurationBuilder : ICommandConfigurationBuilder where TSource : class @@ -19,21 +23,28 @@ public interface ICommandConfigurationBuilder /// Configures if the command is required /// /// True or false - /// + /// new ICommandConfigurationBuilder Required(bool required = true); /// /// Configures the description text for the command /// - /// True or false - /// + /// The description + /// new ICommandConfigurationBuilder Description(string description); /// /// Configures the command name /// /// Command name - /// + /// new ICommandConfigurationBuilder Name(string name); + + /// + /// Configures if the command should auto execute + /// + /// True for automated execution, false for manual + /// + new ICommandConfigurationBuilder AutoExecute(bool autoExecute); } } \ No newline at end of file diff --git a/CommandLineParser/CommandLineParser.xml b/CommandLineParser/CommandLineParser.xml index 589a1d0..2157ff4 100644 --- a/CommandLineParser/CommandLineParser.xml +++ b/CommandLineParser/CommandLineParser.xml @@ -91,6 +91,12 @@ name + + + Generic command builder + + + Configures how the command should be invoked. @@ -124,9 +130,15 @@ Configures the execution of the command + The execution action True or false + + + Command builder + + Configures if the command is required @@ -148,6 +160,19 @@ Command name + + + Configures if the command should auto execute + + True for automated execution, false for manual + + + + + Builder for a generic command + + + Configures an option in the model @@ -161,21 +186,28 @@ Configures if the command is required True or false - + Configures the description text for the command - True or false - + The description + Configures the command name Command name - + + + + + Configures if the command should auto execute + + True for automated execution, false for manual + diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index f9c12de..db93f23 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -403,5 +403,19 @@ public void RegisterCommand() m_commands.Add(command); } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.AutoExecute(bool autoExecute) + { + AutoExecute = autoExecute; + + return this; + } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.AutoExecute(bool autoExecute) + { + AutoExecute = autoExecute; + + return this; + } } } diff --git a/CommandLineParser/Core/Parsing/ParseResult'.cs b/CommandLineParser/Core/Parsing/ParseResult'.cs index 8f9785e..6025192 100644 --- a/CommandLineParser/Core/Parsing/ParseResult'.cs +++ b/CommandLineParser/Core/Parsing/ParseResult'.cs @@ -54,10 +54,20 @@ public void MergeResult(TResult result) public void ExecuteCommands() { - if (HasErrors) throw new InvalidOperationException("Parsing failed commands might be corrupted."); + if (HasErrors) throw new InvalidOperationException("Parsing failed, commands might be corrupted."); - foreach (var cmdResult in CommandResults) + ExecuteCommandsInternal(CommandResults); + } + + private void ExecuteCommandsInternal(IReadOnlyCollection commandParserResults) + { + // execute parent commands first + foreach (var cmdResult in commandParserResults) cmdResult.ExecuteCommand(); + + // execute child commands + foreach (var cmdResult in commandParserResults) + ExecuteCommandsInternal(cmdResult.SubCommands); } } }