public async Task Property_of_type_nullable_TimeSpan_is_bound_as_null_if_the_argument_value_is_not_set() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <SupportedArgumentTypesCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync(new[] { "cmd", "--timespan-nullable" }); var commandInstance = stdOut.GetString().DeserializeJson <SupportedArgumentTypesCommand>(); // Assert exitCode.Should().Be(ExitCodes.Success); commandInstance.Should().BeEquivalentTo(new SupportedArgumentTypesCommand { TimeSpanNullable = null }); }
public async Task Custom_directive_should_have_non_empty_name() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .AddDirective <EmptyNameDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[preview]", "named", "param", "-abc", "--option", "foo" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Error); stdOut.GetString().Should().BeNullOrWhiteSpace(); stdErr.GetString().Should().NotBeNullOrWhiteSpace(); stdErr.GetString().Should().Contain("[ ]"); _output.WriteLine(stdOut.GetString()); _output.WriteLine(stdErr.GetString()); }
public async Task Preview_directive_can_be_specified_to_print_provided_arguments_as_they_were_parsed() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[preview]", "named", "param", "-abc", "--option", "foo" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Success); stdOut.GetString().Should().NotBeNullOrWhiteSpace(); stdOut.GetString().Should().ContainAll( "named", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]" ); _output.WriteLine(stdOut.GetString()); }
public async Task Default_directive_should_allow_default_command_to_execute_when_there_is_a_name_conflict() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <DefaultCommandWithParameter>() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <DefaultDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[!]", "named" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Success); stdOut.GetString().Should().NotBeNullOrWhiteSpace(); stdOut.GetString().Should().ContainAll( "named", DefaultCommandWithParameter.ExpectedOutputText ); _output.WriteLine(stdOut.GetString()); }
public async Task Normal_mode_application_cannot_process_interactive_directive() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[interactive]" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Error); stdOut.GetString().Should().BeNullOrWhiteSpace(); stdOut.GetString().Should().NotContainAll("-h", "--help"); stdErr.GetString().Should().NotBeNullOrWhiteSpace(); stdErr.GetString().Should().Contain("This application does not support interactive mode."); _output.WriteLine(stdOut.GetString()); _output.WriteLine(stdErr.GetString()); }
public async Task Command_may_throw_a_specialized_exception_which_shows_only_the_help_text() { // Arrange await using var stdOut = new MemoryStream(); await using var stdErr = new MemoryStream(); var console = new VirtualConsole(output: stdOut); var application = new CliApplicationBuilder() .AddCommand(typeof(ShowHelpTextOnlyCommand)) .AddCommand(typeof(ShowHelpTextOnlySubCommand)) .UseConsole(console) .Build(); // Act await application.RunAsync(new[] { "exc" }); var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd(); var stdErrData = console.Output.Encoding.GetString(stdErr.ToArray()).TrimEnd(); // Assert stdErrData.Should().BeEmpty(); stdOutData.Should().ContainAll( "Usage", "[command]", "Options", "-h|--help", "Shows help text.", "Commands", "sub", "You can run", "to show help on a specific command." ); }
public async Task Option_of_non_scalar_type_can_use_an_environment_variable_as_fallback_and_extract_multiple_values() { var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <WithEnvironmentVariablesCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync( new[] { "cmd" }, new Dictionary <string, string> { ["ENV_OPT_B"] = $"foo{Path.PathSeparator}bar" } ); var commandInstance = stdOut.GetString().DeserializeJson <WithEnvironmentVariablesCommand>(); // Assert exitCode.Should().Be(ExitCodes.Success); commandInstance.Should().BeEquivalentTo(new WithEnvironmentVariablesCommand { OptB = new[] { "foo", "bar" } }); }
public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class Command : ICommand { public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""foo""); return default; } }"); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .UseTypeActivator(new DefaultTypeActivator()) .Build(); // Act var exitCode = await application.RunAsync( Array.Empty <string>(), new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); // Assert exitCode.Should().Be(0); stdOut.Trim().Should().Be("foo"); }
public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class Command : ICommand { public Command(string foo) {} public ValueTask ExecuteAsync(IConsole console) => default; }"); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .UseTypeActivator(new DefaultTypeActivator()) .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("Failed to create an instance of type"); }
public async Task Help_text_for_a_specific_named_sub_command_is_printed_if_provided_arguments_match_its_name_and_contain_the_help_option() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <DefaultCommand>() .AddCommand <NamedCommand>() .AddCommand <NamedSubCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync(new[] { "named", "sub", "--help" }); // Assert exitCode.Should().Be(ExitCodes.Success); stdOut.GetString().Should().ContainAll( "Named sub command description", "Usage", "named", "sub" ); _output.WriteLine(stdOut.GetString()); }
public async Task Delegate_type_activator_fails_if_the_underlying_function_returns_null() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class Command : ICommand { public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""foo""); return default; } }"); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .UseTypeActivator(_ => null !) .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("Failed to create an instance of type"); }
public async Task RunAsync_Cancellation_Test() { // Arrange using var cancellationTokenSource = new CancellationTokenSource(); await using var stdoutStream = new StringWriter(); var console = new VirtualConsole(stdoutStream, cancellationTokenSource.Token); var application = new CliApplicationBuilder() .AddCommand(typeof(CancellableCommand)) .UseConsole(console) .Build(); var args = new[] { "cancel" }; // Act var runTask = application.RunAsync(args); cancellationTokenSource.Cancel(); var exitCode = await runTask.ConfigureAwait(false); var stdOut = stdoutStream.ToString().Trim(); // Assert exitCode.Should().Be(-2146233029); stdOut.Should().Be("Printed"); }
public async Task RunAsync_Test(IReadOnlyList <Type> commandTypes, IReadOnlyList <string> commandLineArguments, string?expectedStdOut = null) { // Arrange await using var stdoutStream = new StringWriter(); var console = new VirtualConsole(stdoutStream); var environmentVariablesProvider = new EnvironmentVariablesProviderStub(); var application = new CliApplicationBuilder() .AddCommands(commandTypes) .UseVersionText(TestVersionText) .UseConsole(console) .UseEnvironmentVariablesProvider(environmentVariablesProvider) .Build(); // Act var exitCode = await application.RunAsync(commandLineArguments); var stdOut = stdoutStream.ToString().Trim(); // Assert exitCode.Should().Be(0); if (expectedStdOut != null) { stdOut.Should().Be(expectedStdOut); } else { stdOut.Should().NotBeNullOrWhiteSpace(); } }
public async Task Property_of_a_nullable_enum_type_is_bound_by_parsing_the_argument_value_as_name_if_it_is_set() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <SupportedArgumentTypesCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync(new[] { "cmd", "--enum-nullable", "value3" }); var commandInstance = stdOut.GetString().DeserializeJson <SupportedArgumentTypesCommand>(); // Assert exitCode.Should().Be(ExitCodes.Success); commandInstance.Should().BeEquivalentTo(new SupportedArgumentTypesCommand { EnumNullable = CustomEnum.Value3 }); }
public async Task Command_may_throw_a_specialized_exception_which_shows_only_a_stack_trace_and_no_help_text() { // Arrange await using var stdErr = new MemoryStream(); var console = new VirtualConsole(error: stdErr); var application = new CliApplicationBuilder() .AddCommand(typeof(GenericExceptionCommand)) .UseConsole(console) .Build(); // Act var exitCode = await application.RunAsync( new[] { "exc", "-m", "Kaput" }, new Dictionary <string, string>()); var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd(); // Assert exitCode.Should().NotBe(0); stdErrData.Should().ContainAll( "System.Exception:", "Kaput", "at", "CliFx.Tests"); }
public async Task Help_text_shows_application_metadata() { // Arrange var application = new CliApplicationBuilder() .UseConsole(FakeConsole) .SetTitle("App title") .SetDescription("App description") .SetVersion("App version") .Build(); // Act var exitCode = await application.RunAsync( new[] { "--help" }, new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); // Assert exitCode.Should().Be(0); stdOut.Should().ContainAll( "App title", "App description", "App version" ); }
public async Task Command_shows_help_text_on_exceptions_related_to_invalid_user_input() { // Arrange await using var stdOut = new MemoryStream(); await using var stdErr = new MemoryStream(); var console = new VirtualConsole(output: stdOut, error: stdErr); var application = new CliApplicationBuilder() .AddCommand(typeof(InvalidUserInputCommand)) .UseConsole(console) .Build(); // Act var exitCode = await application.RunAsync( new[] { "not-a-valid-command", "-r", "foo" }, new Dictionary <string, string>()); var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd(); var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd(); // Assert exitCode.Should().NotBe(0); stdErrData.Should().ContainAll( "Can't find a command that matches the following arguments:", "not-a-valid-command"); stdOutData.Should().ContainAll( "Usage", "[command]", "Options", "-h|--help", "Shows help text.", "Commands", "inv", "You can run", "to show help on a specific command."); }
public async Task Help_text_shows_command_description() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command(Description = ""Description of the default command."")] public class DefaultCommand : ICommand { public ValueTask ExecuteAsync(IConsole console) => default; } "); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .Build(); // Act var exitCode = await application.RunAsync( new[] { "--help" }, new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); // Assert exitCode.Should().Be(0); stdOut.Should().ContainAllInOrder( "DESCRIPTION", "Description of the default command." ); }
public async Task Option_only_uses_an_environment_variable_as_fallback_if_the_name_matches_case_sensitively() { var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <WithEnvironmentVariablesCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync( new[] { "cmd" }, new Dictionary <string, string> { ["ENV_opt_A"] = "incorrect", ["ENV_OPT_A"] = "correct" } ); var commandInstance = stdOut.GetString().DeserializeJson <WithEnvironmentVariablesCommand>(); // Assert exitCode.Should().Be(ExitCodes.Success); commandInstance.Should().BeEquivalentTo(new WithEnvironmentVariablesCommand { OptA = "correct" }); }
public async Task Help_text_is_printed_if_provided_arguments_contain_the_help_option() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class DefaultCommand : ICommand { public ValueTask ExecuteAsync(IConsole console) => default; } "); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .SetDescription("This will be in help text") .Build(); // Act var exitCode = await application.RunAsync( new[] { "--help" }, new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); // Assert exitCode.Should().Be(0); stdOut.Should().Contain("This will be in help text"); }
public async Task Custom_directive_should_run() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .AddDirective <CustomDirective>() .AddDirective <CustomStopDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[custom]", "named" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Success); stdOut.GetString().Should().NotBeNullOrWhiteSpace(); stdOut.GetString().Should().ContainAll( CustomDirective.ExpectedOutput, NamedCommand.ExpectedOutputText ); _output.WriteLine(stdOut.GetString()); }
public async Task Help_text_shows_the_implicit_help_and_version_options_on_the_default_command() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class Command : ICommand { public ValueTask ExecuteAsync(IConsole console) => default; } "); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .Build(); // Act var exitCode = await application.RunAsync( new[] { "--help" }, new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); // Assert exitCode.Should().Be(0); stdOut.Should().ContainAllInOrder( "OPTIONS", "-h", "--help", "Shows help text", "--version", "Shows version information" ); }
public async Task Custom_interactive_directive_should_not_run_in_normal_mode() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .AddDirective <CustomDirective>() .AddDirective <CustomStopDirective>() .AddDirective <CustomInteractiveModeOnlyDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[custom-interactive]", "named", "param", "-abc", "--option", "foo" }, new Dictionary <string, string>()); // Assert exitCode.Should().NotBe(0); stdOut.GetString().Should().BeNullOrWhiteSpace(); stdOut.GetString().Should().NotContainAll( "@ [custom-interactive]", "Description", "Usage", "Directives", "[custom]" ); stdErr.GetString().Should().ContainAll( "Directive", "[custom-interactive]", "is for interactive mode only." ); _output.WriteLine(stdOut.GetString()); }
public async Task Application_without_interactive_mode_cannot_execute_interactive_only_commands() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); // Act var app = new CliApplicationBuilder().AddCommand <BenchmarkDefaultCommand>() .AddCommand <NamedInteractiveOnlyCommand>() .UseConsole(console) .Build(); // Assert app.Should().NotBeNull(); // Act int exitCode = await app.RunAsync(new string[] { "named-interactive-only" }, new Dictionary <string, string>()); // Asert exitCode.Should().Be(ExitCodes.Error); stdOut.GetString().Should().BeNullOrWhiteSpace(); stdErr.GetString().Should().NotBeNullOrWhiteSpace(); stdErr.GetString().Should().Contain("can be executed only in interactive mode, but this application is using CliApplication."); _output.WriteLine(stdOut.GetString()); _output.WriteLine(stdErr.GetString()); }
public async Task Custom_throwable_directive_with_inner_exception_should_throw_exception() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <PreviewDirective>() .AddDirective <CustomThrowableDirective>() .AddDirective <CustomThrowableDirectiveWithMessage>() .AddDirective <CustomThrowableDirectiveWithInnerException>() .AddDirective <CustomDirective>() .AddDirective <CustomStopDirective>() .AddDirective <CustomInteractiveModeOnlyDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[custom-throwable-with-inner-exception]", "named", "param", "-abc", "--option", "foo" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(CustomThrowableDirectiveWithInnerException.ExpectedExitCode); stdOut.GetString().Should().Be(CustomThrowableDirectiveWithInnerException.ExpectedOutput); stdErr.GetString().Should().ContainEquivalentOf(CustomThrowableDirectiveWithInnerException.ExpectedExceptionMessage); _output.WriteLine(stdOut.GetString()); _output.WriteLine(stdErr.GetString()); }
public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message() { // Arrange var commandType = DynamicCommandBuilder.Compile( // language=cs @" [Command] public class Command : ICommand { public ValueTask ExecuteAsync(IConsole console) => throw new CommandException(""Something went wrong"", 69); } "); var application = new CliApplicationBuilder() .AddCommand(commandType) .UseConsole(FakeConsole) .Build(); // Act var exitCode = await application.RunAsync( Array.Empty <string>(), new Dictionary <string, string>() ); var stdOut = FakeConsole.ReadOutputString(); var stdErr = FakeConsole.ReadErrorString(); // Assert exitCode.Should().Be(69); stdOut.Should().BeEmpty(); stdErr.Trim().Should().Be("Something went wrong"); }
public async Task Interactive_only_directive_cannot_be_executed_in_normal_mode() { // Arrange var(console, stdOut, stdErr) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <NamedCommand>() .UseConsole(console) .AddDirective <CustomInteractiveModeOnlyDirective>() .Build(); // Act int exitCode = await application.RunAsync( new[] { "[custom-interactive]", "named", "param", "-abc", "--option", "foo" }, new Dictionary <string, string>()); // Assert exitCode.Should().Be(ExitCodes.Error); stdOut.GetString().Should().BeNullOrWhiteSpace(); stdOut.GetString().Should().NotContainAll("-h", "--help"); stdErr.GetString().Should().NotBeNullOrWhiteSpace(); stdErr.GetString().Should().Contain("Directive '[custom-interactive]' is for interactive mode only. Thus, cannot be used in normal mode."); _output.WriteLine(stdOut.GetString()); _output.WriteLine(stdErr.GetString()); }
public async Task Application_can_be_created_with_a_fully_customized_configuration() { // Act var app = new CliApplicationBuilder() .AddCommand <NoOpCommand>() .AddCommandsFrom(typeof(NoOpCommand).Assembly) .AddCommands(new[] { typeof(NoOpCommand) }) .AddCommandsFrom(new[] { typeof(NoOpCommand).Assembly }) .AddCommandsFromThisAssembly() .AllowDebugMode() .AllowPreviewMode() .SetTitle("test") .SetExecutableName("test") .SetVersion("test") .SetDescription("test") .UseConsole(FakeConsole) .UseTypeActivator(Activator.CreateInstance !) .Build(); var exitCode = await app.RunAsync( Array.Empty <string>(), new Dictionary <string, string>() ); // Assert exitCode.Should().Be(0); }
public async Task Command_can_perform_additional_cleanup_if_cancellation_is_requested() { // Can't test it with a real console because CliWrap can't send Ctrl+C // Arrange using var cts = new CancellationTokenSource(); await using var stdOut = new MemoryStream(); var console = new VirtualConsole(output: stdOut, cancellationToken: cts.Token); var application = new CliApplicationBuilder() .AddCommand(typeof(CancellableCommand)) .UseConsole(console) .Build(); // Act cts.CancelAfter(TimeSpan.FromSeconds(0.2)); var exitCode = await application.RunAsync( new[] { "cancel" }, new Dictionary <string, string>()); var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd(); // Assert exitCode.Should().NotBe(0); stdOutData.Should().Be("Cancellation requested"); }
public async Task Property_of_type_DateTimeOffset_is_bound_by_parsing_the_argument_value() { // Arrange var(console, stdOut, _) = VirtualConsole.CreateBuffered(); var application = new CliApplicationBuilder() .AddCommand <SupportedArgumentTypesCommand>() .UseConsole(console) .Build(); // Act int exitCode = await application.RunAsync(new[] { "cmd", "--datetime-offset", "28 Apr 1995" }); var commandInstance = stdOut.GetString().DeserializeJson <SupportedArgumentTypesCommand>(); // Assert exitCode.Should().Be(ExitCodes.Success); commandInstance.Should().BeEquivalentTo(new SupportedArgumentTypesCommand { DateTimeOffset = new DateTime(1995, 04, 28) }); }