Skip to content

Commit db332c6

Browse files
authored
fix #1199 (#1342)
1 parent b751c2e commit db332c6

File tree

6 files changed

+90
-52
lines changed

6 files changed

+90
-52
lines changed

src/System.CommandLine.Tests/Binding/BindingTestCase.cs

+28-8
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,32 @@ namespace System.CommandLine.Tests.Binding
66
public class BindingTestCase
77
{
88
private readonly Action<object> _assertBoundValue;
9-
9+
10+
private BindingTestCase(
11+
string commandLineToken,
12+
Type parameterType,
13+
Action<object> assertBoundValue,
14+
string variationName) : this(
15+
new[] { commandLineToken },
16+
parameterType,
17+
assertBoundValue,
18+
variationName)
19+
{
20+
}
21+
1022
private BindingTestCase(
11-
string commandLine,
23+
string[] commandLineTokens,
1224
Type parameterType,
1325
Action<object> assertBoundValue,
1426
string variationName)
1527
{
1628
_assertBoundValue = assertBoundValue;
1729
VariationName = variationName;
18-
CommandLine = commandLine;
30+
CommandLineTokens = commandLineTokens;
1931
ParameterType = parameterType;
2032
}
2133

22-
public string CommandLine { get; }
34+
public string[] CommandLineTokens { get; }
2335

2436
public Type ParameterType { get; }
2537

@@ -31,11 +43,19 @@ public void AssertBoundValue(object value)
3143
}
3244

3345
public static BindingTestCase Create<T>(
34-
string commandLine,
35-
Action<T> assertBoundValue,
46+
string commandLineToken,
47+
Action<T> assertBoundValue,
48+
string variationName = null) =>
49+
new(commandLineToken,
50+
typeof(T),
51+
o => assertBoundValue((T) o),
52+
variationName);
53+
54+
public static BindingTestCase Create<T>(
55+
string[] commandLineTokens,
56+
Action<T> assertBoundValue,
3657
string variationName = null) =>
37-
new BindingTestCase(
38-
commandLine,
58+
new(commandLineTokens,
3959
typeof(T),
4060
o => assertBoundValue((T) o),
4161
variationName);

src/System.CommandLine.Tests/Binding/ModelBindingCommandHandlerTests.cs

+40-34
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ public async Task Handler_method_receives_option_arguments_bound_to_the_specifie
301301
};
302302
command.Handler = handler;
303303

304-
var parseResult = command.Parse($"--value {testCase.CommandLine}");
304+
var commandLine = string.Join(" ", testCase.CommandLineTokens.Select(t => $"--value {t}"));
305+
var parseResult = command.Parse(commandLine);
305306

306307
var invocationContext = new InvocationContext(parseResult);
307308

@@ -350,11 +351,11 @@ public async Task When_binding_fails_due_to_parameter_naming_mismatch_then_handl
350351
public async Task Handler_method_receives_command_arguments_bound_to_the_specified_type(
351352
Type type)
352353
{
353-
var c = BindingCases[type];
354+
var testCase = BindingCases[type];
354355

355356
var captureMethod = GetType()
356357
.GetMethod(nameof(CaptureMethod), BindingFlags.NonPublic | BindingFlags.Static)
357-
.MakeGenericMethod(c.ParameterType);
358+
.MakeGenericMethod(testCase.ParameterType);
358359

359360
var handler = CommandHandler.Create(captureMethod);
360361

@@ -364,22 +365,23 @@ public async Task Handler_method_receives_command_arguments_bound_to_the_specifi
364365
new Argument
365366
{
366367
Name = "value",
367-
ArgumentType = c.ParameterType
368+
ArgumentType = testCase.ParameterType
368369
}
369370
};
370371
command.Handler = handler;
371372

372-
var parseResult = command.Parse(c.CommandLine);
373+
var commandLine = string.Join(" ", testCase.CommandLineTokens);
374+
var parseResult = command.Parse(commandLine);
373375

374376
var invocationContext = new InvocationContext(parseResult);
375377

376378
await handler.InvokeAsync(invocationContext);
377379

378380
var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;
379381

380-
boundValue.Should().BeOfType(c.ParameterType);
382+
boundValue.Should().BeOfType(testCase.ParameterType);
381383

