aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/cxxopts/test/options.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-11-07 14:49:13 +0100
committerGitHub Enterprise <[email protected]>2025-11-07 14:49:13 +0100
commit24e43a913f29ac3b314354e8ce5175f135bcc64f (patch)
treeca442937ceeb63461012b33a4576e9835099f106 /thirdparty/cxxopts/test/options.cpp
parentget oplog attachments (#622) (diff)
downloadzen-24e43a913f29ac3b314354e8ce5175f135bcc64f.tar.xz
zen-24e43a913f29ac3b314354e8ce5175f135bcc64f.zip
switch to xmake for package management (#611)
This change removes our dependency on vcpkg for package management, in favour of bringing some code in-tree in the `thirdparty` folder as well as using the xmake build-in package management feature. For the latter, all the package definitions are maintained in the zen repo itself, in the `repo` folder. It should now also be easier to build the project as it will no longer depend on having the right version of vcpkg installed, which has been a common problem for new people coming in to the codebase. Now you should only need xmake to build. * Bumps xmake requirement on github runners to 2.9.9 to resolve an issue where xmake on Windows invokes cmake with `v144` toolchain which does not exist * BLAKE3 is now in-tree at `thirdparty/blake3` * cpr is now in-tree at `thirdparty/cpr` * cxxopts is now in-tree at `thirdparty/cxxopts` * fmt is now in-tree at `thirdparty/fmt` * robin-map is now in-tree at `thirdparty/robin-map` * ryml is now in-tree at `thirdparty/ryml` * sol2 is now in-tree at `thirdparty/sol2` * spdlog is now in-tree at `thirdparty/spdlog` * utfcpp is now in-tree at `thirdparty/utfcpp` * xmake package repo definitions is in `repo` * implemented support for sanitizers. ASAN is supported on windows, TSAN, UBSAN, MSAN etc are supported on Linux/MacOS though I have not yet tested it extensively on MacOS * the zencore encryption implementation also now supports using mbedTLS which is used on MacOS, though for now we still use openssl on Linux * crashpad * bumps libcurl to 8.11.0 (from 8.8.0) which should address a rare build upload bug
Diffstat (limited to 'thirdparty/cxxopts/test/options.cpp')
-rw-r--r--thirdparty/cxxopts/test/options.cpp1028
1 files changed, 1028 insertions, 0 deletions
diff --git a/thirdparty/cxxopts/test/options.cpp b/thirdparty/cxxopts/test/options.cpp
new file mode 100644
index 000000000..24fc7068a
--- /dev/null
+++ b/thirdparty/cxxopts/test/options.cpp
@@ -0,0 +1,1028 @@
+#include "catch.hpp"
+#include <iostream>
+
+#include <initializer_list>
+
+#include "cxxopts.hpp"
+
+class Argv {
+ public:
+
+ Argv(std::initializer_list<const char*> args)
+ : m_argv(new const char*[args.size()])
+ , m_argc(static_cast<int>(args.size()))
+ {
+ int i = 0;
+ auto iter = args.begin();
+ while (iter != args.end()) {
+ auto len = strlen(*iter) + 1;
+ auto ptr = std::unique_ptr<char[]>(new char[len]);
+
+ strcpy(ptr.get(), *iter);
+ m_args.push_back(std::move(ptr));
+ m_argv.get()[i] = m_args.back().get();
+
+ ++iter;
+ ++i;
+ }
+ }
+
+ const char** argv() const {
+ return m_argv.get();
+ }
+
+ int argc() const {
+ return m_argc;
+ }
+
+ private:
+
+ std::vector<std::unique_ptr<char[]>> m_args{};
+ std::unique_ptr<const char*[]> m_argv;
+ int m_argc;
+};
+
+TEST_CASE("Basic options", "[options]")
+{
+
+ cxxopts::Options options("tester", " - test basic options");
+
+ options.add_options()
+ ("long", "a long option")
+ ("s,short", "a short option")
+ ("quick,brown", "An option with multiple long names and no short name")
+ ("f,ox,jumped", "An option with multiple long names and a short name")
+ ("over,z,lazy,dog", "An option with multiple long names and a short name, not listed first")
+ ("value", "an option with a value", cxxopts::value<std::string>())
+ ("a,av", "a short option with a value", cxxopts::value<std::string>())
+ ("6,six", "a short number option")
+ ("p, space", "an option with space between short and long")
+ ("period.delimited", "an option with a period in the long name")
+ ("nothing", "won't exist", cxxopts::value<std::string>())
+ ;
+
+ Argv argv({
+ "tester",
+ "--long",
+ "-s",
+ "--value",
+ "value",
+ "-a",
+ "b",
+ "-6",
+ "-p",
+ "--space",
+ "--quick",
+ "--ox",
+ "-f",
+ "--brown",
+ "-z",
+ "--over",
+ "--dog",
+ "--lazy",
+ "--period.delimited",
+ });
+
+ auto** actual_argv = argv.argv();
+ auto argc = argv.argc();
+
+ auto result = options.parse(argc, actual_argv);
+
+ CHECK(result.count("long") == 1);
+ CHECK(result.count("s") == 1);
+ CHECK(result.count("value") == 1);
+ CHECK(result.count("a") == 1);
+ CHECK(result["value"].as<std::string>() == "value");
+ CHECK(result["a"].as<std::string>() == "b");
+ CHECK(result.count("6") == 1);
+ CHECK(result.count("p") == 2);
+ CHECK(result.count("space") == 2);
+ CHECK(result.count("quick") == 2);
+ CHECK(result.count("f") == 2);
+ CHECK(result.count("z") == 4);
+ CHECK(result.count("period.delimited") == 1);
+
+ auto& arguments = result.arguments();
+ REQUIRE(arguments.size() == 16);
+ CHECK(arguments[0].key() == "long");
+ CHECK(arguments[0].value() == "true");
+ CHECK(arguments[0].as<bool>() == true);
+
+ CHECK(arguments[1].key() == "short");
+ CHECK(arguments[2].key() == "value");
+ CHECK(arguments[3].key() == "av");
+
+ CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value);
+
+ CHECK(options.program() == "tester");
+}
+
+TEST_CASE("Short options", "[options]")
+{
+ cxxopts::Options options("test_short", " - test short options");
+
+ options.add_options()
+ ("a", "a short option", cxxopts::value<std::string>())
+ ("b", "b option")
+ ("c", "c option", cxxopts::value<std::string>());
+
+ Argv argv({"test_short", "-a", "value", "-bcfoo=something"});
+
+ auto actual_argv = argv.argv();
+ auto argc = argv.argc();
+
+ auto result = options.parse(argc, actual_argv);
+
+ CHECK(result.count("a") == 1);
+ CHECK(result["a"].as<std::string>() == "value");
+
+ auto& arguments = result.arguments();
+ REQUIRE(arguments.size() == 3);
+ CHECK(arguments[0].key() == "a");
+ CHECK(arguments[0].value() == "value");
+
+ CHECK(result.count("b") == 1);
+ CHECK(result.count("c") == 1);
+
+ CHECK(result["c"].as<std::string>() == "foo=something");
+
+ REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
+ cxxopts::exceptions::invalid_option_format);
+}
+
+TEST_CASE("No positional", "[positional]")
+{
+ cxxopts::Options options("test_no_positional",
+ " - test no positional options");
+
+ Argv av({"tester", "a", "b", "def"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(argc == 4);
+ CHECK(strcmp(argv[1], "a") == 0);
+}
+
+TEST_CASE("All positional", "[positional]")
+{
+ std::vector<std::string> positional;
+
+ cxxopts::Options options("test_all_positional", " - test all positional");
+ options.add_options()
+ ("positional", "Positional parameters",
+ cxxopts::value<std::vector<std::string>>(positional))
+ ;
+
+ Argv av({"tester", "a", "b", "c"});
+
+ auto argc = av.argc();
+ auto argv = av.argv();
+
+ std::vector<std::string> pos_names = {"positional"};
+
+ options.parse_positional(pos_names.begin(), pos_names.end());
+
+ auto result = options.parse(argc, argv);
+
+ CHECK(result.unmatched().size() == 0);
+ REQUIRE(positional.size() == 3);
+
+ CHECK(positional[0] == "a");
+ CHECK(positional[1] == "b");
+ CHECK(positional[2] == "c");
+}
+
+TEST_CASE("Some positional explicit", "[positional]")
+{
+ cxxopts::Options options("positional_explicit", " - test positional");
+
+ options.add_options()
+ ("input", "Input file", cxxopts::value<std::string>())
+ ("output", "Output file", cxxopts::value<std::string>())
+ ("positional", "Positional parameters",
+ cxxopts::value<std::vector<std::string>>())
+ ;
+
+ options.parse_positional({"input", "output", "positional"});
+
+ Argv av({"tester", "--output", "a", "b", "c", "d"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+
+ CHECK(result.unmatched().size() == 0);
+ CHECK(result.count("output"));
+ CHECK(result["input"].as<std::string>() == "b");
+ CHECK(result["output"].as<std::string>() == "a");
+
+ auto& positional = result["positional"].as<std::vector<std::string>>();
+
+ REQUIRE(positional.size() == 2);
+ CHECK(positional[0] == "c");
+ CHECK(positional[1] == "d");
+}
+
+TEST_CASE("No positional with extras", "[positional]")
+{
+ cxxopts::Options options("posargmaster", "shows incorrect handling");
+ options.add_options()
+ ("dummy", "oh no", cxxopts::value<std::string>())
+ ;
+
+ Argv av({"extras", "--", "a", "b", "c", "d"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+
+ auto& unmatched = result.unmatched();
+ CHECK((unmatched == std::vector<std::string>{"a", "b", "c", "d"}));
+}
+
+TEST_CASE("Positional not valid", "[positional]") {
+ cxxopts::Options options("positional_invalid", "invalid positional argument");
+ options.add_options()
+ ("long", "a long option", cxxopts::value<std::string>())
+ ;
+
+ options.parse_positional("something");
+
+ Argv av({"foobar", "bar", "baz"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
+}
+
+TEST_CASE("Positional with empty arguments", "[positional]") {
+ cxxopts::Options options("positional_with_empty_arguments", "positional with empty argument");
+ options.add_options()
+ ("long", "a long option", cxxopts::value<std::string>())
+ ("program", "program to run", cxxopts::value<std::string>())
+ ("programArgs", "program arguments", cxxopts::value<std::vector<std::string>>())
+ ;
+
+ options.parse_positional("program", "programArgs");
+
+ Argv av({"foobar", "--long", "long_value", "--", "someProgram", "ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", });
+ std::vector<std::string> expected({"ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", });
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ auto actual = result["programArgs"].as<std::vector<std::string>>();
+
+ REQUIRE(result.count("program") == 1);
+ REQUIRE(result["program"].as<std::string>() == "someProgram");
+ REQUIRE(result.count("programArgs") == expected.size());
+ REQUIRE(actual == expected);
+}
+
+TEST_CASE("Positional with list delimiter", "[positional]") {
+ std::string single;
+ std::vector<std::string> positional;
+
+ cxxopts::Options options("test_all_positional_list_delimiter", " - test all positional with list delimiters");
+ options.add_options()
+ ("single", "Single positional param",
+ cxxopts::value<std::string>(single))
+ ("positional", "Positional parameters vector",
+ cxxopts::value<std::vector<std::string>>(positional))
+ ;
+
+ Argv av({"tester", "a,b", "c,d", "e"});
+
+ auto argc = av.argc();
+ auto argv = av.argv();
+
+ std::vector<std::string> pos_names = {"single", "positional"};
+
+ options.parse_positional(pos_names.begin(), pos_names.end());
+
+ auto result = options.parse(argc, argv);
+
+ CHECK(result.unmatched().size() == 0);
+ REQUIRE(positional.size() == 2);
+
+ CHECK(single == "a,b");
+ CHECK(positional[0] == "c,d");
+ CHECK(positional[1] == "e");
+}
+
+TEST_CASE("Empty with implicit value", "[implicit]")
+{
+ cxxopts::Options options("empty_implicit", "doesn't handle empty");
+ options.add_options()
+ ("implicit", "Has implicit", cxxopts::value<std::string>()
+ ->implicit_value("foo"));
+
+ Argv av({"implicit", "--implicit="});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("implicit") == 1);
+ REQUIRE(result["implicit"].as<std::string>() == "");
+}
+
+TEST_CASE("Boolean without implicit value", "[implicit]")
+{
+ cxxopts::Options options("no_implicit", "bool without an implicit value");
+ options.add_options()
+ ("bool", "Boolean without implicit", cxxopts::value<bool>()
+ ->no_implicit_value());
+
+ SECTION("When no value provided") {
+ Argv av({"no_implicit", "--bool"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument);
+ }
+
+ SECTION("With equal-separated true") {
+ Argv av({"no_implicit", "--bool=true"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("bool") == 1);
+ CHECK(result["bool"].as<bool>() == true);
+ }
+
+ SECTION("With equal-separated false") {
+ Argv av({"no_implicit", "--bool=false"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("bool") == 1);
+ CHECK(result["bool"].as<bool>() == false);
+ }
+
+ SECTION("With space-separated true") {
+ Argv av({"no_implicit", "--bool", "true"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("bool") == 1);
+ CHECK(result["bool"].as<bool>() == true);
+ }
+
+ SECTION("With space-separated false") {
+ Argv av({"no_implicit", "--bool", "false"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("bool") == 1);
+ CHECK(result["bool"].as<bool>() == false);
+ }
+}
+
+TEST_CASE("Default values", "[default]")
+{
+ cxxopts::Options options("defaults", "has defaults");
+ options.add_options()
+ ("default", "Has implicit", cxxopts::value<int>()->default_value("42"))
+ ("v,vector", "Default vector", cxxopts::value<std::vector<int>>()
+ ->default_value("1,4"))
+ ;
+
+ SECTION("Sets defaults") {
+ Argv av({"implicit"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("default") == 0);
+ CHECK(result["default"].as<int>() == 42);
+
+ auto& v = result["vector"].as<std::vector<int>>();
+ REQUIRE(v.size() == 2);
+ CHECK(v[0] == 1);
+ CHECK(v[1] == 4);
+ }
+
+ SECTION("When values provided") {
+ Argv av({"implicit", "--default", "5"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("default") == 1);
+ CHECK(result["default"].as<int>() == 5);
+ }
+}
+
+TEST_CASE("Parse into a reference", "[reference]")
+{
+ int value = 0;
+ bool b_value = true;
+
+ cxxopts::Options options("into_reference", "parses into a reference");
+ options.add_options()
+ ("ref", "A reference", cxxopts::value(value))
+ ("bool", "A bool", cxxopts::value(b_value));
+
+ Argv av({"into_reference", "--ref", "42"});
+
+ auto argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+ CHECK(result.count("ref") == 1);
+ CHECK(value == 42);
+ CHECK(result.count("bool") == 0);
+ CHECK(b_value == true);
+}
+
+TEST_CASE("Integers", "[options]")
+{
+ cxxopts::Options options("parses_integers", "parses integers correctly");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<int>>());
+
+ Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("positional") == 7);
+
+ auto& positional = result["positional"].as<std::vector<int>>();
+ REQUIRE(positional.size() == 7);
+ CHECK(positional[0] == 5);
+ CHECK(positional[1] == 6);
+ CHECK(positional[2] == -6);
+ CHECK(positional[3] == 0);
+ CHECK(positional[4] == 0xab);
+ CHECK(positional[5] == 0xaf);
+ CHECK(positional[6] == 0x0);
+}
+
+TEST_CASE("Leading zero integers", "[options]")
+{
+ cxxopts::Options options("parses_integers", "parses integers correctly");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<int>>());
+
+ Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("positional") == 4);
+
+ auto& positional = result["positional"].as<std::vector<int>>();
+ REQUIRE(positional.size() == 4);
+ CHECK(positional[0] == 5);
+ CHECK(positional[1] == 6);
+ CHECK(positional[2] == 0xab);
+ CHECK(positional[3] == 0x1);
+}
+
+TEST_CASE("Unsigned integers", "[options]")
+{
+ cxxopts::Options options("parses_unsigned", "detects unsigned errors");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<unsigned int>>());
+
+ Argv av({"ints", "--", "-2"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
+}
+
+TEST_CASE("Integer bounds", "[integer]")
+{
+ cxxopts::Options options("integer_boundaries", "check min/max integer");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<int8_t>>());
+
+ SECTION("No overflow")
+ {
+ Argv av({"ints", "--", "127", "-128", "0x7f", "-0x80", "0x7e"});
+
+ auto argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("positional") == 5);
+
+ auto& positional = result["positional"].as<std::vector<int8_t>>();
+ CHECK(positional[0] == 127);
+ CHECK(positional[1] == -128);
+ CHECK(positional[2] == 0x7f);
+ CHECK(positional[3] == -0x80);
+ CHECK(positional[4] == 0x7e);
+ }
+}
+
+TEST_CASE("Overflow on boundary", "[integer]")
+{
+ using namespace cxxopts::values;
+
+ int8_t si;
+ int16_t si16;
+ int64_t si64;
+ uint8_t ui;
+ uint16_t ui16;
+ uint64_t ui64;
+
+ CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type);
+
+ CHECK_THROWS_AS((integer_parser("65536", ui16)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("75536", ui16)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("32768", si16)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-32769", si16)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-42769", si16)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-75536", si16)), cxxopts::exceptions::incorrect_argument_type);
+
+ CHECK_THROWS_AS((integer_parser("18446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("28446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("9223372036854775808", si64)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-9223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-10223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("-28446744073709551616", si64)), cxxopts::exceptions::incorrect_argument_type);
+}
+
+TEST_CASE("Integer overflow", "[options]")
+{
+ using namespace cxxopts::values;
+
+ cxxopts::Options options("reject_overflow", "rejects overflowing integers");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<int8_t>>());
+
+ Argv av({"ints", "--", "128"});
+
+ auto argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
+
+ int integer = 0;
+ CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type);
+ CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type);
+}
+
+TEST_CASE("Floats", "[options]")
+{
+ cxxopts::Options options("parses_floats", "parses floats correctly");
+ options.add_options()
+ ("double", "Double precision", cxxopts::value<double>())
+ ("positional", "Floats", cxxopts::value<std::vector<float>>());
+
+ Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("double") == 1);
+ REQUIRE(result.count("positional") == 4);
+
+ CHECK(result["double"].as<double>() == 0.5);
+
+ auto& positional = result["positional"].as<std::vector<float>>();
+ CHECK(positional[0] == 4);
+ CHECK(positional[1] == -4);
+ CHECK(positional[2] == 1.5e6);
+ CHECK(positional[3] == -1.5e6);
+}
+
+TEST_CASE("Invalid integers", "[integer]") {
+ cxxopts::Options options("invalid_integers", "rejects invalid integers");
+ options.add_options()
+ ("positional", "Integers", cxxopts::value<std::vector<int>>());
+
+ Argv av({"ints", "--", "Ae"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse_positional("positional");
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
+}
+
+TEST_CASE("Booleans", "[boolean]") {
+ cxxopts::Options options("parses_floats", "parses floats correctly");
+ options.add_options()
+ ("bool", "A Boolean", cxxopts::value<bool>())
+ ("debug", "Debugging", cxxopts::value<bool>())
+ ("timing", "Timing", cxxopts::value<bool>())
+ ("verbose", "Verbose", cxxopts::value<bool>())
+ ("dry-run", "Dry Run", cxxopts::value<bool>())
+ ("noExplicitDefault", "No Explicit Default", cxxopts::value<bool>())
+ ("defaultTrue", "Timing", cxxopts::value<bool>()->default_value("true"))
+ ("defaultFalse", "Timing", cxxopts::value<bool>()->default_value("false"))
+ ("others", "Other arguments", cxxopts::value<std::vector<std::string>>())
+ ;
+
+ options.parse_positional("others");
+
+ Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("bool") == 1);
+ REQUIRE(result.count("debug") == 1);
+ REQUIRE(result.count("timing") == 1);
+ REQUIRE(result.count("verbose") == 1);
+ REQUIRE(result.count("dry-run") == 1);
+ REQUIRE(result.count("noExplicitDefault") == 0);
+ REQUIRE(result.count("defaultTrue") == 0);
+ REQUIRE(result.count("defaultFalse") == 0);
+
+ CHECK(result["bool"].as<bool>() == false);
+ CHECK(result["debug"].as<bool>() == true);
+ CHECK(result["timing"].as<bool>() == true);
+ CHECK(result["verbose"].as<bool>() == true);
+ CHECK(result["dry-run"].as<bool>() == false);
+ CHECK(result["noExplicitDefault"].as<bool>() == false);
+ CHECK(result["defaultTrue"].as<bool>() == true);
+ CHECK(result["defaultFalse"].as<bool>() == false);
+
+ REQUIRE(result.count("others") == 1);
+}
+
+TEST_CASE("std::vector", "[vector]") {
+ std::vector<double> vector;
+ cxxopts::Options options("vector", " - tests vector");
+ options.add_options()
+ ("vector", "an vector option", cxxopts::value<std::vector<double>>(vector));
+
+ Argv av({"vector", "--vector", "1,-2.1,3,4.5"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse(argc, argv);
+
+ REQUIRE(vector.size() == 4);
+ CHECK(vector[0] == 1);
+ CHECK(vector[1] == -2.1);
+ CHECK(vector[2] == 3);
+ CHECK(vector[3] == 4.5);
+}
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+TEST_CASE("std::optional", "[optional]") {
+ std::optional<std::string> optional;
+ std::optional<bool> opt_bool;
+ cxxopts::Options options("optional", " - tests optional");
+ options.add_options()
+ ("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional))
+ ("optional_bool", "an boolean optional", cxxopts::value<std::optional<bool>>(opt_bool)->default_value("false"));
+
+ Argv av({"optional", "--optional", "foo", "--optional_bool", "true"});
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ options.parse(argc, argv);
+
+ REQUIRE(optional.has_value());
+ CHECK(*optional == "foo");
+ CHECK(opt_bool.has_value());
+ CHECK(*opt_bool);
+}
+#endif
+
+TEST_CASE("Unrecognised options", "[options]") {
+ cxxopts::Options options("unknown_options", " - test unknown options");
+
+ options.add_options()
+ ("long", "a long option")
+ ("s,short", "a short option");
+
+ Argv av({
+ "unknown_options",
+ "--unknown",
+ "--long",
+ "-su",
+ "--another_unknown",
+ "-a",
+ });
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ SECTION("Default behaviour") {
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
+ }
+
+ SECTION("After allowing unrecognised options") {
+ options.allow_unrecognised_options();
+ auto result = options.parse(argc, argv);
+ auto& unmatched = result.unmatched();
+ CHECK((unmatched == std::vector<std::string>{"--unknown", "-u", "--another_unknown", "-a"}));
+ }
+}
+
+TEST_CASE("Allow bad short syntax", "[options]") {
+ cxxopts::Options options("unknown_options", " - test unknown options");
+
+ options.add_options()
+ ("long", "a long option")
+ ("s,short", "a short option");
+
+ Argv av({
+ "--ab?",
+ "-?b?#@"
+ });
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ SECTION("Default behaviour") {
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
+ }
+
+ SECTION("After allowing unrecognised options") {
+ options.allow_unrecognised_options();
+ CHECK_NOTHROW(options.parse(argc, argv));
+ REQUIRE(argc == 2);
+ CHECK_THAT(argv[1], Catch::Equals("-?b?#@"));
+ }
+}
+
+TEST_CASE("Invalid option syntax", "[options]") {
+ cxxopts::Options options("invalid_syntax", " - test invalid syntax");
+
+ Argv av({
+ "invalid_syntax",
+ "--a",
+ });
+
+ auto** argv = av.argv();
+ auto argc = av.argc();
+
+ SECTION("Default behaviour") {
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
+ }
+}
+
+TEST_CASE("Options empty", "[options]") {
+ cxxopts::Options options("Options list empty", " - test empty option list");
+ options.add_options();
+ options.add_options("");
+ options.add_options("", {});
+ options.add_options("test");
+
+ Argv argv_({
+ "test",
+ "--unknown"
+ });
+ auto argc = argv_.argc();
+ auto** argv = argv_.argv();
+
+ CHECK(options.groups().empty());
+ CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
+}
+
+TEST_CASE("Initializer list with group", "[options]") {
+ cxxopts::Options options("Initializer list group", " - test initializer list with group");
+
+ options.add_options("", {
+ {"a, address", "server address", cxxopts::value<std::string>()->default_value("127.0.0.1")},
+ {"p, port", "server port", cxxopts::value<std::string>()->default_value("7110"), "PORT"},
+ });
+
+ cxxopts::Option help{"h,help", "Help"};
+
+ options.add_options("TEST_GROUP", {
+ {"t, test", "test option"},
+ help
+ });
+
+ Argv argv({
+ "test",
+ "--address",
+ "10.0.0.1",
+ "-p",
+ "8000",
+ "-t",
+ });
+ auto** actual_argv = argv.argv();
+ auto argc = argv.argc();
+ auto result = options.parse(argc, actual_argv);
+
+ CHECK(options.groups().size() == 2);
+ CHECK(result.count("address") == 1);
+ CHECK(result.count("port") == 1);
+ CHECK(result.count("test") == 1);
+ CHECK(result.count("help") == 0);
+ CHECK(result["address"].as<std::string>() == "10.0.0.1");
+ CHECK(result["port"].as<std::string>() == "8000");
+ CHECK(result["test"].as<bool>() == true);
+}
+
+TEST_CASE("Option add with add_option(string, Option)", "[options]") {
+ cxxopts::Options options("Option add with add_option", " - test Option add with add_option(string, Option)");
+
+ cxxopts::Option option_1("t,test", "test option", cxxopts::value<int>()->default_value("7"), "TEST");
+
+ options.add_option("", option_1);
+ options.add_option("TEST", {"a,aggregate", "test option 2", cxxopts::value<int>(), "AGGREGATE"});
+ options.add_option("TEST", {"multilong,m,multilong-alias", "test option 3", cxxopts::value<int>(), "An option with multiple long names"});
+
+ Argv argv_({
+ "test",
+ "--test",
+ "5",
+ "-a",
+ "4",
+ "--multilong-alias",
+ "6"
+ });
+ auto argc = argv_.argc();
+ auto** argv = argv_.argv();
+ auto result = options.parse(argc, argv);
+
+ CHECK(result.arguments().size() == 3);
+ CHECK(options.groups().size() == 2);
+ CHECK(result.count("address") == 0);
+ CHECK(result.count("aggregate") == 1);
+ CHECK(result.count("test") == 1);
+ CHECK(result["aggregate"].as<int>() == 4);
+ CHECK(result["multilong"].as<int>() == 6);
+ CHECK(result["multilong-alias"].as<int>() == 6);
+ CHECK(result["m"].as<int>() == 6);
+ CHECK(result["test"].as<int>() == 5);
+}
+
+TEST_CASE("Const array", "[const]") {
+ const char* const option_list[] = {"empty", "options"};
+ cxxopts::Options options("Empty options", " - test constness");
+ auto result = options.parse(2, option_list);
+}
+
+TEST_CASE("Parameter follow option", "[parameter]") {
+ cxxopts::Options options("param_follow_opt", " - test parameter follow option without space.");
+ options.add_options()
+ ("j,job", "Job", cxxopts::value<std::vector<unsigned>>());
+ Argv av({"implicit",
+ "-j", "9",
+ "--job", "7",
+ "--job=10",
+ "-j5",
+ });
+
+ auto ** argv = av.argv();
+ auto argc = av.argc();
+
+ auto result = options.parse(argc, argv);
+
+ REQUIRE(result.count("job") == 4);
+
+ auto job_values = result["job"].as<std::vector<unsigned>>();
+ CHECK(job_values[0] == 9);
+ CHECK(job_values[1] == 7);
+ CHECK(job_values[2] == 10);
+ CHECK(job_values[3] == 5);
+}
+
+TEST_CASE("Iterator", "[iterator]") {
+ cxxopts::Options options("tester", " - test iterating over parse result");
+
+ options.add_options()
+ ("long", "a long option")
+ ("s,short", "a short option")
+ ("a", "a short-only option")
+ ("value", "an option with a value", cxxopts::value<std::string>())
+ ("default", "an option with default value", cxxopts::value<int>()->default_value("42"))
+ ("nothing", "won't exist", cxxopts::value<std::string>())
+ ;
+
+ Argv argv({
+ "tester",
+ "--long",
+ "-s",
+ "-a",
+ "--value",
+ "value",
+ });
+
+ auto** actual_argv = argv.argv();
+ auto argc = argv.argc();
+
+ auto result = options.parse(argc, actual_argv);
+
+ auto iter = result.begin();
+
+ REQUIRE(iter != result.end());
+ CHECK(iter->key() == "long");
+ CHECK(iter->value() == "true");
+
+ REQUIRE(++iter != result.end());
+ CHECK(iter->key() == "short");
+ CHECK(iter->value() == "true");
+
+ REQUIRE(++iter != result.end());
+ CHECK(iter->key() == "a");
+ CHECK(iter->value() == "true");
+
+ REQUIRE(++iter != result.end());
+ CHECK(iter->key() == "value");
+ CHECK(iter->value() == "value");
+
+ REQUIRE(++iter != result.end());
+ CHECK(iter->key() == "default");
+ CHECK(iter->value() == "42");
+
+ REQUIRE(++iter == result.end());
+}
+
+TEST_CASE("Iterator no args", "[iterator]") {
+ cxxopts::Options options("tester", " - test iterating over parse result");
+
+ options.add_options()
+ ("value", "an option with a value", cxxopts::value<std::string>())
+ ("default", "an option with default value", cxxopts::value<int>()->default_value("42"))
+ ("nothing", "won't exist", cxxopts::value<std::string>())
+ ;
+
+ Argv argv({
+ "tester",
+ });
+
+ auto** actual_argv = argv.argv();
+ auto argc = argv.argc();
+
+ auto result = options.parse(argc, actual_argv);
+
+ auto iter = result.begin();
+
+ REQUIRE(iter != result.end());
+ CHECK(iter->key() == "default");
+ CHECK(iter->value() == "42");
+
+ ++iter;
+ CHECK(iter == result.end());
+}
+
+
+TEST_CASE("No Options help", "[options]")
+{
+ std::vector<std::string> positional;
+
+ cxxopts::Options options("test", "test no options help");
+
+ // explicitly setting custom help empty to overwrite
+ // default "[OPTION...]" when there are no options
+ options.positional_help("<posArg1>...<posArgN>")
+ .custom_help("")
+ .add_options()
+ ("positional", "", cxxopts::value<std::vector<std::string>>(positional));
+
+ Argv av({"test", "posArg1", "posArg2", "posArg3"});
+
+ auto argc = av.argc();
+ auto** argv = av.argv();
+
+ options.parse_positional({"positional"});
+
+ CHECK_NOTHROW(options.parse(argc, argv));
+ CHECK(options.help().find("test <posArg1>...<posArgN>") != std::string::npos);
+}