Skip to content

Commit 3fe4872

Browse files
authored
Add Async API (#59)
* Add async api * Add an async api for validations * Add first async api test * Add more async tests * Add more async test cases * Use Async version when parsing commands as well. * Add validation tests * Improve test coverage
1 parent 7414576 commit 3fe4872

24 files changed

+1034
-54
lines changed

CommandLineParser.Tests/Command/MultipleCommandTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ public void NonRequiredCommandShouldNotSetResultInErrorStateWhenRequiredOptionsA
1414
{
1515
var parser = new CommandLineParser();
1616

17-
parser.AddCommand<MultipleCOmmandTestsOptions>()
17+
parser.AddCommand<MultipleCommandTestsOptions>()
1818
.Name("cmd1")
1919
.Required(false)
2020
.Description("cmd1");
2121

22-
parser.AddCommand<MultipleCOmmandTestsOptions>()
22+
parser.AddCommand<MultipleCommandTestsOptions>()
2323
.Name("cmd2")
2424
.Required(false)
2525
.Description("cmd2");
@@ -29,7 +29,7 @@ public void NonRequiredCommandShouldNotSetResultInErrorStateWhenRequiredOptionsA
2929
result.AssertNoErrors();
3030
}
3131

32-
private class MultipleCOmmandTestsOptions
32+
private class MultipleCommandTestsOptions
3333
{
3434
[Required, Name("x", "bla"), Description("some description")]
3535
public int Option { get; set; }

CommandLineParser.Tests/Command/SubCommandTests.cs

+55
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Linq;
88
using System.Threading;
9+
using System.Threading.Tasks;
910
using Xunit;
1011

1112
namespace MatthiWare.CommandLine.Tests.Command
@@ -42,6 +43,36 @@ public void TestSubCommandWorksCorrectlyInModel(bool autoExecute, string bla, in
4243
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.True);
4344
}
4445

46+
[Theory]
47+
[InlineData(true, "something", 15, -1)]
48+
[InlineData(false, "something", 15, -1)]
49+
[InlineData(true, "", 15, -1)]
50+
public async Task TestSubCommandWorksCorrectlyInModelAsync(bool autoExecute, string bla, int i, int n)
51+
{
52+
var lock1 = new ManualResetEventSlim();
53+
var lock2 = new ManualResetEventSlim();
54+
55+
var containerResolver = new CustomInstantiator(lock1, lock2, autoExecute, bla, i, n);
56+
57+
var parser = new CommandLineParser<MainModel>(containerResolver);
58+
59+
var result = await parser.ParseAsync(new[] { "main", "-b", bla, "sub", "-i", i.ToString(), "-n", n.ToString() });
60+
61+
result.AssertNoErrors();
62+
63+
if (!autoExecute)
64+
{
65+
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.False);
66+
67+
result.ExecuteCommands();
68+
}
69+
70+
Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time.");
71+
Assert.True(lock2.Wait(1000), "SubCommand didn't execute in time.");
72+
73+
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.True);
74+
}
75+
4576
private class CustomInstantiator : DefaultContainerResolver
4677
{
4778
private readonly ManualResetEventSlim lock1;
@@ -108,6 +139,18 @@ public override void OnExecute(MainModel options, SubModel commandOptions)
108139

109140
locker.Set();
110141
}
142+
143+
public override Task OnExecuteAsync(MainModel options, SubModel commandOptions, CancellationToken cancellationToken)
144+
{
145+
base.OnExecuteAsync(options, commandOptions, cancellationToken);
146+
147+
Assert.Equal(bla, options.Bla);
148+
Assert.Equal(i, commandOptions.Item);
149+
150+
locker.Set();
151+
152+
return Task.CompletedTask;
153+
}
111154
}
112155

113156
public class SubCommand : Command<MainModel, SubSubModel>
@@ -144,6 +187,18 @@ public override void OnExecute(MainModel options, SubSubModel commandOptions)
144187

145188
locker.Set();
146189
}
190+
191+
public override Task OnExecuteAsync(MainModel options, SubSubModel commandOptions, CancellationToken cancellationToken)
192+
{
193+
base.OnExecuteAsync(options, commandOptions, cancellationToken);
194+
195+
Assert.Equal(bla, options.Bla);
196+
Assert.Equal(n, commandOptions.Nothing);
197+
198+
locker.Set();
199+
200+
return Task.CompletedTask;
201+
}
147202
}
148203