382-
c.AssertBoundValue(boundValue);
384+
testCase.AssertBoundValue(boundValue);
383385
}
384386

385387
[Theory]
@@ -398,19 +400,19 @@ public async Task Handler_method_receives_command_arguments_bound_to_the_specifi
398400
public async Task Handler_method_receives_command_arguments_explicitly_bound_to_the_specified_type(
399401
Type type)
400402
{
401-
var c = BindingCases[type];
403+
var testCase = BindingCases[type];
402404

403405
var captureMethod = GetType()
404406
.GetMethod(nameof(CaptureMethod), BindingFlags.NonPublic | BindingFlags.Static)
405-
.MakeGenericMethod(c.ParameterType);
407+
.MakeGenericMethod(testCase.ParameterType);
406408
var parameter = captureMethod.GetParameters()[0];
407409

408410
var handler = CommandHandler.Create(captureMethod);
409411

410412
var argument = new Argument
411413
{
412414
Name = "value",
413-
ArgumentType = c.ParameterType
415+
ArgumentType = testCase.ParameterType
414416
};
415417

416418
var command = new Command(
@@ -425,17 +427,18 @@ public async Task Handler_method_receives_command_arguments_explicitly_bound_to_
425427
bindingHandler.BindParameter(parameter, argument);
426428
command.Handler = handler;
427429

428-
var parseResult = command.Parse(c.CommandLine);
430+
var commandLine = string.Join(" ", testCase.CommandLineTokens);
431+
var parseResult = command.Parse(commandLine);
429432

430433
var invocationContext = new InvocationContext(parseResult);
431434

432435
await handler.InvokeAsync(invocationContext);
433436

434437
var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;
435438

436-
boundValue.Should().BeOfType(c.ParameterType);
439+
boundValue.Should().BeOfType(testCase.ParameterType);
437440

438-
c.AssertBoundValue(boundValue);
441+
testCase.AssertBoundValue(boundValue);
439442
}
440443

441444
[Theory]
@@ -455,16 +458,16 @@ public async Task Handler_method_receives_command_arguments_explicitly_bound_to_
455458
public async Task Handler_method_receive_option_arguments_explicitly_bound_to_the_specified_type(
456459
Type type)
457460
{
458-
var c = BindingCases[type];
461+
var testCase = BindingCases[type];
459462

460463
var captureMethod = GetType()
461464
.GetMethod(nameof(CaptureMethod), BindingFlags.NonPublic | BindingFlags.Static)
462-
.MakeGenericMethod(c.ParameterType);
465+
.MakeGenericMethod(testCase.ParameterType);
463466
var parameter = captureMethod.GetParameters()[0];
464467

465468
var handler = CommandHandler.Create(captureMethod);
466469

467-
var option = new Option("--value", argumentType: c.ParameterType);
470+
var option = new Option("--value", argumentType: testCase.ParameterType);
468471

469472
var command = new Command("command")
470473
{
@@ -477,7 +480,7 @@ public async Task Handler_method_receive_option_arguments_explicitly_bound_to_th
477480
bindingHandler.BindParameter(parameter, option);
478481
command.Handler = handler;
479482

480-
var commandLine = $"--value {c.CommandLine}";
483+
var commandLine = string.Join(" ", testCase.CommandLineTokens.Select(t => $"--value {t}"));
481484
var parseResult = command.Parse(commandLine);
482485

483486
var invocationContext = new InvocationContext(parseResult);
@@ -486,9 +489,9 @@ public async Task Handler_method_receive_option_arguments_explicitly_bound_to_th
486489

487490
var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;
488491

489-
boundValue.Should().BeOfType(c.ParameterType);
492+
boundValue.Should().BeOfType(testCase.ParameterType);
490493

491-
c.AssertBoundValue(boundValue);
494+
testCase.AssertBoundValue(boundValue);
492495
}
493496

494497
private static void CaptureMethod<T>(T value, InvocationContext invocationContext)
@@ -550,7 +553,10 @@ public void Apply(InvocationContext context)
550553
.Be(Path.Combine(ExistingDirectory(), "file1.txt"))),
551554

552555
BindingTestCase.Create<FileInfo[]>(
553-
$"{Path.Combine(ExistingDirectory(), "file1.txt")} {Path.Combine(ExistingDirectory(), "file2.txt")}",
556+
new[] {
557+
Path.Combine(ExistingDirectory(), "file1.txt"),
558+
Path.Combine(ExistingDirectory(), "file2.txt")
559+
} ,
554560
o => o.Select(f => f.FullName)
555561
.Should()
556562
.BeEquivalentTo(new[]
@@ -569,17 +575,17 @@ public void Apply(InvocationContext context)
569575
.Be(ExistingDirectory())),
570576

571577
BindingTestCase.Create<DirectoryInfo[]>(
572-
$"{ExistingDirectory()} {ExistingDirectory()}",
578+
new[] { ExistingDirectory(), ExistingDirectory() },
573579
fsi => fsi.Should()
574-
.BeAssignableTo<IEnumerable<DirectoryInfo>>()
575-
.Which
576-
.Select(d => d.FullName)
577-
.Should()
578-
.BeEquivalentTo(new[]
579-
{
580-
ExistingDirectory(),
581-
ExistingDirectory()
582-
})),
580+
.BeAssignableTo<IEnumerable<DirectoryInfo>>()
581+
.Which
582+
.Select(d => d.FullName)
583+
.Should()
584+
.BeEquivalentTo(new[]
585+
{
586+
ExistingDirectory(),
587+
ExistingDirectory()
588+
})),
583589

584590
BindingTestCase.Create<FileSystemInfo>(
585591
ExistingFile(),
@@ -633,19 +639,19 @@ public void Apply(InvocationContext context)
633639
variationName: nameof(NonexistentPathWithoutTrailingSlash)),
634640

635641
BindingTestCase.Create<string[]>(
636-
"one two",
642+
new[] { "one", "two" },
637643
o => o.Should().BeEquivalentTo(new[] { "one", "two" })),
638644

639645
BindingTestCase.Create<List<string>>(
640-
"one two",
646+
new[] { "one", "two" },
641647
o => o.Should().BeEquivalentTo(new List<string> { "one", "two" })),
642648

643649
BindingTestCase.Create<int[]>(
644-
"1 2",
650+
new[] { "1", "2" },
645651
o => o.Should().BeEquivalentTo(new[] { 1, 2 })),
646652

647653
BindingTestCase.Create<List<int>>(
648-
"1 2",
654+
new[] { "1", "2" },
649655
o => o.Should().BeEquivalentTo(new List<int> { 1, 2 }))
650656
};
651657

