This commit is contained in:
Tyrrrz
2023-05-16 01:36:03 +03:00
parent 01f29a5375
commit e52781c25a
61 changed files with 1264 additions and 1345 deletions

View File

@@ -16,7 +16,7 @@ public class ApplicationSpecs : SpecsBase
}
[Fact]
public async Task Application_can_be_created_with_minimal_configuration()
public async Task I_can_create_an_application_with_the_default_configuration()
{
// Act
var app = new CliApplicationBuilder()
@@ -34,7 +34,7 @@ public class ApplicationSpecs : SpecsBase
}
[Fact]
public async Task Application_can_be_created_with_a_fully_customized_configuration()
public async Task I_can_create_an_application_with_a_custom_configuration()
{
// Act
var app = new CliApplicationBuilder()
@@ -63,7 +63,7 @@ public class ApplicationSpecs : SpecsBase
}
[Fact]
public async Task Application_configuration_fails_if_an_invalid_command_is_registered()
public async Task I_can_try_to_add_an_invalid_command_to_the_application_and_get_an_error()
{
// Act
var app = new CliApplicationBuilder()
@@ -76,10 +76,10 @@ public class ApplicationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("not a valid command");
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CliFx.Tests.Utils;
using CliWrap;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
@@ -15,8 +18,36 @@ public class CancellationSpecs : SpecsBase
{
}
[Fact(Timeout = 15000)]
public async Task I_can_configure_the_command_to_listen_to_the_interrupt_signal()
{
// Arrange
var stdOutBuffer = new StringBuilder();
var command = Cli.Wrap("dotnet")
.WithArguments(a => a
.Add(Dummy.Program.Location)
.Add("cancel-test")
) | stdOutBuffer;
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(0.2));
// Act & assert
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
await command.ExecuteAsync(
// Forceful cancellation (not required because we have a timeout)
CancellationToken.None,
// Graceful cancellation
cts.Token
)
);
stdOutBuffer.ToString().Trim().Should().Be("Cancelled");
}
[Fact]
public async Task Command_can_receive_a_cancellation_signal_from_the_console()
public async Task I_can_configure_the_command_to_listen_to_the_interrupt_signal_when_running_in_isolation()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -51,18 +82,18 @@ public class CancellationSpecs : SpecsBase
.UseConsole(FakeConsole)
.Build();
// Act
FakeConsole.RequestCancellation(TimeSpan.FromSeconds(0.2));
// Act
var exitCode = await application.RunAsync(
Array.Empty<string>(),
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().NotBe(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("Cancelled");
}
}

View File

@@ -22,10 +22,9 @@ public class ConsoleSpecs : SpecsBase
}
[Fact(Timeout = 15000)]
public async Task Real_console_maps_directly_to_system_console()
public async Task I_can_run_the_application_with_the_default_console_implementation_to_interact_with_the_system_console()
{
// Can't verify our own console output, so using an
// external process for this test.
// Can't verify our own console output, so using an external process for this test
// Arrange
var command = "Hello world" | Cli.Wrap("dotnet")
@@ -43,7 +42,26 @@ public class ConsoleSpecs : SpecsBase
}
[Fact]
public async Task Fake_console_does_not_leak_to_system_console()
public void I_can_run_the_application_on_a_system_with_a_custom_console_encoding_and_not_get_corrupted_output()
{
// Arrange
using var buffer = new MemoryStream();
using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8);
// Act
consoleWriter.Write("Hello world");
consoleWriter.Flush();
// Assert
var outputBytes = buffer.ToArray();
outputBytes.Should().NotContain(Encoding.UTF8.GetPreamble());
var output = consoleWriter.Encoding.GetString(outputBytes);
output.Should().Be("Hello world");
}
[Fact]
public async Task I_can_run_the_application_with_the_fake_console_implementation_to_isolate_console_interactions()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -104,7 +122,7 @@ public class ConsoleSpecs : SpecsBase
}
[Fact]
public async Task Fake_console_can_be_used_with_an_in_memory_backing_store()
public async Task I_can_run_the_application_with_the_fake_console_implementation_and_simulate_stream_interactions()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -138,17 +156,18 @@ public class ConsoleSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("Hello world");
var stdErr = FakeConsole.ReadErrorString();
stdErr.Trim().Should().Be("Hello world");
}
[Fact]
public async Task Fake_console_can_read_key_presses()
public async Task I_can_run_the_application_with_the_fake_console_implementation_and_simulate_key_presses()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -184,31 +203,14 @@ public class ConsoleSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().ConsistOfLines(
"D0",
"A",
"Backspace"
);
}
[Fact]
public void Console_does_not_emit_preamble_when_used_with_encoding_that_has_it()
{
// Arrange
using var buffer = new MemoryStream();
using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8);
// Act
consoleWriter.Write("Hello world");
consoleWriter.Flush();
var output = consoleWriter.Encoding.GetString(buffer.ToArray());
// Assert
output.Should().Be("Hello world");
}
}