149204
public class MainModel

CommandLineParser.Tests/CommandLineParserTests.cs

+136
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Linq;
99
using System.Threading;
10+
using System.Threading.Tasks;
1011
using Xunit;
1112

1213
namespace MatthiWare.CommandLine.Tests
@@ -84,6 +85,36 @@ public void CommandLineParserUsesContainerCorrectly(bool generic)
8485
containerMock.VerifyAll();
8586
}
8687

88+
[Theory]
89+
[InlineData(true)]
90+
[InlineData(false)]
91+
public async Task CommandLineParserUsesContainerCorrectlyAsync(bool generic)
92+
{
93+
var commandMock = new Mock<MyCommand>();
94+
commandMock.Setup(
95+
c => c.OnConfigure(It.IsAny<ICommandConfigurationBuilder<object>>()))
96+
.CallBase().Verifiable("OnConfigure not called");
97+
98+
commandMock.Setup(c => c.OnExecuteAsync(It.IsAny<object>(), It.IsAny<object>(), It.IsAny<CancellationToken>())).Verifiable("OnExecute not called");
99+
100+
var containerMock = new Mock<IContainerResolver>();
101+
containerMock.Setup(c => c.Resolve<MyCommand>()).Returns(commandMock.Object).Verifiable();
102+
103+
var parser = new CommandLineParser<object>(containerMock.Object);
104+
105+
if (generic)
106+
parser.RegisterCommand<MyCommand, object>();
107+
else
108+
parser.RegisterCommand(typeof(MyCommand), typeof(object));
109+
110+
var result = await parser.ParseAsync(new[] { "app.exe", "my" });
111+
112+
result.AssertNoErrors();
113+
114+
commandMock.VerifyAll();
115+
containerMock.VerifyAll();
116+
}
117+
87118
[Fact]
88119
public void CommandLinerParserPassesContainerCorreclty()
89120
{
@@ -150,6 +181,30 @@ public void AutoExecuteCommandsWithExceptionDoesntCrashTheParser()
150181
Assert.Equal(ex, result.Errors.First());
151182
}
152183

184+
[Fact]
185+
public async Task AutoExecuteCommandsWithExceptionDoesntCrashTheParserAsync()
186+
{
187+
var parser = new CommandLineParser();
188+
189+
var ex = new Exception("uh-oh");
190+
191+
parser.AddCommand()
192+
.Name("test")
193+
.InvokeCommand(true)
194+
.Required(true)
195+
.OnExecutingAsync(async (_, __) =>
196+
{
197+
await Task.Delay(1);
198+
throw ex;
199+
});
200+
201+
var result = await parser.ParseAsync(new[] { "test" });
202+
203+
Assert.True(result.HasErrors);
204+
205+
Assert.Equal(ex, result.Errors.First());
206+
}
207+
153208
[Fact]
154209
public void CommandLineParserUsesArgumentFactoryCorrectly()
155210
{
@@ -347,6 +402,49 @@ public void ParseWithCommandTests()
347402
Assert.True(wait.WaitOne(2000));
348403
}
349404

405+
[Fact]
406+
public async Task ParseWithCommandTestsAsync()
407+
{
408+
var wait = new ManualResetEvent(false);
409+
410+
var parser = new CommandLineParser<Options>();
411+
412+
parser.Configure(opt => opt.Option1)
413+
.Name("o")
414+
.Default("Default message")
415+
.Required();
416+
417+
var addCmd = parser.AddCommand<AddOption>()
418+
.Name("add")
419+
.OnExecutingAsync(async (opt, cmdOpt, ctx) =>
420+
{
421+
await Task.Delay(100);
422+
423+
Assert.Equal("test", opt.Option1);
424+
Assert.Equal("my message", cmdOpt.Message);
425+
426+
await Task.Delay(100);
427+
428+
wait.Set();
429+
430+
await Task.Delay(100);
431+
});
432+
433+
addCmd.Configure(opt => opt.Message)
434+
.Name("m", "message")
435+
.Required();
436+
437+
var parsed = await parser.ParseAsync(new string[] { "app.exe", "-o", "test", "add", "-m=my message" });
438+
439+
parsed.AssertNoErrors();
440+
441+
Assert.Equal("test", parsed.Result.Option1);
442+
443+
parsed.ExecuteCommands();
444+
445+
Assert.True(wait.WaitOne(2000));
446+
}
447+
350448
[Theory]
351449
[InlineData(new[] { "app.exe", "add", "-m", "message2", "-m", "message1" }, "message1", "message2")]
352450
[InlineData(new[] { "app.exe", "-m", "message1", "add", "-m", "message2" }, "message1", "message2")]
@@ -383,6 +481,44 @@ public void ParseCommandTests(string[] args, string result1, string result2)
383481
Assert.True(wait.WaitOne(2000));
384482
}
385483

