From d8e0508cdc379b4abd3c8be81b83d0baae1c0226 Mon Sep 17 00:00:00 2001 From: Alexei Samokvalov Date: Sun, 31 Dec 2023 20:36:57 +0200 Subject: [PATCH] Test failures Closes #32 --- src/command.zig | 2 +- src/help.zig | 6 ++- src/parser.zig | 3 ++ src/tests.zig | 140 +++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 142 insertions(+), 9 deletions(-) diff --git a/src/command.zig b/src/command.zig index 5b1f4ea..0ed899b 100644 --- a/src/command.zig +++ b/src/command.zig @@ -68,6 +68,6 @@ pub const PositionalArgs = struct { pub const PositionalArg = struct { name: []const u8, - help: []const u8, + help: ?[]const u8 = null, value_ref: *ValueRef, }; diff --git a/src/help.zig b/src/help.zig index c785da6..deb54e6 100644 --- a/src/help.zig +++ b/src/help.zig @@ -105,8 +105,10 @@ const HelpPrinter = struct { while (it.next()) |parg| { self.printer.write(" "); self.printer.printInColor(self.help_config.color_option, parg.name); - self.printer.printSpaces(max_arg_width - parg.name.len + 3); - self.printer.write(parg.help); + if (parg.help) |help| { + self.printer.printSpaces(max_arg_width - parg.name.len + 3); + self.printer.write(help); + } self.printer.printNewLine(); } } diff --git a/src/parser.zig b/src/parser.zig index 24426a7..e01fac3 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -169,6 +169,9 @@ pub fn Parser(comptime Iterator: type) type { if (posArgRef.value_type == vref.ValueType.single) { self.position_argument_ix += 1; } + } else { + self.error_data = ErrorData{ .entity_name = cmd.name }; + return error.CommandDoesNotHavePositionalArguments; } }, } diff --git a/src/tests.zig b/src/tests.zig index 688a074..5d66352 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -9,6 +9,7 @@ const AppRunner = @import("app_runner.zig").AppRunner; const expect = std.testing.expect; const alloc = std.testing.allocator; +const expectError = std.testing.expectError; const StringSliceIterator = struct { items: []const []const u8, @@ -30,13 +31,13 @@ fn runner() *AppRunner { } fn run(app: *const command.App, items: []const []const u8) !void { - const it = StringSliceIterator{ - .items = items, - }; - - var parser = try Parser(StringSliceIterator).init(app, it, alloc); + var parser = try Parser(StringSliceIterator).init( + app, + StringSliceIterator{ .items = items }, + alloc, + ); + defer parser.deinit(); _ = try parser.parse(); - parser.deinit(); } fn dummy_action() !void {} @@ -305,3 +306,130 @@ test "parse enums" { alloc.free(aa); } + +test "unknown option" { + var r = runner(); + defer r.deinit(); + var aa: []const u8 = undefined; + const opt = command.Option{ + .long_name = "aa", + .short_alias = 'a', + .help = "option aa", + .value_ref = r.mkRef(&aa), + }; + + try expectError(error.UnknownOption, runOptions(&.{ "abc", "--bad", "val" }, &.{opt})); + + try expectError(error.UnknownOptionAlias, runOptions(&.{ "abc", "-b", "val" }, &.{opt})); +} + +test "unknown subcommand" { + var r = runner(); + defer r.deinit(); + + const app = command.App{ + .command = command.Command{ + .name = "abc", + .target = command.CommandTarget{ + .subcommands = &.{}, + }, + }, + }; + + try expectError(error.UnknownSubcommand, run(&app, &.{ "abc", "bad" })); +} + +test "missing subcommand" { + var r = runner(); + defer r.deinit(); + + const app = command.App{ + .command = command.Command{ + .name = "abc", + .target = command.CommandTarget{ + .subcommands = &.{}, + }, + }, + }; + + try expectError(error.MissingSubcommand, run(&app, &.{"abc"})); + try expectError(error.CommandDoesNotHavePositionalArguments, run(&app, &.{ "abc", "--", "3" })); +} + +test "missing required option" { + var r = runner(); + defer r.deinit(); + var aa: []const u8 = undefined; + const opt = command.Option{ + .long_name = "aa", + .short_alias = 'a', + .required = true, + .help = "option aa", + .value_ref = r.mkRef(&aa), + }; + + try expectError(error.MissingRequiredOption, runOptions(&.{"abc"}, &.{opt})); + try expectError(error.MissingOptionValue, runOptions(&.{ "abc", "--aa" }, &.{opt})); +} + +test "missing positional argument" { + var r = runner(); + defer r.deinit(); + + var x: usize = 0; + + const app = command.App{ + .command = command.Command{ + .name = "abc", + .target = command.CommandTarget{ + .action = command.CommandAction{ + .positional_args = command.PositionalArgs{ + .required = &.{ + command.PositionalArg{ + .name = "PA1", + .value_ref = r.mkRef(&x), + }, + }, + }, + .exec = dummy_action, + }, + }, + }, + }; + + try expectError(error.MissingRequiredPositionalArgument, run(&app, &.{"abc"})); + try expectError(error.UnexpectedPositionalArgument, run(&app, &.{ "abc", "3", "4" })); +} + +test "command without positional arguments" { + var r = runner(); + defer r.deinit(); + + const app = command.App{ + .command = command.Command{ + .name = "abc", + .target = command.CommandTarget{ + .action = command.CommandAction{ + .exec = dummy_action, + }, + }, + }, + }; + + try expectError(error.CommandDoesNotHavePositionalArguments, run(&app, &.{ "abc", "3", "abc" })); +} + +test "invalid value" { + var r = runner(); + defer r.deinit(); + var aa: usize = undefined; + const opt = command.Option{ + .long_name = "aa", + .short_alias = 'a', + .required = true, + .help = "option aa", + .value_ref = r.mkRef(&aa), + }; + + try expectError(error.InvalidValue, runOptions(&.{ "abc", "--aa", "bad" }, &.{opt})); +}