View File

@@ -16,7 +16,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_string()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -26,7 +26,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public string Foo { get; set; }
public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -48,15 +48,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("xyz");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_an_object()
public async Task I_can_bind_a_parameter_or_an_option_to_an_object_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -66,7 +66,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public object Foo { get; set; }
public object? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -88,15 +88,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("xyz");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_boolean()
public async Task I_can_bind_a_parameter_or_an_option_to_a_boolean_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -106,15 +106,19 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public bool Foo { get; set; }
public bool Foo { get; init; }
[CommandOption('b')]
public bool Bar { get; set; }
public bool Bar { get; init; }
[CommandOption('c')]
public bool Baz { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine("Foo = " + Foo);
console.Output.WriteLine("Bar = " + Bar);
console.Output.WriteLine("Baz = " + Baz);
return default;
}
@@ -129,22 +133,28 @@ public class ConversionSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "true", "-b", "false"},
new[]
{
"-f", "true",
"-b", "false",
"-c"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = True",
"Bar = False"
"Bar = False",
"Baz = True"
);
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_boolean_with_implicit_value()
public async Task I_can_bind_a_parameter_or_an_option_to_an_integer_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -154,47 +164,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public bool Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Foo);
return default;
}
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"-f"},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("True");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_an_integer()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption('f')]
public int Foo { get; set; }
public int Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -216,15 +186,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("32");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_double()
public async Task I_can_bind_a_parameter_or_an_option_to_a_double_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -234,7 +204,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public double Foo { get; set; }
public double Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -256,15 +226,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("32.14");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_DateTimeOffset()
public async Task I_can_bind_a_parameter_or_an_option_to_a_DateTimeOffset_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -274,7 +244,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public DateTimeOffset Foo { get; set; }
public DateTimeOffset Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -296,15 +266,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("1995-04-28 00:00:00Z");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_TimeSpan()
public async Task I_can_bind_a_parameter_or_an_option_to_a_TimeSpan_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -314,7 +284,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public TimeSpan Foo { get; set; }
public TimeSpan Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -336,15 +306,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("12:34:56");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_an_enum()
public async Task I_can_bind_a_parameter_or_an_option_to_an_enum_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -356,7 +326,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomEnum Foo { get; set; }
public CustomEnum Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -378,15 +348,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("2");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_nullable_integer()
public async Task I_can_bind_a_parameter_or_an_option_to_a_nullable_integer_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -396,10 +366,10 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public int? Foo { get; set; }
public int? Foo { get; init; }
[CommandOption('b')]
public int? Bar { get; set; }
public int? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -423,10 +393,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = ",
"Bar = 123"
@@ -434,7 +404,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_nullable_enum()
public async Task I_can_bind_a_parameter_or_an_option_to_a_nullable_enum_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -446,10 +416,10 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomEnum? Foo { get; set; }
public CustomEnum? Foo { get; init; }
[CommandOption('b')]
public CustomEnum? Bar { get; set; }
public CustomEnum? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -473,10 +443,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = ",
"Bar = 2"
@@ -484,7 +454,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_type_that_has_a_constructor_accepting_a_string()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_constructable_object_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -501,7 +471,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public CustomType? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -523,15 +493,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("xyz");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_type_that_has_a_static_parse_method()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_parsable_object_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -561,10 +531,10 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomTypeA Foo { get; set; }
public CustomTypeA? Foo { get; init; }
[CommandOption('b')]
public CustomTypeB Bar { get; set; }
public CustomTypeB? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -588,10 +558,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = hello",
"Bar = world"
@@ -599,7 +569,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_using_a_custom_converter()
public async Task I_can_bind_a_parameter_or_an_option_to_a_property_with_a_custom_converter()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -615,7 +585,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f', Converter = typeof(CustomConverter))]
public int Foo { get; set; }
public int Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -637,15 +607,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("11");
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_an_array_of_strings()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_array_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -655,7 +625,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public string[] Foo { get; set; }
public string[]? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -679,10 +649,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -691,7 +661,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_read_only_list_of_strings()
public async Task I_can_bind_a_parameter_or_an_option_to_a_read_only_list_of_strings_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -701,7 +671,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -725,10 +695,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -737,7 +707,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_a_list_of_strings()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_list_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -747,7 +717,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public List<string> Foo { get; set; }
public List<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -771,10 +741,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -783,7 +753,7 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_can_be_converted_to_an_array_of_integers()
public async Task I_can_bind_a_parameter_or_an_option_to_an_integer_array_property()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -793,7 +763,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public int[] Foo { get; set; }
public int[]? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -817,10 +787,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"1",
"13",
@@ -829,55 +799,21 @@ public class ConversionSpecs : SpecsBase
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_value_cannot_be_converted_to_the_target_type()
public async Task I_can_try_to_bind_a_parameter_or_an_option_to_a_property_of_an_unsupported_type_and_get_an_error()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
public class CustomType
{
[CommandOption('f')]
public int Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "12.34"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().NotBeNullOrWhiteSpace();
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_target_type_is_not_supported()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
public class CustomType {}
[Command]
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public CustomType? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -895,15 +831,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("has an unsupported underlying property type");
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_target_non_scalar_type_is_not_supported()
public async Task I_can_try_to_bind_a_parameter_or_an_option_to_a_non_scalar_property_of_an_unsupported_type_and_get_an_error()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -920,7 +856,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public CustomType? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -938,15 +874,51 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("has an unsupported underlying property type");
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_one_of_the_validators_fail()
public async Task I_can_bind_a_parameter_or_an_option_to_a_property_and_get_an_error_if_the_user_provides_an_invalid_value()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption('f')]
public int Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "12.34"},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().NotBeNullOrWhiteSpace();
}
[Fact]
public async Task I_can_bind_a_parameter_or_an_option_to_a_property_and_get_an_error_if_a_custom_validator_fails()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -966,7 +938,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f', Validators = new[] {typeof(ValidatorA), typeof(ValidatorB)})]
public int Foo { get; set; }
public int Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -984,15 +956,15 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Hello world");
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_static_parse_method_throws()
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_parsable_property_and_get_an_error_if_the_parsing_fails()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -1011,7 +983,7 @@ public class ConversionSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public CustomType? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -1029,10 +1001,10 @@ public class ConversionSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Hello world");
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CliFx.Tests.Utils;
@@ -20,7 +19,7 @@ public class DirectivesSpecs : SpecsBase
}
[Fact(Timeout = 15000)]
public async Task Debug_directive_can_be_specified_to_interrupt_execution_until_a_debugger_is_attached()
public async Task I_can_use_the_debug_directive_to_make_the_application_wait_for_the_debugger_to_attach()
{
// Arrange
using var cts = new CancellationTokenSource();
@@ -29,7 +28,7 @@ public class DirectivesSpecs : SpecsBase
void HandleStdOut(string line)
{
// Kill the process once it writes the output we expect
if (line.Contains("Attach debugger to", StringComparison.OrdinalIgnoreCase))
if (line.Contains("Attach the debugger to", StringComparison.OrdinalIgnoreCase))
cts.Cancel();
}
@@ -51,7 +50,7 @@ public class DirectivesSpecs : SpecsBase
}
[Fact]
public async Task Preview_directive_can_be_specified_to_print_command_input()
public async Task I_can_use_the_preview_directive_to_make_the_application_print_the_parsed_command_input()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -81,10 +80,10 @@ public class DirectivesSpecs : SpecsBase
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder(
"cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]",
"ENV_QOP", "=", "\"hello\"",

View File

@@ -20,7 +20,7 @@ public class EnvironmentSpecs : SpecsBase
}
[Fact]
public async Task Option_can_fall_back_to_an_environment_variable()
public async Task I_can_configure_an_option_to_fall_back_to_an_environment_variable_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -29,12 +29,17 @@ public class EnvironmentSpecs : SpecsBase
[Command]
public class Command : ICommand
{
[CommandOption("foo", IsRequired = true, EnvironmentVariable = "ENV_FOO")]
public string Foo { get; set; }
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
public string? Foo { get; init; }
[CommandOption("bar", EnvironmentVariable = "ENV_BAR")]
public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Foo);
console.Output.WriteLine(Bar);
return default;
}
}
@@ -48,22 +53,26 @@ public class EnvironmentSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
Array.Empty<string>(),
new[] {"--foo", "42"},
new Dictionary<string, string>
{
["ENV_FOO"] = "bar"
["ENV_FOO"] = "100",
["ENV_BAR"] = "200"
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("bar");
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().ConsistOfLines(
"42",
"200"
);
}
[Fact]
public async Task Option_does_not_fall_back_to_an_environment_variable_if_a_value_is_provided_through_arguments()
public async Task I_can_configure_an_option_bound_to_a_non_scalar_property_to_fall_back_to_an_environment_variable_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -73,50 +82,7 @@ public class EnvironmentSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
public string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Foo);
return default;
}
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--foo", "baz"},
new Dictionary<string, string>
{
["ENV_FOO"] = "bar"
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("baz");
}
[Fact]
public async Task Option_of_non_scalar_type_can_receive_multiple_values_from_an_environment_variable()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -143,10 +109,10 @@ public class EnvironmentSpecs : SpecsBase
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"bar",
"baz"
@@ -154,7 +120,7 @@ public class EnvironmentSpecs : SpecsBase
}
[Fact]
public async Task Option_of_scalar_type_always_receives_a_single_value_from_an_environment_variable()
public async Task I_can_configure_an_option_bound_to_a_scalar_property_to_fall_back_to_an_environment_variable_while_ignoring_path_separators()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -164,7 +130,7 @@ public class EnvironmentSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
public string Foo { get; set; }
public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -189,60 +155,15 @@ public class EnvironmentSpecs : SpecsBase
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be($"bar{Path.PathSeparator}baz");
}
[Fact]
public async Task Environment_variables_are_matched_case_sensitively()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
public string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Foo);
return default;
}
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
Array.Empty<string>(),
new Dictionary<string, string>
{
["ENV_foo"] = "baz",
["ENV_FOO"] = "bar",
["env_FOO"] = "qop"
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("bar");
}
[Fact(Timeout = 15000)]
public async Task Environment_variables_are_extracted_automatically()
public async Task I_can_run_the_application_and_it_will_resolve_all_required_environment_variables_automatically()
{
// Ensures that the environment variables are properly obtained from
// System.Environment when they are not provided explicitly to CliApplication.

View File

@@ -17,7 +17,7 @@ public class ErrorReportingSpecs : SpecsBase
}
[Fact]
public async Task Command_can_throw_an_exception_which_exits_with_a_stacktrace()
public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_with_a_stacktrace()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -43,12 +43,13 @@ public class ErrorReportingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder(
"System.Exception", "Something went wrong",
"at", "CliFx."
@@ -56,7 +57,7 @@ public class ErrorReportingSpecs : SpecsBase
}
[Fact]
public async Task Command_can_throw_an_exception_with_an_inner_exception_which_exits_with_a_stacktrace()
public async Task I_can_throw_an_exception_with_an_inner_exception_in_a_command_to_report_an_error_with_a_stacktrace()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -82,12 +83,13 @@ public class ErrorReportingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder(
"System.Exception", "Something went wrong",
"System.Exception", "Another exception",
@@ -96,7 +98,7 @@ public class ErrorReportingSpecs : SpecsBase
}
[Fact]
public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message()
public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_and_exit_with_the_specified_code()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -122,17 +124,18 @@ public class ErrorReportingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().Be(69);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString();
stdErr.Trim().Should().Be("Something went wrong");
}
[Fact]
public async Task Command_can_throw_a_special_exception_without_message_which_exits_with_a_stacktrace()
public async Task I_can_throw_an_exception_without_a_message_in_a_command_to_report_an_error_with_a_stacktrace()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -158,12 +161,13 @@ public class ErrorReportingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().Be(69);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder(
"CliFx.Exceptions.CommandException",
"at", "CliFx."
@@ -171,7 +175,7 @@ public class ErrorReportingSpecs : SpecsBase
}
[Fact]
public async Task Command_can_throw_a_special_exception_which_prints_help_text_before_exiting()
public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_and_print_the_help_text()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -198,12 +202,13 @@ public class ErrorReportingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().Be(69);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().Contain("This will be in help text");
var stdErr = FakeConsole.ReadErrorString();
stdErr.Trim().Should().Be("Something went wrong");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_is_bound_from_an_argument_matching_its_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_by_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -27,7 +27,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public bool Foo { get; set; }
public bool Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -49,15 +49,15 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("True");
}
[Fact]
public async Task Option_is_bound_from_an_argument_matching_its_short_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_by_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -67,7 +67,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public bool Foo { get; set; }
public bool Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -89,15 +89,15 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("True");
}
[Fact]
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_set_by_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -107,10 +107,10 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public string Foo { get; set; }
public string? Foo { get; init; }
[CommandOption("bar")]
public string Bar { get; set; }
public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -130,14 +130,18 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"--foo", "one", "--bar", "two"},
new[]
{
"--foo", "one",
"--bar", "two"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = one",
"Bar = two"
@@ -145,7 +149,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_short_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_set_by_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -155,10 +159,10 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public string Foo { get; set; }
public string? Foo { get; init; }
[CommandOption('b')]
public string Bar { get; set; }
public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -178,14 +182,18 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "one", "-b", "two"},
new[]
{
"-f", "one",
"-b", "two"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = one",
"Bar = two"
@@ -193,7 +201,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_is_bound_from_a_stack_of_arguments_matching_its_short_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_stack_by_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -203,10 +211,10 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public string Foo { get; set; }
public string? Foo { get; init; }
[CommandOption('b')]
public string Bar { get; set; }
public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -230,10 +238,10 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = ",
"Bar = value"
@@ -241,7 +249,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_name()
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_the_values_from_the_corresponding_arguments_by_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -251,7 +259,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("Foo")]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -275,10 +283,10 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -287,7 +295,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_short_name()
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_the_values_from_the_corresponding_arguments_by_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -297,7 +305,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -321,10 +329,10 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -333,7 +341,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name()
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_the_values_from_the_corresponding_argument_sets_by_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -343,7 +351,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -363,14 +371,19 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"--foo", "one", "--foo", "two", "--foo", "three"},
new[]
{
"--foo", "one",
"--foo", "two",
"--foo", "three"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -379,7 +392,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_short_name()
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_the_values_from_the_corresponding_argument_sets_by_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -389,7 +402,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption('f')]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -409,14 +422,19 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "one", "-f", "two", "-f", "three"},
new[]
{
"-f", "one",
"-f", "two",
"-f", "three"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -425,7 +443,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name_or_short_name()
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_the_values_from_the_corresponding_argument_sets_by_name_or_short_name()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -435,7 +453,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo", 'f')]
public IReadOnlyList<string> Foo { get; set; }
public IReadOnlyList<string>? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -455,14 +473,19 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"--foo", "one", "-f", "two", "--foo", "three"},
new[]
{
"--foo", "one",
"-f", "two",
"--foo", "three"
},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"one",
"two",
@@ -471,7 +494,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_is_not_bound_if_there_are_no_arguments_matching_its_name_or_short_name()
public async Task I_can_bind_an_option_to_a_property_and_get_no_value_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -481,10 +504,10 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public string Foo { get; set; }
public string? Foo { get; init; }
[CommandOption("bar")]
public string Bar { get; set; } = "hello";
public string? Bar { get; init; } = "hello";
public ValueTask ExecuteAsync(IConsole console)
{
@@ -508,10 +531,10 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = one",
"Bar = hello"
@@ -519,7 +542,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_binding_supports_multiple_inheritance_through_default_interface_members()
public async Task I_can_bind_an_option_to_a_property_through_multiple_inheritance()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -538,7 +561,7 @@ public class OptionBindingSpecs : SpecsBase
public int Foo
{
get => SharedContext.Foo;
set => SharedContext.Foo = value;
init => SharedContext.Foo = value;
}
}
@@ -548,20 +571,20 @@ public class OptionBindingSpecs : SpecsBase
public bool Bar
{
get => SharedContext.Bar;
set => SharedContext.Bar = value;
init => SharedContext.Bar = value;
}
}
public interface IHasBaz : ICommand
{
public string Baz { get; set; }
public string? Baz { get; init; }
}
[Command]
public class Command : IHasFoo, IHasBar, IHasBaz
{
[CommandOption("baz")]
public string Baz { get; set; }
public string? Baz { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -582,13 +605,18 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] { "--foo", "42", "--bar", "--baz", "xyz" }
new[]
{
"--foo", "42",
"--bar",
"--baz", "xyz"
}
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = 42",
"Bar = True",
@@ -597,7 +625,7 @@ public class OptionBindingSpecs : SpecsBase
}
[Fact]
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
public async Task I_can_bind_an_option_to_a_property_and_get_the_correct_value_if_the_user_provides_an_argument_containing_a_negative_number()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -607,12 +635,11 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public string Foo { get; set; }
public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Foo);
return default;
}
}
@@ -630,15 +657,15 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("-13");
}
[Fact]
public async Task Option_binding_fails_if_a_required_option_has_not_been_provided()
public async Task I_can_bind_a_required_option_to_a_property_and_get_an_error_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -647,8 +674,8 @@ public class OptionBindingSpecs : SpecsBase
[Command]
public class Command : ICommand
{
[CommandOption("foo", IsRequired = true)]
public string Foo { get; set; }
[CommandOption("foo")]
public required string Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -666,87 +693,15 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Missing required option(s)");
}
[Fact]
public async Task Option_binding_fails_if_a_required_option_has_been_provided_with_an_empty_value()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo", IsRequired = true)]
public string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--foo"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing required option(s)");
}
[Fact]
public async Task Option_binding_fails_if_a_required_option_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo", IsRequired = true)]
public IReadOnlyList<string> Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--foo"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing required option(s)");
}
[Fact]
public async Task Option_binding_fails_if_one_of_the_provided_option_names_is_not_recognized()
public async Task I_can_bind_a_required_option_to_a_property_and_get_an_error_if_the_user_provides_an_empty_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -756,7 +711,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public string Foo { get; set; }
public required string Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -770,19 +725,95 @@ public class OptionBindingSpecs : SpecsBase
// Act
var exitCode = await application.RunAsync(
new[] {"--foo", "one", "--bar", "two"},
new[] {"--foo"},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Missing required option(s)");
}
[Fact]
public async Task I_can_bind_an_option_to_a_non_scalar_property_and_get_an_error_if_the_user_does_not_provide_at_least_one_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo")]
public required IReadOnlyList<string> Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--foo"},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Missing required option(s)");
}
[Fact]
public async Task I_can_bind_options_and_get_an_error_if_the_user_provides_unrecognized_arguments()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo")]
public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[]
{
"--foo", "one",
"--bar", "two"
},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Unrecognized option(s)");
}
[Fact]
public async Task Option_binding_fails_if_an_option_of_scalar_type_has_been_provided_with_multiple_values()
public async Task I_can_bind_an_option_to_a_scalar_property_and_get_an_error_if_the_user_provides_too_many_arguments()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -792,7 +823,7 @@ public class OptionBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandOption("foo")]
public string Foo { get; set; }
public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -810,46 +841,10 @@ public class OptionBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("expects a single argument, but provided with multiple");
}
[Fact]
public async Task Option_binding_fails_if_a_required_property_option_has_not_been_provided()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandOption("foo")]
public required string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
Array.Empty<string>(),
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing required option(s)");
}
}