484+
[Theory]
485+
[InlineData(new[] { "app.exe", "add", "-m", "message2", "-m", "message1" }, "message1", "message2")]
486+
[InlineData(new[] { "app.exe", "-m", "message1", "add", "-m", "message2" }, "message1", "message2")]
487+
[InlineData(new[] { "add", "-m", "message2", "-m", "message1" }, "message1", "message2")]
488+
[InlineData(new[] { "-m", "message1", "add", "-m", "message2" }, "message1", "message2")]
489+
public async Task ParseCommandTestsAsync(string[] args, string result1, string result2)
490+
{
491+
var parser = new CommandLineParser<AddOption>();
492+
var wait = new ManualResetEvent(false);
493+
494+
parser.AddCommand<AddOption>()
495+
.Name("add")
496+
.Required()
497+
.OnExecutingAsync(async (opt1, opt2, ctx) =>
498+
{
499+
await Task.Delay(100);
500+
501+
wait.Set();
502+
503+
Assert.Equal(result2, opt2.Message);
504+
})
505+
.Configure(c => c.Message)
506+
.Name("m", "message")
507+
.Required();
508+
509+
parser.Configure(opt => opt.Message)
510+
.Name("m", "message")
511+
.Required();
512+
513+
var result = await parser.ParseAsync(args);
514+
515+
result.AssertNoErrors();
516+
517+
Assert.Equal(result1, result.Result.Message);
518+
519+
Assert.True(wait.WaitOne(2000));
520+
}
521+
386522
[Theory]
387523
[InlineData(new string[] { "-x", "" }, true)]
388524
[InlineData(new string[] { "-x" }, true)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using MatthiWare.CommandLine.Abstractions;
2+
using MatthiWare.CommandLine.Core;
3+
using Moq;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using Xunit;
9+
10+
namespace MatthiWare.CommandLine.Tests.Core
11+
{
12+
public class TypedInstanceCacheTests
13+
{
14+
[Theory]
15+
[InlineData(true)]
16+
[InlineData(false)]
17+
public void AddingItemsDoesNotTriggerResolve(bool doubleAdd)
18+
{
19+
var containerMock = new Mock<IContainerResolver>();
20+
21+
var cache = new TypedInstanceCache<MyType>(containerMock.Object);
22+
23+
var type1 = new MyType();
24+
var type2 = new MyType();
25+
26+
cache.Add(type1);
27+
28+
var result = cache.Get();
29+
30+
Assert.Equal(type1, result.First());
31+
32+
if (doubleAdd)
33+
{
34+
cache.Add(type2);
35+
36+
result = cache.Get();
37+
38+
Assert.Equal(type2, result.First());
39+
}
40+
41+
Assert.True(result.Count == 1);
42+
43+
containerMock.Verify(c => c.Resolve(It.Is<Type>(t => t == typeof(MyType))), Times.Never());
44+
}
45+
46+
[Fact]
47+
public void AddingItemTypeDoesTriggerResolve()
48+
{
49+
var containerMock = new Mock<IContainerResolver>();
50+
51+
var cache = new TypedInstanceCache<MyType>(containerMock.Object);
52+
53+
var type1 = new MyType();
54+
55+
containerMock.Setup(c => c.Resolve(It.Is<Type>(t => t == typeof(MyType)))).Returns(type1);
56+
57+
cache.Add(typeof(MyType));
58+
59+
var result = cache.Get();
60+
61+
Assert.Equal(type1, result.First());
62+
63+
Assert.True(result.Count == 1);
64+
65+
containerMock.Verify(c => c.Resolve(It.Is<Type>(t => t == typeof(MyType))), Times.Once());
66+
}
67+
68+
private class MyType { }
69+
}
70+
}

0 commit comments

Comments
 (0)