src/System.CommandLine.Tests/Binding/TypeConversionTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ public void Max_arity_greater_than_1_converts_to_enumerable_types(
559559
option
560560
};
561561

562-
var result = command.Parse("--items one two three");
562+
var result = command.Parse("--items one --items two --items three");
563563

564564
result.Errors.Should().BeEmpty();
565565
result.FindResultFor(option).GetValueOrDefault().Should().BeAssignableTo(argumentType);

src/System.CommandLine.Tests/ParserTests.cs

+18-6
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,11 @@ public void Options_can_be_specified_multiple_times_and_their_arguments_are_coll
453453
[Fact]
454454
public void When_a_Parser_root_option_is_not_respecified_but_limit_is_not_reached_then_the_following_token_is_used_as_value()
455455
{
456-
var animalsOption = new Option(new[] { "-a", "--animals" }) { Arity = ArgumentArity.ZeroOrMore };
456+
var animalsOption = new Option(new[] { "-a", "--animals" })
457+
{
458+
AllowMultipleArgumentsPerToken = true,
459+
Arity = ArgumentArity.ZeroOrMore
460+
};
457461
var vegetablesOption = new Option(new[] { "-v", "--vegetables" }) { Arity = ArgumentArity.ZeroOrMore };
458462

459463
var parser = new Parser(
@@ -485,7 +489,11 @@ public void When_a_Parser_root_option_is_not_respecified_but_limit_is_not_reache
485489
[Fact]
486490
public void When_a_Parser_root_option_is_not_respecified_and_limit_is_reached_then_the_following_token_is_unmatched()
487491
{
488-
var animalsOption = new Option(new[] { "-a", "--animals" }) { Arity = ArgumentArity.ZeroOrOne };
492+
var animalsOption = new Option(new[] { "-a", "--animals" })
493+
{
494+
AllowMultipleArgumentsPerToken = true,
495+
Arity = ArgumentArity.ZeroOrOne
496+
};
489497
var vegetablesOption = new Option(new[] { "-v", "--vegetables" }) { Arity = ArgumentArity.ZeroOrMore };
490498

491499
var parser = new Parser(
@@ -515,7 +523,11 @@ public void When_a_Parser_root_option_is_not_respecified_and_limit_is_reached_th
515523
[Fact]
516524
public void When_an_option_is_not_respecified_but_limit_is_not_reached_then_the_following_token_is_considered_as_value()
517525
{
518-
var animalsOption = new Option(new[] { "-a", "--animals" }) { Arity = ArgumentArity.ZeroOrMore };
526+
var animalsOption = new Option(new[] { "-a", "--animals" })
527+
{
528+
AllowMultipleArgumentsPerToken = true,
529+
Arity = ArgumentArity.ZeroOrMore
530+
};
519531
var vegetablesOption = new Option(new[] { "-v", "--vegetables" }) { Arity = ArgumentArity.ZeroOrMore };
520532
var parser = new Parser(
521533
new Command("the-command")
@@ -1513,7 +1525,7 @@ public void Option_argument_arity_can_be_a_fixed_value_greater_than_1()
15131525
option
15141526
};
15151527

1516-
command.Parse("-x 1 2 3")
1528+
command.Parse("-x 1 -x 2 -x 3")
15171529
.FindResultFor(option)
15181530
.Tokens
15191531
.Should()
@@ -1533,15 +1545,15 @@ public void Option_argument_arity_can_be_a_range_with_a_lower_bound_greater_than
15331545
option
15341546
};
15351547

1536-
command.Parse("-x 1 2 3")
1548+
command.Parse("-x 1 -x 2 -x 3")
15371549
.FindResultFor(option)
15381550
.Tokens
15391551
.Should()
15401552
.BeEquivalentTo(
15411553
new Token("1", TokenType.Argument),
15421554
new Token("2", TokenType.Argument),
15431555
new Token("3", TokenType.Argument));
1544-
command.Parse("-x 1 2 3 4 5")
1556+
command.Parse("-x 1 -x 2 -x 3 -x 4 -x 5")
15451557
.FindResultFor(option)
15461558
.Tokens
15471559
.Should()

src/System.CommandLine.Tests/ParsingValidationTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ public void LegalFilePathsOnly_accepts_option_arguments_containing_valid_path_ch
418418
var validPathName = Directory.GetCurrentDirectory();
419419
var validNonExistingFileName = Path.Combine(validPathName, Guid.NewGuid().ToString());
420420

421-
var result = command.Parse($"the-command -x {validPathName} {validNonExistingFileName}");
421+
var result = command.Parse($"the-command -x {validPathName} -x {validNonExistingFileName}");
422422

423423
result.Errors.Should().BeEmpty();
424424
}
@@ -489,7 +489,7 @@ public void LegalFileNamesOnly_accepts_option_arguments_containing_valid_file_na
489489
var validFileName = Path.GetFileName(Directory.GetCurrentDirectory());
490490
var validNonExistingFileName = Guid.NewGuid().ToString();
491491

492-
var result = command.Parse($"the-command -x {validFileName} {validNonExistingFileName}");
492+
var result = command.Parse($"the-command -x {validFileName} -x {validNonExistingFileName}");
493493

494494
result.Errors.Should().BeEmpty();
495495
}

src/System.CommandLine/Option.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public void SetDefaultValueFactory(Func<object?> getDefaultValue) =>
229229
IArgument IOption.Argument => Argument;
230230

231231
/// <inheritdoc/>
232-
public bool AllowMultipleArgumentsPerToken { get; set; } = true;
232+
public bool AllowMultipleArgumentsPerToken { get; set; }
233233

234234
/// <summary>
235235
/// Indicates whether the option is required when its parent command is invoked.

0 commit comments

Comments
 (0)