View File

@@ -16,7 +16,7 @@ public class ParameterBindingSpecs : SpecsBase
}
[Fact]
public async Task Parameter_is_bound_from_an_argument_matching_its_order()
public async Task I_can_bind_a_parameter_to_a_property_and_get_the_value_from_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -26,10 +26,10 @@ public class ParameterBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public required string Foo { get; init; }
[CommandParameter(1)]
public string Bar { get; set; }
public required string Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -53,10 +53,10 @@ public class ParameterBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = one",
"Bar = two"
@@ -64,7 +64,7 @@ public class ParameterBindingSpecs : SpecsBase
}
[Fact]
public async Task Parameter_of_non_scalar_type_is_bound_from_remaining_non_option_arguments()
public async Task I_can_bind_a_parameter_to_a_non_scalar_property_and_get_values_from_the_remaining_non_option_arguments()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -74,16 +74,16 @@ public class ParameterBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public required string Foo { get; init; }
[CommandParameter(1)]
public string Bar { get; set; }
public required string Bar { get; init; }
[CommandParameter(2)]
public IReadOnlyList<string> Baz { get; set; }
public required IReadOnlyList<string> Baz { get; init; }
[CommandOption("boo")]
public string Boo { get; set; }
public string? Boo { get; init; }
public ValueTask ExecuteAsync(IConsole console)
{
@@ -110,10 +110,10 @@ public class ParameterBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = one",
"Bar = two",
@@ -124,7 +124,7 @@ public class ParameterBindingSpecs : SpecsBase
}
[Fact]
public async Task Parameter_is_not_bound_if_there_are_no_arguments_matching_its_order()
public async Task I_can_bind_a_parameter_to_a_property_and_get_an_error_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -134,10 +134,88 @@ public class ParameterBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public required string Foo { get; init; }
[CommandParameter(1)]
public required string Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"one"},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
public async Task I_can_bind_a_parameter_to_a_non_scalar_property_and_get_an_error_if_the_user_does_not_provide_at_least_one_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public required string Foo { get; init; }
[CommandParameter(1)]
public required IReadOnlyList<string> Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"one"},
new Dictionary<string, string>()
);
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
public async Task I_can_bind_a_non_required_parameter_to_a_property_and_get_no_value_if_the_user_does_not_provide_the_corresponding_argument()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public required string Foo { get; init; }
[CommandParameter(1, IsRequired = false)]
public string Bar { get; set; } = "xyz";
public string? Bar { get; init; } = "xyz";
public ValueTask ExecuteAsync(IConsole console)
{
@@ -161,10 +239,10 @@ public class ParameterBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines(
"Foo = abc",
"Bar = xyz"
@@ -172,7 +250,7 @@ public class ParameterBindingSpecs : SpecsBase
}
[Fact]
public async Task Parameter_binding_fails_if_a_required_parameter_has_not_been_provided()
public async Task I_can_bind_parameters_and_get_an_error_if_the_user_provides_too_many_arguments()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -182,88 +260,10 @@ public class ParameterBindingSpecs : SpecsBase
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public required string Foo { get; init; }
[CommandParameter(1)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"one"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
public async Task Parameter_binding_fails_if_a_parameter_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
[CommandParameter(1)]
public IReadOnlyList<string> Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
"""
);
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"one"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
public async Task Parameter_binding_fails_if_one_of_the_provided_parameters_is_unexpected()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
"""
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
[CommandParameter(1)]
public string Bar { get; set; }
public required string Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -281,10 +281,10 @@ public class ParameterBindingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Unexpected parameter(s)");
}
}

