Skip to content

Explicit parameter binding #1018

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

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fa703a9
Sync to upstream
KathleenDollard May 18, 2020
1dd2356
Merge remote-tracking branch 'upstream/master'
KathleenDollard May 31, 2020
6ed6bd0
Merge remote-tracking branch 'upstream/master'
KathleenDollard Jun 10, 2020
bf2c2e9
Merge remote-tracking branch 'upstream/master'
KathleenDollard Jun 27, 2020
c6b6fb9
Merge remote-tracking branch 'upstream/master'
KathleenDollard Jul 9, 2020
627c633
Merge remote-tracking branch 'upstream/master'
KathleenDollard Jul 22, 2020
2e1d614
Merge remote-tracking branch 'upstream/master'
KathleenDollard Jul 29, 2020
0b64118
Prior to GetValue refactoring
KathleenDollard Aug 5, 2020
a0b5394
Merge remote-tracking branch 'upstream/master'
KathleenDollard Aug 5, 2020
12f4171
Merge branch 'master' into explicit-parameter-binding-3
KathleenDollard Aug 5, 2020
4294e94
Refactor GetBoundValue
KathleenDollard Aug 5, 2020
87b5bda
WIP
KathleenDollard Aug 7, 2020
63faa9b
Sort of works. Prior to Lazy redeisgn
KathleenDollard Aug 8, 2020
80487a9
All tests passing!!! Needs refactor
KathleenDollard Aug 9, 2020
cb6791c
Small refactoring and removing old code
KathleenDollard Aug 9, 2020
582350e
Merge remote-tracking branch 'upstream/master'
KathleenDollard Aug 9, 2020
05a395b
Cleanup adn remove dead code
KathleenDollard Aug 13, 2020
46f0117
Merge remote-tracking branch 'upstream/master'
KathleenDollard Aug 13, 2020
15ca186
Merge branch 'master' into explicit-binding-4
KathleenDollard Aug 13, 2020
1fd7908
Updated tests. 3 questions: search // ??
KathleenDollard Aug 14, 2020
1ddbc18
Cleanup and respond to comments
KathleenDollard Aug 15, 2020
fd68742
Cleanup
KathleenDollard Aug 17, 2020
fae829e
Fixed warnings that were breaking Arcade, uncommented tests and fixed
KathleenDollard Aug 21, 2020
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
143 changes: 143 additions & 0 deletions src/System.CommandLine.Tests/Binding/ModelBinderConstructorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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.Binding;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Xunit;
using common = System.CommandLine.Tests.Binding.ModelBindingCommandHandlerTests;

namespace System.CommandLine.Tests.Binding
{
public class ModelBinderConstructorTests
{

[Theory]
//[InlineData(typeof(ClassWithCtorParameter<int>))]
[InlineData(typeof(ClassWithSetter<int>))]
//[InlineData(typeof(ClassWithCtorParameter<string>))]
[InlineData(typeof(ClassWithSetter<string>))]
[InlineData(typeof(FileInfo))]
[InlineData(typeof(FileInfo[]))]
[InlineData(typeof(string[]))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(int[]))]
[InlineData(typeof(List<int>))]
public async Task Handler_constructor_receives_option_arguments_bound_to_the_specified_type(
Type type)
{
var testCase = common.BindingCases[type];
ICommandHandler handler = CommandHandler.Create(
MakeGenericType(typeof(TesttCommandHandler<>), testCase.ParameterType)
.GetMethod(nameof(TesttCommandHandler<int>.Invoke)));
Command command = GetSingleArgumentCommand(testCase);
command.Handler = handler;

var parseResult = command.Parse($"--value {testCase.CommandLine}");
var invocationContext = new InvocationContext(parseResult);
await handler.InvokeAsync(invocationContext);

var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;
boundValue.Should().BeAssignableTo(testCase.ParameterType);
testCase.AssertBoundValue(boundValue);
}

[Theory]
[InlineData(typeof(ClassWithCtorParameter<int>))]
[InlineData(typeof(ClassWithSetter<int>))]
[InlineData(typeof(ClassWithCtorParameter<string>))]
[InlineData(typeof(ClassWithSetter<string>))]
[InlineData(typeof(FileInfo))]
[InlineData(typeof(FileInfo[]))]
[InlineData(typeof(string[]))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(int[]))]
[InlineData(typeof(List<int>))]
public void Model_constructor_receives_option_arguments_bound_to_the_specified_type(
Type type)
{
var testCase = common.BindingCases[type];
var typeToCreate = MakeGenericType(typeof(TestModel<>), testCase.ParameterType);
Command command = GetSingleArgumentCommand(testCase);

var binder = new ModelBinder(typeToCreate);
var commandLine = $"--value {testCase.CommandLine}";
var bindingContext = new BindingContext(command.Parse(commandLine));
var instance = binder.CreateInstance(bindingContext) as TestModelBase;

instance.Value.Should().BeAssignableTo(testCase.ParameterType);
testCase.AssertBoundValue(instance.Value);
}

