Skip to content

CommandHandler.Create without name matching #1012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// // Copyright (c) .NET Foundation and contributors. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Binding;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;

namespace System.CommandLine.Tests.Binding
{
public partial class ModelBindingCommandHandlerTests
{
public class BindingByName
{
[Theory]
[InlineData(typeof(bool), "--value", true)]
[InlineData(typeof(bool), "--value false", false)]
[InlineData(typeof(string), "--value hello", "hello")]
[InlineData(typeof(int), "--value 123", 123)]
public async Task Option_arguments_are_bound_by_name_to_method_parameters(
Type type,
string commandLine,
object expectedValue)
{
var targetType = typeof(ClassWithMethodHavingParameter<>).MakeGenericType(type);

var handlerMethod = targetType.GetMethod(nameof(ClassWithMethodHavingParameter<int>.HandleAsync));

var handler = HandlerDescriptor.FromMethodInfo(handlerMethod)
.GetCommandHandler();

var command = new Command("the-command")
{
new Option("--value", argumentType: type)
};

var console = new TestConsole();

await handler.InvokeAsync(
new InvocationContext(command.Parse(commandLine), console));

console.Out.ToString().Should().Be(expectedValue.ToString());
}

[Theory]
[InlineData(typeof(bool), "--value", true)]
[InlineData(typeof(bool), "--value false", false)]
[InlineData(typeof(string), "--value hello", "hello")]
[InlineData(typeof(int), "--value 123", 123)]
public async Task Option_arguments_are_bound_by_name_to_the_properties_of_method_parameters(
Type type,
string commandLine,
object expectedValue)
{
var complexParameterType = typeof(ClassWithSetter<>).MakeGenericType(type);

var handlerType = typeof(ClassWithMethodHavingParameter<>).MakeGenericType(complexParameterType);

var handlerMethod = handlerType.GetMethod("HandleAsync");

var handler = HandlerDescriptor.FromMethodInfo(handlerMethod)
.GetCommandHandler();

var command = new Command("the-command")
{
new Option("--value", argumentType: type)
};

var console = new TestConsole();

await handler.InvokeAsync(
new InvocationContext(command.Parse(commandLine), console));

console.Out.ToString().Should().Be($"ClassWithSetter<{type.Name}>: {expectedValue}");
}

[Theory]
[InlineData(typeof(bool), "--value", true)]
[InlineData(typeof(bool), "--value false", false)]
[InlineData(typeof(string), "--value hello", "hello")]
[InlineData(typeof(int), "--value 123", 123)]
public async Task Option_arguments_are_bound_by_name_to_the_constructor_parameters_of_method_parameters(
Type type,
string commandLine,
object expectedValue)
{
var complexParameterType = typeof(ClassWithCtorParameter<>).MakeGenericType(type);

var handlerType = typeof(ClassWithMethodHavingParameter<>).MakeGenericType(complexParameterType);

var handlerMethod = handlerType.GetMethod("HandleAsync");

var handler = HandlerDescriptor.FromMethodInfo(handlerMethod)
.GetCommandHandler();

var command = new Command("the-command")
{
new Option("--value", argumentType: type)
};

var console = new TestConsole();

await handler.InvokeAsync(
new InvocationContext(command.Parse(commandLine), console));

console.Out.ToString().Should().Be($"ClassWithCtorParameter<{type.Name}>: {expectedValue}");
}

[Theory]
[InlineData(typeof(string), "hello", "hello")]
[InlineData(typeof(int), "123", 123)]
public async Task Command_arguments_are_bound_by_name_to_handler_method_parameters(
Type type,
string commandLine,
object expectedValue)
{
var targetType = typeof(ClassWithMethodHavingParameter<>).MakeGenericType(type);

var handlerMethod = targetType.GetMethod(nameof(ClassWithMethodHavingParameter<int>.HandleAsync));

var handler = HandlerDescriptor.FromMethodInfo(handlerMethod)
.GetCommandHandler();

var command = new Command("the-command")
{
new Argument
{
Name = "value",
ArgumentType = type
}
};

var console = new TestConsole();

await handler.InvokeAsync(
new InvocationContext(command.Parse(commandLine), console));

console.Out.ToString().Should().Be(expectedValue.ToString());
}

[Fact]
public void When_argument_type_is_more_specific_than_parameter_type_then_parameter_is_bound_correctly()
{
FileSystemInfo received = null;

var root = new RootCommand
{
new Option<DirectoryInfo>("-f")
};
root.Handler = CommandHandler.Create<FileSystemInfo>(f => received = f);
var path = $"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}";

root.Invoke($"-f {path}");

received.Should()
.BeOfType<DirectoryInfo>()
.Which
.FullName
.Should()
.Be(path);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.CommandLine.Invocation;
using System.Linq;
using FluentAssertions;
using System.Threading.Tasks;
using Xunit;

namespace System.CommandLine.Tests.Binding
{
public partial class ModelBindingCommandHandlerTests
{
public class BindingBySymbol
{
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
[InlineData(6)]
[InlineData(7)]
[InlineData(8)]
[InlineData(9)]
[InlineData(10)]
[InlineData(11)]
[InlineData(12)]
[InlineData(13)]
[InlineData(14)]
[InlineData(15)]
[InlineData(16)]
public void Binding_is_correct_for_overload_having_arity_(int arity)
{
var command = new RootCommand();
var commandLine = "";

for (var i = 1; i <= arity; i++)
{
command.AddArgument(new Argument<int>($"i{i}"));

commandLine += $" {i}";
}

var receivedValues = new List<int>();
Delegate handlerFunc = arity switch
{
1 => new Func<int, Task>(
i1 =>
Received(i1)),
2 => new Func<int, int, Task>(
(i1, i2) =>
Received(i1, i2)),
3 => new Func<int, int, int, Task>(
(i1, i2, i3) =>
Received(i1, i2, i3)),
4 => new Func<int, int, int, int, Task>(
(i1, i2, i3, i4) =>
Received(i1, i2, i3, i4)),
5 => new Func<int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5) =>
Received(i1, i2, i3, i4, i5)),
6 => new Func<int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6) =>
Received(i1, i2, i3, i4, i5, i6)),
7 => new Func<int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7) =>
Received(i1, i2, i3, i4, i5, i6, i7)),
8 => new Func<int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8)),
9 => new Func<int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9)),
10 => new Func<int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10)),
11 => new Func<int, int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11)),
12 => new Func<int, int, int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12)),
13 => new Func<int, int, int, int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13)),
14 => new Func<int, int, int, int, int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14)),
15 => new Func<int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, Task>(
(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15)),
16 => new Func<int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, Task>(

(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, i16) =>
Received(i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, i16)),

_ => throw new ArgumentOutOfRangeException()
};

// build up the method invocation
var genericMethodDef = typeof(CommandHandler)
.GetMethods()
.Where(m => m.Name == nameof(CommandHandler.Create))
.Where(m => m.IsGenericMethod /* symbols + handler Func */)
.Single(m => m.GetParameters().Length == arity + 1);

var genericParameterTypes = Enumerable.Range(1, arity)
.Select(_ => typeof(int))
.ToArray();

var createMethod = genericMethodDef.MakeGenericMethod(genericParameterTypes);

var parameters = new List<object>();

parameters.AddRange(command.Arguments);
parameters.Add(handlerFunc);

var handler = (ICommandHandler) createMethod.Invoke(null, parameters.ToArray());

command.Handler = handler;

command.Invoke(commandLine);

receivedValues.Should().BeEquivalentTo(
Enumerable.Range(1, arity),
config => config.WithStrictOrdering());

Task Received(params int[] values)
{
receivedValues.AddRange(values);
return Task.CompletedTask;
}
}
}
}
}
Loading