View File

@@ -16,7 +16,7 @@ public class RoutingSpecs : SpecsBase
}
[Fact]
public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command()
public async Task I_can_configure_a_command_to_be_executed_by_default_when_the_user_does_not_specify_a_command_name()
{
// Arrange
var commandTypes = DynamicCommandBuilder.CompileMany(
@@ -65,15 +65,15 @@ public class RoutingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("default");
}
[Fact]
public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name()
public async Task I_can_configure_a_command_to_be_executed_when_the_user_specifies_its_name()
{
// Arrange
var commandTypes = DynamicCommandBuilder.CompileMany(
@@ -122,15 +122,15 @@ public class RoutingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("cmd");
}
[Fact]
public async Task Specific_named_child_command_is_executed_if_provided_arguments_match_its_name()
public async Task I_can_configure_a_nested_command_to_be_executed_when_the_user_specifies_its_name()
{
// Arrange
var commandTypes = DynamicCommandBuilder.CompileMany(
@@ -179,10 +179,10 @@ public class RoutingSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("cmd child");
}
}

View File

@@ -18,7 +18,7 @@ public class TypeActivationSpecs : SpecsBase
}
[Fact]
public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor()
public async Task I_can_configure_the_application_to_use_the_default_type_activator_to_initialize_types_through_parameterless_constructors()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -48,15 +48,15 @@ public class TypeActivationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("foo");
}
[Fact]
public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor()
public async Task I_can_configure_the_application_to_use_the_default_type_activator_and_get_an_error_if_the_requested_type_does_not_have_a_parameterless_constructor()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -84,15 +84,15 @@ public class TypeActivationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Failed to create an instance of type");
}
[Fact]
public async Task Custom_type_activator_can_initialize_a_type_using_a_given_function()
public async Task I_can_configure_the_application_to_use_a_custom_type_activator_to_initialize_types_using_a_delegate()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -126,15 +126,15 @@ public class TypeActivationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("Hello world");
}
[Fact]
public async Task Custom_type_activator_can_initialize_a_type_using_a_service_provider()
public async Task I_can_configure_the_application_to_use_a_custom_type_activator_to_initialize_types_using_a_service_provider()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -181,15 +181,15 @@ public class TypeActivationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().Be("Hello world");
}
[Fact]
public async Task Custom_type_activator_fails_if_the_underlying_function_returns_null()
public async Task I_can_configure_the_application_to_use_a_custom_type_activator_and_get_an_error_if_the_requested_type_cannot_be_initialized()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -219,10 +219,10 @@ public class TypeActivationSpecs : SpecsBase
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().Contain("Failed to create an instance of type");
}
}

