From bbbcaa66f7fe512fac3ed9bc8845519297109b53 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Fri, 6 May 2016 12:56:26 -0600 Subject: add full test suite --- test.cxx | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 233 insertions(+), 29 deletions(-) (limited to 'test.cxx') diff --git a/test.cxx b/test.cxx index d7eb11d..c39e000 100644 --- a/test.cxx +++ b/test.cxx @@ -6,38 +6,242 @@ #include -int main(int argc, char **argv) +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +TEST_CASE("Help flag throws Help exception", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); - args::ArgFlag integer(parser, "integer", "The integer flag", args::Matcher({'i'})); - args::ArgFlagList characters(parser, "characters", "The character flag", args::Matcher({'c'})); - args::PosArg foo(parser, "foo", "The foo position"); - args::PosArgList numbers(parser, "numbers", "The numbers position list"); - try - { - parser.ParseCLI(argc, argv); - } - catch (args::Help) - { - std::cout << parser.Help(); - return 0; - } - catch (args::ParseError e) - { - std::cerr << e.what() << std::endl; - std::cerr << parser.Help(); - return 1; - } - catch (args::ValidationError e) + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--help"}), args::Help); +} + +TEST_CASE("Unknown flags throw exceptions", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--Help"}), args::ParseError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-H"}), args::ParseError); +} + +TEST_CASE("Boolean flags work as expected, with clustering", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::Flag foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::Flag bar(parser, "BAR", "test flag", args::Matcher({'b'}, {"bar"})); + args::Flag baz(parser, "BAZ", "test flag", args::Matcher({'a'}, {"baz"})); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher({'x'}, {"bix"})); + parser.ParseArgs(std::vector{"--baz", "-fb"}); + REQUIRE(foo); + REQUIRE(bar); + REQUIRE(baz); + REQUIRE_FALSE(bix); +} + +TEST_CASE("Argument flags work as expected, with clustering", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::ArgFlag foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::Flag bar(parser, "BAR", "test flag", args::Matcher({'b'}, {"bar"})); + args::ArgFlag baz(parser, "BAZ", "test flag", args::Matcher({'a'}, {"baz"})); + args::ArgFlag bim(parser, "BAZ", "test flag", args::Matcher({'B'}, {"bim"})); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher({'x'}, {"bix"})); + parser.ParseArgs(std::vector{"-bftest", "--baz=7.555e2", "--bim", "c"}); + REQUIRE(foo); + REQUIRE(foo.value == "test"); + REQUIRE(bar); + REQUIRE(baz); + REQUIRE((baz.value > 755.49 && baz.value < 755.51)); + REQUIRE(bim); + REQUIRE(bim.value == 'c'); + REQUIRE_FALSE(bix); +} + +TEST_CASE("Invalid argument parsing throws parsing exceptions", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::ArgFlag foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo=7.5"}), args::ParseError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7a"}), args::ParseError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7e4"}), args::ParseError); +} + +TEST_CASE("Argument flag lists work as expected", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::ArgFlagList foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + parser.ParseArgs(std::vector{"--foo=7", "-f2", "-f", "9", "--foo", "42"}); + REQUIRE((foo.values == std::vector{7, 2, 9, 42})); +} + +TEST_CASE("Positional arguments and positional argument lists work as expected", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::PosArg foo(parser, "FOO", "test flag"); + args::PosArg bar(parser, "BAR", "test flag"); + args::PosArgList baz(parser, "BAZ", "test flag"); + parser.ParseArgs(std::vector{"this is a test flag", "0", "a", "b", "c", "x", "y", "z"}); + REQUIRE(foo); + REQUIRE((foo.value == "this is a test flag")); + REQUIRE(bar); + REQUIRE(!bar.value); + REQUIRE(baz); + REQUIRE((baz.values == std::vector{'a', 'b', 'c', 'x', 'y', 'z'})); +} + +TEST_CASE("Positionals that are unspecified evaluate false", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::PosArg foo(parser, "FOO", "test flag"); + args::PosArg bar(parser, "BAR", "test flag"); + args::PosArgList baz(parser, "BAZ", "test flag"); + parser.ParseArgs(std::vector{"this is a test flag again"}); + REQUIRE(foo); + REQUIRE((foo.value == "this is a test flag again")); + REQUIRE_FALSE(bar); + REQUIRE_FALSE(baz); +} + +TEST_CASE("Additional positionals throw an exception", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::PosArg foo(parser, "FOO", "test flag"); + args::PosArg bar(parser, "BAR", "test flag"); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"this is a test flag again", "1", "this has no positional available"}), args::ParseError); +} + +TEST_CASE("Argument groups should throw when validation fails", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::Group xorgroup(parser, "this group provides xor validation", args::Group::Validators::Xor); + args::Flag a(xorgroup, "a", "test flag", args::Matcher({'a'})); + args::Flag b(xorgroup, "b", "test flag", args::Matcher({'b'})); + args::Flag c(xorgroup, "c", "test flag", args::Matcher({'c'})); + args::Group nxor(parser, "this group provides all-or-none (nxor) validation", args::Group::Validators::AllOrNone); + args::Flag d(nxor, "d", "test flag", args::Matcher({'d'})); + args::Flag e(nxor, "e", "test flag", args::Matcher({'e'})); + args::Flag f(nxor, "f", "test flag", args::Matcher({'f'})); + args::Group atleastone(parser, "this group provides at-least-one validation", args::Group::Validators::AtLeastOne); + args::Flag g(atleastone, "g", "test flag", args::Matcher({'g'})); + args::Flag h(atleastone, "h", "test flag", args::Matcher({'h'})); + // Needs g or h + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a"}), args::ValidationError); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-g", "-a"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-a"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-gh", "-a"})); + // Xor stuff + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g"}), args::ValidationError); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-b"})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ab"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ac"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-abc"}), args::ValidationError); + // Nxor stuff + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-a"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-h", "-adef"})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-ad"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-adf"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g", "-aef"}), args::ValidationError); +} + +TEST_CASE("Argument groups should nest", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::Group xorgroup(parser, "this group provides xor validation", args::Group::Validators::Xor); + args::Flag a(xorgroup, "a", "test flag", args::Matcher({'a'})); + args::Flag b(xorgroup, "b", "test flag", args::Matcher({'b'})); + args::Flag c(xorgroup, "c", "test flag", args::Matcher({'c'})); + args::Group nxor(xorgroup, "this group provides all-or-none (nxor) validation", args::Group::Validators::AllOrNone); + args::Flag d(nxor, "d", "test flag", args::Matcher({'d'})); + args::Flag e(nxor, "e", "test flag", args::Matcher({'e'})); + args::Flag f(nxor, "f", "test flag", args::Matcher({'f'})); + args::Group atleastone(xorgroup, "this group provides at-least-one validation", args::Group::Validators::AtLeastOne); + args::Flag g(atleastone, "g", "test flag", args::Matcher({'g'})); + args::Flag h(atleastone, "h", "test flag", args::Matcher({'h'})); + // Nothing actually matches, because nxor validates properly when it's empty, + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-a", "-d"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-c", "-f"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-de", "-f"})); + REQUIRE_NOTHROW(parser.ParseArgs(std::vector{"-gh", "-f"})); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-g"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-b"}), args::ValidationError); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-a", "-dg"}), args::ValidationError); +} + +#include + +std::istream& operator>>(std::istream& is, std::tuple& ints) +{ + is >> std::get<0>(ints); + is.get(); + is >> std::get<1>(ints); + return is; +} + +void DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) +{ + size_t commapos = 0; + std::get<0>(destination) = std::stod(value, &commapos); + std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); +} + +TEST_CASE("Custom types work", "[args]") +{ { - std::cerr << e.what() << std::endl; - std::cerr << parser.Help(); - return 1; + args::ArgumentParser parser("This is a test program."); + args::PosArg> ints(parser, "INTS", "This takes a pair of integers."); + args::PosArg, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); + REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"1.2,2", "3.8,4"}), args::ParseError); } - if (integer) { std::cout << "i: " << integer.value << std::endl; } - if (characters) { for (const auto ch: characters.values) { std::cout << "c: " << ch << std::endl; } } - if (foo) { std::cout << "f: " << foo.value << std::endl; } - if (numbers) { for (const auto nm: numbers.values) { std::cout << "n: " << nm << std::endl; } } - return 0; + args::ArgumentParser parser("This is a test program."); + args::PosArg> ints(parser, "INTS", "This takes a pair of integers."); + args::PosArg, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); + parser.ParseArgs(std::vector{"1,2", "3.8,4"}); + REQUIRE(std::get<0>(ints.value) == 1); + REQUIRE(std::get<1>(ints.value) == 2); + REQUIRE((std::get<0>(doubles.value) > 3.79 && std::get<0>(doubles.value) < 3.81)); + REQUIRE((std::get<1>(doubles.value) > 3.99 && std::get<1>(doubles.value) < 4.01)); +} + +TEST_CASE("Custom parser prefixes (dd-style)", "[args]") +{ + args::ArgumentParser parser("This command likes to break your disks"); + parser.LongPrefix(""); + parser.LongSeparator("="); + args::HelpFlag help(parser, "HELP", "Show this help menu.", args::Matcher({"help"})); + args::ArgFlag bs(parser, "BYTES", "Block size", args::Matcher({"bs"}), 512); + args::ArgFlag skip(parser, "BYTES", "Bytes to skip", args::Matcher({"skip"}), 0); + args::ArgFlag input(parser, "BLOCK SIZE", "Block size", args::Matcher({"if"})); + args::ArgFlag output(parser, "BLOCK SIZE", "Block size", args::Matcher({"of"})); + parser.ParseArgs(std::vector{"skip=8", "if=/dev/null"}); + REQUIRE_FALSE(bs); + REQUIRE(bs.value == 512); + REQUIRE(skip); + REQUIRE(skip.value == 8); + REQUIRE(input); + REQUIRE(input.value == "/dev/null"); + REQUIRE_FALSE(output); +} + +TEST_CASE("Custom parser prefixes (Some Windows styles)", "[args]") +{ + args::ArgumentParser parser("This command likes to break your disks"); + parser.LongPrefix("/"); + parser.LongSeparator(":"); + args::HelpFlag help(parser, "HELP", "Show this help menu.", args::Matcher({"help"})); + args::ArgFlag bs(parser, "BYTES", "Block size", args::Matcher({"bs"}), 512); + args::ArgFlag skip(parser, "BYTES", "Bytes to skip", args::Matcher({"skip"}), 0); + args::ArgFlag input(parser, "BLOCK SIZE", "Block size", args::Matcher({"if"})); + args::ArgFlag output(parser, "BLOCK SIZE", "Block size", args::Matcher({"of"})); + parser.ParseArgs(std::vector{"/skip:8", "/if:/dev/null"}); + REQUIRE_FALSE(bs); + REQUIRE(bs.value == 512); + REQUIRE(skip); + REQUIRE(skip.value == 8); + REQUIRE(input); + REQUIRE(input.value == "/dev/null"); + REQUIRE_FALSE(output); } -- cgit v1.2.1