/* Copyright © 2016 Taylor C. Richberger * This code is released under the license described in the LICENSE file */ #include #include #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"})); 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]") { { 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); } 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); }