View File

@@ -11,14 +11,9 @@ using Microsoft.CodeAnalysis.Text;
namespace CliFx.Tests.Utils;
// This class uses Roslyn to compile commands dynamically.
//
// It allows us to collocate commands with tests more
// easily, which helps a lot when reasoning about them.
// Unfortunately, this comes at a cost of static typing,
// but this is still a worthwhile trade off.
//
// Maybe one day C# will allow declaring classes inside
// methods and doing this will no longer be necessary.
// It allows us to collocate commands with tests more easily, which helps a lot when reasoning about them.
// Unfortunately, this comes at a cost of static typing, but this is still a worthwhile trade off.
// Maybe one day C# will allow declaring classes inside methods and doing this will no longer be necessary.
// Language proposal: https://github.com/dotnet/csharplang/discussions/130
internal static class DynamicCommandBuilder
{
@@ -124,7 +119,6 @@ internal static class DynamicCommandBuilder
public static Type Compile(string sourceCode)
{
var commandTypes = CompileMany(sourceCode);
if (commandTypes.Count > 1)
{
throw new InvalidOperationException(

View File

@@ -41,7 +41,7 @@ internal static class AssertionExtensions
lastIndex = index;
}
return new(assertions);
return new AndConstraint<StringAssertions>(assertions);
}
public static AndConstraint<StringAssertions> ContainAllInOrder(

View File

@@ -5,7 +5,7 @@ using CliFx.Infrastructure;
namespace CliFx.Tests.Utils;
[Command]
public class NoOpCommand : ICommand
internal class NoOpCommand : ICommand
{
public ValueTask ExecuteAsync(IConsole console) => default;
}