private static Command GetSingleArgumentCommand(BindingTestCase testCase)
{
return new Command("command")
{
new Option("--value")
{
Argument = new Argument
{
ArgumentType = testCase.ParameterType
}
}
};
}

private MethodInfo MakeGenericMethod(Type type, string methodName, params Type[] typeParameters)
=> type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance)
.MakeGenericMethod(typeParameters);

private Type MakeGenericType(Type openType, params Type[] typeParameters)
=> openType.MakeGenericType(typeParameters);

private static void CaptureMethod<T>(T value, InvocationContext invocationContext)
{
invocationContext.InvocationResult = new BoundValueCapturer(value);
}

private class BoundValueCapturer : IInvocationResult
{
public BoundValueCapturer(object boundValue)
{
BoundValue = boundValue;
}

public object BoundValue { get; }

public void Apply(InvocationContext context)
{
}
}

private class TesttCommandHandler<T>
{
public TesttCommandHandler(T value, InvocationContext invocationContext)
{
invocationContext.InvocationResult = new BoundValueCapturer(value);
}

public void Invoke() { }
}

private class TestModelBase
{
public object Value { get; protected set; }

}
private class TestModel<T> : TestModelBase
{
public TestModel(T value)
{
Value = value;
}

}
}
}
35 changes: 34 additions & 1 deletion src/System.CommandLine.Tests/Binding/ModelBinderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,39 @@ public void Command_arguments_are_bound_by_name_to_constructor_parameters(
valueReceivedValue.Should().Be(expectedValue);
}

[Theory]
[InlineData(typeof(FileInfo), "MyFile.cs")]
public void Command_arguments_are_bound_by_name_to_complex_constructor_parameters(
Type type,
string commandLine)
{
var targetType = typeof(ClassWithCtorParameter<>).MakeGenericType(type);
var binder = new ModelBinder(targetType);

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

var bindingContext = new BindingContext(command.Parse(commandLine));

var instance = binder.CreateInstance(bindingContext);

object valueReceivedValue = ((dynamic)instance).Value;
var expectedValue = new FileInfo(commandLine);

valueReceivedValue.Should().BeOfType<FileInfo>();
var fileInfoValue = valueReceivedValue as FileInfo;
fileInfoValue.FullName.Should().Be(expectedValue.FullName);
// The following fails when it attempts to compare the Length of the file. I have
// no idea why this previously worked.
//valueReceivedValue.Should().BeEquivalentTo(expectedValue);
}

[Fact]
public void Explicitly_configured_default_values_can_be_bound_by_name_to_constructor_parameters()
{
Expand Down Expand Up @@ -334,7 +367,7 @@ public void Values_from_parent_command_arguments_are_bound_by_name_by_default()

instance.IntOption.Should().Be(123);
}

[Fact]
public void Default_values_from_parent_command_arguments_are_bound_by_name_by_default()
{
Expand Down
Loading