mirror of https://github.com/jarro2783/cxxopts.git
1990 lines
53 KiB
C++
1990 lines
53 KiB
C++
#include "catch.hpp"
|
|
#include <iostream>
|
|
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
|
|
#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.contains("long"));
|
|
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(result.count("nothing") == 0);
|
|
CHECK_FALSE(result.contains("nothing"));
|
|
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("Providing options values via equal sign", "[options]")
|
|
{
|
|
cxxopts::Options options("test_equal_sign", " - Providing options values via equal sign");
|
|
|
|
options.add_options()
|
|
("o,option", "a option", cxxopts::value<std::string>())
|
|
("b", "bool option as string",
|
|
cxxopts::value<std::string>()->implicit_value("true")->default_value("false"))
|
|
("c", "c option", cxxopts::value<std::string>()->implicit_value("implicit"));
|
|
|
|
struct testcases
|
|
{
|
|
std::string name;
|
|
Argv argv;
|
|
std::vector<std::pair<std::string, std::string>> expValues;
|
|
bool parseException;
|
|
} tests[] = {
|
|
{
|
|
"Short value with =",
|
|
Argv{"test_equal_sign", "-o=hi", "-b=true"},
|
|
{{"o", "hi"}, {"b", "true"}},
|
|
false
|
|
},
|
|
{
|
|
"Short non alphanumeric value with =",
|
|
Argv{"test_equal_sign", "-o==*hi_$&"},
|
|
{{"o", "=*hi_$&"}, {"b", "false"}},
|
|
false
|
|
},
|
|
{
|
|
"Short value with = in the value, not as seperator",
|
|
Argv{"test_equal_sign", "-oDEBUG=1"},
|
|
{{"o", "DEBUG=1"}},
|
|
false
|
|
},
|
|
{
|
|
"Invalid short name with =",
|
|
Argv{"test_equal_sign", "-?=hi"},
|
|
{},
|
|
true
|
|
},
|
|
{
|
|
"Short value without =",
|
|
Argv{"test_equal_sign", "-ohi"},
|
|
{{"o", "hi"}},
|
|
false
|
|
},
|
|
{
|
|
"Short empty value",
|
|
Argv{"test_equal_sign", "-o="},
|
|
{{"o", ""}},
|
|
false
|
|
},
|
|
{
|
|
"Short value grouped",
|
|
Argv{"test_equal_sign", "-bo", "hi"},
|
|
{{"o", "hi"}, {"b", "true"}},
|
|
false
|
|
},
|
|
{
|
|
"Multiple short values",
|
|
Argv{"test_equal_sign", "-o=hi", "-c=bye"},
|
|
{{"o", "hi"}, {"c", "bye"}},
|
|
false
|
|
},
|
|
{
|
|
"Multiple short values",
|
|
Argv{"test_equal_sign", "-o=hi", "-bc"},
|
|
{{"o", "hi"}, {"c", "implicit"}},
|
|
false
|
|
},
|
|
{
|
|
"Grouped short values with implicit value",
|
|
Argv{"test_equal_sign", "-cbo", "hi"},
|
|
{{"o", "hi"}, {"c", "implicit"}, {"b", "true"}},
|
|
false
|
|
},
|
|
{
|
|
"Grouped short values with implicit value with =",
|
|
Argv{"test_equal_sign", "-cbo=hi"},
|
|
{{"o", "=hi"}, {"c", "implicit"}, {"b", "true"}},
|
|
false
|
|
},
|
|
{
|
|
"Explicit value for implicit valued options (Parse Expection)",
|
|
Argv{"test_equal_sign", "-cX"},
|
|
{},
|
|
true
|
|
},
|
|
{
|
|
"Long value with equal",
|
|
Argv{"test_equal_sign", "--option=hi"},
|
|
{{"o", "hi"}},
|
|
false
|
|
},
|
|
{
|
|
"Long value without equal",
|
|
Argv{"test_equal_sign", "--option", "hi"},
|
|
{{"o", "hi"}},
|
|
false
|
|
},
|
|
{
|
|
"Long empty value",
|
|
Argv{"test_equal_sign", "--option="},
|
|
{{"o", ""}},
|
|
false
|
|
},
|
|
};
|
|
|
|
for(const auto& tc : tests) {
|
|
SECTION(tc.name){
|
|
if(tc.parseException) {
|
|
CHECK_THROWS(options.parse(tc.argv.argc(), tc.argv.argv()));
|
|
continue;
|
|
}
|
|
auto result = options.parse(tc.argv.argc(), tc.argv.argv());
|
|
for (const auto& p : tc.expValues) {
|
|
// TODO: Allow the type to be defined in the testcase
|
|
CHECK(result[p.first].as<std::string>() == p.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Valid/Invalid Option names", "[options]")
|
|
{
|
|
const char prog_name[] = "test_invalid_opt_names";
|
|
std::vector<std::pair<std::string, std::string>> invalid_opts = {
|
|
{"Equals short name", "="},
|
|
{"Dash short name", "-"},
|
|
{"Long name starts with =", "a,=abcd"},
|
|
{"Long name starts with dash", "a,-abcd"},
|
|
{"Whitespace as name", " "},
|
|
{"Name is a space", "a, "},
|
|
{"Only comma delimiter given", ","},
|
|
{"Leading comma", ",a"},
|
|
{"Trailing comma after names", "a,ab,"},
|
|
{"Double comma", "a,,ab"},
|
|
{"Empty option name string", ""},
|
|
{"Control character in name", "\x03"},
|
|
{"Tab inside option name", "a,ab,\tac"},
|
|
{"Equals inside option name", "a,ab,ac,ab=cd"},
|
|
{"Two simple option names", "a,b"},
|
|
};
|
|
|
|
std::vector<std::pair<std::string, std::string>> valid_opts = {
|
|
{"Basic", "a,ab"},
|
|
{"Spaces", "a, ab, xyz"},
|
|
{"Arbitrary characters", "#,%%%,/><>?"},
|
|
};
|
|
|
|
for(const auto& t: invalid_opts) {
|
|
SECTION(t.first){
|
|
cxxopts::Options options(prog_name, " - Option names as invalid characters");
|
|
auto option_adder = options.add_options();
|
|
CHECK_THROWS(option_adder(t.second, "description", cxxopts::value<std::string>()));
|
|
}
|
|
}
|
|
for(const auto& t: valid_opts) {
|
|
SECTION(t.first){
|
|
cxxopts::Options options(prog_name, " - Option names as invalid characters");
|
|
auto option_adder = options.add_options();
|
|
CHECK_NOTHROW(option_adder(t.second, "description", cxxopts::value<std::string>()));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Option names as arbitrary characters and values parsing", "[options]")
|
|
{
|
|
const char prog_name[] = "test_name_chars";
|
|
const char implicit_value[] = "implicit";
|
|
struct testcases
|
|
{
|
|
std::string name;
|
|
std::vector<std::string> opt_names;
|
|
Argv argv;
|
|
std::vector<std::pair<std::string, std::string>> expValues;
|
|
bool parseException;
|
|
bool setImplicit;
|
|
} tests[] = {
|
|
{
|
|
"Arbitrary chars",
|
|
{"#,^^","$$$"},
|
|
Argv{prog_name, "-#", "abc", "--$$$=@#$$"},
|
|
{{"#", "abc"}, {"$$$", "@#$$"}},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"Arbitrary chars and spaces",
|
|
{"*, ab-cd","ab_cd"},
|
|
Argv{prog_name, "--ab-cd", "xyz","--ab_cd={}()"},
|
|
{{"ab-cd", "xyz"}, {"ab_cd", "{}()"}},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"Arbitrary character as single dot",
|
|
{"."},
|
|
Argv{prog_name, "-.=.."},
|
|
{{".", ".."}},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"Multiple Arbitrary characters as short options",
|
|
{"@",":","?","[","}","*","~","!"},
|
|
Argv{prog_name, "-@:?[}*~!"},
|
|
{{"@", implicit_value}, {"[", implicit_value}, {"!", implicit_value}},
|
|
false,
|
|
true,
|
|
}
|
|
};
|
|
|
|
for(const auto& tc : tests) {
|
|
SECTION(tc.name){
|
|
cxxopts::Options options(prog_name, " - Option names as arbitrary characters");
|
|
auto option_adder = options.add_options();
|
|
for(const auto& opt_name: tc.opt_names) {
|
|
if(tc.setImplicit){
|
|
option_adder(opt_name, "description", cxxopts::value<std::string>()->implicit_value(implicit_value));
|
|
}
|
|
else{
|
|
option_adder(opt_name, "description", cxxopts::value<std::string>());
|
|
}
|
|
}
|
|
if(tc.parseException) {
|
|
CHECK_THROWS(options.parse(tc.argv.argc(), tc.argv.argv()));
|
|
continue;
|
|
}
|
|
auto result = options.parse(tc.argv.argc(), tc.argv.argv());
|
|
for (const auto& p : tc.expValues) {
|
|
CHECK(result[p.first].as<std::string>() == p.second);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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("Multiple calls to parse_positionals", "[positional]") {
|
|
const char prog_name[] = "multiple_parse_positionals";
|
|
|
|
Argv av{prog_name, "1", "2", "3", "last"};
|
|
|
|
struct testcase{
|
|
std::string name;
|
|
bool use_append_for_c;
|
|
std::vector<std::string> unmatched;
|
|
std::vector<std::pair<std::string, int>> exp_values;
|
|
} tests_eq[] = {
|
|
{"Append c", true, {"last"}, {{"a", 1}, {"b", 2}, {"c", 3}}},
|
|
{"Replace c", false, {"2", "3", "last"}, {{"c", 1}}},
|
|
};
|
|
|
|
for(const auto& tc : tests_eq) {
|
|
SECTION(tc.name){
|
|
cxxopts::Options options(prog_name, "Multiple calls to parse_positionals");
|
|
options.add_options()
|
|
("a", "First value", cxxopts::value<int>())
|
|
("b", "First value", cxxopts::value<int>())
|
|
("c", "First value", cxxopts::value<int>());
|
|
options.parse_positional({"a", "b"});
|
|
if(tc.use_append_for_c) {
|
|
options.parse_positional({"c"}, cxxopts::PositionalMode::Append);
|
|
} else {
|
|
options.parse_positional({"c"});
|
|
}
|
|
|
|
auto res = options.parse(av.argc(), av.argv());
|
|
auto um = res.unmatched();
|
|
CHECK(um.size() == tc.unmatched.size());
|
|
for(size_t i=0; i<um.size(); ++i) {
|
|
CHECK(um[i] == tc.unmatched[i]);
|
|
}
|
|
|
|
for (const auto& p : tc.exp_values) {
|
|
CHECK(res[p.first].as<int>() == p.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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("Implicit value with disabled_args", "[no_value]")
|
|
{
|
|
const char prog_name[] = "disabled_args";
|
|
cxxopts::Options options(prog_name, "Implicit value with disabled args");
|
|
options.add_options()
|
|
("b,bool", "bool with disabled_args", cxxopts::value<bool>()->implicit_value("true", cxxopts::ImplicitArgPolicy::Disabled))
|
|
("s,string", "string with disabled_args", cxxopts::value<std::string>()->implicit_value("value", cxxopts::ImplicitArgPolicy::Disabled))
|
|
("x,string2", "string with first disabled and then enabled args",
|
|
cxxopts::value<std::string>()->implicit_value("value", cxxopts::ImplicitArgPolicy::Disabled)->no_implicit_value());
|
|
struct testcase{
|
|
std::string name;
|
|
Argv argv;
|
|
} tests_eq[] = {
|
|
{
|
|
"exception due to value passed in long arg",
|
|
Argv{prog_name, "--bool=true"},
|
|
},
|
|
{
|
|
"exception due to value passed in short arg",
|
|
Argv{prog_name, "-b=true"},
|
|
},
|
|
{
|
|
"exception due to string value",
|
|
Argv{prog_name, "-b", "--string=something_else"},
|
|
}
|
|
};
|
|
|
|
for(const auto& tc : tests_eq) {
|
|
SECTION(tc.name){
|
|
CHECK_THROWS_AS(options.parse(tc.argv.argc(), tc.argv.argv()),
|
|
cxxopts::exceptions::specified_disabled_args);
|
|
}
|
|
}
|
|
|
|
SECTION("unmatched string value without ="){
|
|
Argv argv{prog_name, "--string", "new_value"};
|
|
auto result = options.parse(argv.argc(), argv.argv());
|
|
CHECK(result.unmatched().size() == 1);
|
|
CHECK(result.unmatched()[0] == "new_value");
|
|
CHECK(result["string"].as<std::string>() == "value");
|
|
}
|
|
SECTION("unmatched short value without ="){
|
|
Argv argv{prog_name, "-s", "new_value"};
|
|
auto result = options.parse(argv.argc(), argv.argv());
|
|
CHECK(result.unmatched().size() == 1);
|
|
CHECK(result.unmatched()[0] == "new_value");
|
|
CHECK(result["string"].as<std::string>() == "value");
|
|
}
|
|
SECTION("exception due to short value grouped"){
|
|
Argv argv{prog_name, "-snew_value"};
|
|
// it will be considered as -s -n -e ... because -s has implicit value
|
|
CHECK_THROWS_AS(options.parse(argv.argc(), argv.argv()),
|
|
cxxopts::exceptions::no_such_option);
|
|
}
|
|
SECTION("No exception"){
|
|
Argv argv{prog_name, "--string", "-b", "--string2=new_value"};
|
|
auto result = options.parse(argv.argc(), argv.argv());
|
|
CHECK(result["bool"].as<bool>() == true);
|
|
CHECK(result["string"].as<std::string>() == "value");
|
|
CHECK(result["string2"].as<std::string>() == "new_value");
|
|
}
|
|
}
|
|
|
|
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_NOTHROW((integer_parser("-9223372036854775808", si64)));
|
|
CHECK(si64 == (std::numeric_limits<int64_t>::min)());
|
|
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);
|
|
}
|
|
|
|
TEST_CASE("empty std::vector", "[vector]") {
|
|
std::vector<double> double_vector;
|
|
std::vector<std::string> string_vector;
|
|
|
|
cxxopts::Options options("empty vector", " - tests behaviour of empty vector options");
|
|
|
|
SECTION("string vector") {
|
|
options.add_options()
|
|
("string_vector", "vector of strings", cxxopts::value(string_vector)->default_value(""));
|
|
|
|
Argv av({"empty vector"});
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
options.parse(argc, argv);
|
|
|
|
CHECK(string_vector.empty());
|
|
}
|
|
|
|
SECTION("double vector") {
|
|
options.add_options()
|
|
("double_vector", "vector of doubles", cxxopts::value(double_vector)->default_value(""));
|
|
|
|
Argv av({"empty vector"});
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
options.parse(argc, argv); // throws
|
|
|
|
CHECK(double_vector.empty());
|
|
}
|
|
|
|
}
|
|
|
|
#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
|
|
|
|
#ifdef CXXOPTS_HAS_FILESYSTEM
|
|
TEST_CASE("std::filesystem::path", "[path]") {
|
|
std::filesystem::path path;
|
|
cxxopts::Options options("path", " - tests path");
|
|
options.add_options()
|
|
("path", "a path", cxxopts::value<std::filesystem::path>(path));
|
|
|
|
Argv av({"path", "--path", "Hello World.txt"});
|
|
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
REQUIRE(path.empty());
|
|
|
|
options.parse(argc, argv);
|
|
|
|
REQUIRE(!path.empty());
|
|
CHECK(path == "Hello World.txt");
|
|
}
|
|
#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);
|
|
}
|
|
|
|
#ifdef CXXOPTS_HAS_OPTIONAL
|
|
TEST_CASE("Optional value", "[optional]")
|
|
{
|
|
cxxopts::Options options("options", "query as std::optional");
|
|
options.add_options()
|
|
("int", "Integer", cxxopts::value<int>())
|
|
("float", "Float", cxxopts::value<float>())
|
|
("string", "String", cxxopts::value<std::string>())
|
|
;
|
|
|
|
SECTION("Available") {
|
|
Argv av({
|
|
"available",
|
|
"--int",
|
|
"42",
|
|
"--float",
|
|
"3.141",
|
|
"--string",
|
|
"Hello"
|
|
});
|
|
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
auto result = options.parse(argc, argv);
|
|
|
|
CHECK(result.as_optional<int>("int"));
|
|
CHECK(result.as_optional<float>("float"));
|
|
CHECK(result.as_optional<std::string>("string"));
|
|
|
|
CHECK(result.as_optional<int>("int") == 42);
|
|
CHECK(result.as_optional<float>("float") == 3.141f);
|
|
CHECK(result.as_optional<std::string>("string") == "Hello");
|
|
}
|
|
|
|
SECTION("Unavailable") {
|
|
Argv av({
|
|
"unavailable"
|
|
});
|
|
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
auto result = options.parse(argc, argv);
|
|
|
|
CHECK(!result.as_optional<int>("int"));
|
|
CHECK(!result.as_optional<float>("float"));
|
|
CHECK(!result.as_optional<std::string>("string"));
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifdef CXXOPTS_HAS_OPTIONAL
|
|
TEST_CASE("std::filesystem::path value", "[path]")
|
|
{
|
|
cxxopts::Options options("options", "query as std::fileystem::path");
|
|
options.add_options()
|
|
("a", "Path", cxxopts::value<std::filesystem::path>())
|
|
("b", "Path", cxxopts::value<std::filesystem::path>())
|
|
("c", "Path", cxxopts::value<std::filesystem::path>())
|
|
("d", "Path", cxxopts::value<std::filesystem::path>())
|
|
("e", "Path", cxxopts::value<std::filesystem::path>())
|
|
;
|
|
|
|
SECTION("Available") {
|
|
Argv av({
|
|
"available",
|
|
"-a", "hello.txt",
|
|
"-b", "C:\\Users\\JoeCitizen\\hello world.txt",
|
|
"-c", "/home/joecitzen/hello world.txt",
|
|
"-d", "../world.txt"
|
|
});
|
|
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
auto result = options.parse(argc, argv);
|
|
|
|
CHECK(result.as_optional<std::filesystem::path>("a"));
|
|
CHECK(result.as_optional<std::filesystem::path>("b"));
|
|
CHECK(result.as_optional<std::filesystem::path>("c"));
|
|
CHECK(result.as_optional<std::filesystem::path>("d"));
|
|
CHECK(!result.as_optional<std::filesystem::path>("e"));
|
|
|
|
CHECK(result.as_optional<std::filesystem::path>("a") == "hello.txt");
|
|
CHECK(result.as_optional<std::filesystem::path>("b") == "C:\\Users\\JoeCitizen\\hello world.txt");
|
|
CHECK(result.as_optional<std::filesystem::path>("c") == "/home/joecitzen/hello world.txt");
|
|
CHECK(result.as_optional<std::filesystem::path>("d") == "../world.txt");
|
|
}
|
|
|
|
SECTION("Unavailable") {
|
|
Argv av({
|
|
"unavailable"
|
|
});
|
|
|
|
auto** argv = av.argv();
|
|
auto argc = av.argc();
|
|
|
|
auto result = options.parse(argc, argv);
|
|
|
|
CHECK(!result.as_optional<std::filesystem::path>("a"));
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
TEST_CASE("Ordering of multiple long options", "[help]")
|
|
{
|
|
cxxopts::Options options("test", "Ordering of multiple long options");
|
|
options.add_options()
|
|
("x,alphax,betax,gammax,deltax", "Multiple names x", cxxopts::value<int>())
|
|
("deltay,gammay,y,betay,alphay", "Multiple names y", cxxopts::value<int>())
|
|
("alphaz,betaz,z", "Multiple names z", cxxopts::value<int>())
|
|
("alphaw,w", "Multiple names w", cxxopts::value<int>())
|
|
("beta,gamma,alpha,delta", "Without short name", cxxopts::value<int>());
|
|
|
|
auto opts = options.group_help("").options;
|
|
|
|
// Veryfiy ordering
|
|
REQUIRE(opts.size()==5);
|
|
CHECK(opts[0].l == std::vector<std::string>{"alphax", "betax", "gammax", "deltax"});
|
|
CHECK(opts[1].l == std::vector<std::string>{"deltay", "gammay", "betay", "alphay"});
|
|
CHECK(opts[2].l == std::vector<std::string>{"alphaz", "betaz"});
|
|
CHECK(opts[3].l == std::vector<std::string>{"alphaw"});
|
|
CHECK(opts[4].l == std::vector<std::string>{"beta", "gamma", "alpha", "delta"});
|
|
|
|
|
|
std::string help = options.help();
|
|
|
|
// Verify first long options are always present in help output
|
|
CHECK(help.find("--alphax") != std::string::npos);
|
|
CHECK(help.find("--deltay") != std::string::npos);
|
|
CHECK(help.find("--alphaz") != std::string::npos);
|
|
CHECK(help.find("--alphaw") != std::string::npos);
|
|
CHECK(help.find("--beta") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("Help output wrapping", "[help]")
|
|
{
|
|
struct {
|
|
std::string name;
|
|
cxxopts::Options parser;
|
|
std::vector<std::pair<std::string, std::string>> opts;
|
|
std::vector<std::string> positionals;
|
|
std::string expected;
|
|
} tests[] = {
|
|
{
|
|
"Basic test",
|
|
cxxopts::Options("prog_abc", "This is a sample program for snake jazz")
|
|
.positional_help("Positional help")
|
|
.custom_help("Custom help")
|
|
.set_width(15),
|
|
{{"o,opt", "Sample description"}},
|
|
{"o"},
|
|
"This is a\n"
|
|
"sample program\n"
|
|
"for snake jazz\n"
|
|
"Usage:\n"
|
|
"prog_abc Custom\n"
|
|
"help Positional\n"
|
|
"help\n"
|
|
"\n"
|
|
" -o, --opt Sample\n"
|
|
" descriptio\n"
|
|
" n\n"
|
|
},
|
|
{
|
|
"Custom help manual newline",
|
|
cxxopts::Options("prog")
|
|
.custom_help("Custom\nHelp")
|
|
.set_width(12),
|
|
{{"o,opt", "desc"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog Custom\n"
|
|
"Help\n"
|
|
"\n"
|
|
" -o, --opt desc\n"
|
|
},
|
|
{
|
|
"Description spaces before explicit newline",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "alpha \nbeta"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt alpha\n"
|
|
" beta\n"
|
|
},
|
|
{
|
|
"Description blank line is preserved",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "alpha\n\nbeta"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt alpha\n"
|
|
"\n"
|
|
" beta\n"
|
|
},
|
|
{
|
|
"Description trailing newline is preserved",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "alpha\n"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt alpha\n"
|
|
"\n"
|
|
},
|
|
{
|
|
"Description leading newline is preserved",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "\nalpha"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt \n"
|
|
" alpha\n"
|
|
},
|
|
{
|
|
"Custom help trailing newline is preserved",
|
|
cxxopts::Options("prog")
|
|
.custom_help("Custom\n")
|
|
.set_width(12),
|
|
{{"o,opt", "desc"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog Custom\n"
|
|
"\n"
|
|
"\n"
|
|
" -o, --opt desc\n"
|
|
},
|
|
{
|
|
"Tab expansion happens before description wrapping",
|
|
cxxopts::Options("prog")
|
|
.set_width(26)
|
|
.set_tab_expansion(true),
|
|
{{"o,opt", "a\tb"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt a b\n"
|
|
},
|
|
{
|
|
"Long word does not drop trailing character",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "01234567890"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt 0123456789\n"
|
|
" 0\n"
|
|
},
|
|
{
|
|
"Consecutive explicit newlines keep following text",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "a\n\nb"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt a\n"
|
|
"\n"
|
|
" b\n"
|
|
},
|
|
{
|
|
"Trailing newline does not create whitespace-only continuation",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "abc\n"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt abc\n"
|
|
"\n"
|
|
},
|
|
{
|
|
"Spaces before explicit newline are trimmed",
|
|
cxxopts::Options("prog")
|
|
.set_width(18),
|
|
{{"o,opt", "abc \nxyz"}},
|
|
{},
|
|
"\n"
|
|
"Usage:\n"
|
|
"prog [OPTION...]\n"
|
|
"\n"
|
|
" -o, --opt abc\n"
|
|
" xyz\n"
|
|
},
|
|
};
|
|
|
|
for (auto& tc : tests)
|
|
{
|
|
SECTION(tc.name)
|
|
{
|
|
for (const auto& opt : tc.opts)
|
|
{
|
|
tc.parser.add_options()(opt.first, opt.second);
|
|
}
|
|
tc.parser.parse_positional(tc.positionals);
|
|
CHECK(tc.parser.help() == tc.expected);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("wrap_text", "[wrap_text]")
|
|
{
|
|
struct {
|
|
std::string name;
|
|
cxxopts::String text;
|
|
std::size_t allowed;
|
|
std::size_t start;
|
|
cxxopts::String expected;
|
|
} tests[] = {
|
|
{
|
|
"Plain Newline",
|
|
"\n",
|
|
3,
|
|
3,
|
|
"\n"
|
|
},
|
|
{
|
|
"Manual newlines",
|
|
"abc\ndef\nghi",
|
|
3,
|
|
0,
|
|
"abc\n"
|
|
"def\n"
|
|
"ghi"
|
|
},
|
|
{
|
|
"Basic wrap",
|
|
"abc def ghi",
|
|
3,
|
|
0,
|
|
"abc\n"
|
|
"def\n"
|
|
"ghi"
|
|
},
|
|
{
|
|
"Word splitting",
|
|
"abcdefghi",
|
|
3,
|
|
0,
|
|
"abc\n"
|
|
"def\n"
|
|
"ghi"
|
|
},
|
|
|
|
{
|
|
"Clamping with manual newlines",
|
|
"\na\n\nbcdef",
|
|
3,
|
|
3,
|
|
"\n"
|
|
" a\n"
|
|
"\n"
|
|
" bcd\n"
|
|
" ef"
|
|
},
|
|
{
|
|
"Trailing newline is preserved",
|
|
"abc\n",
|
|
3,
|
|
3,
|
|
"abc\n"
|
|
},
|
|
{
|
|
"Trailing spaces after final newline preserve newline",
|
|
"abc\n ",
|
|
5,
|
|
2,
|
|
"abc\n"
|
|
},
|
|
{
|
|
"Whitespace around final newline preserves blank line",
|
|
" \n ",
|
|
5,
|
|
2,
|
|
"\n"
|
|
},
|
|
{
|
|
"Consecutive newlines stay consecutive",
|
|
"a\n\nb",
|
|
3,
|
|
3,
|
|
"a\n"
|
|
"\n"
|
|
" b"
|
|
},
|
|
{
|
|
"Leading spaces do not create blank lines",
|
|
" abc",
|
|
3,
|
|
3,
|
|
"abc"
|
|
},
|
|
{
|
|
"Exact fit with separating space stays on one line",
|
|
"a b",
|
|
3,
|
|
0,
|
|
"a b"
|
|
},
|
|
{
|
|
"Exact fit with longer words stays on one line",
|
|
"abc def",
|
|
7,
|
|
0,
|
|
"abc def"
|
|
},
|
|
{
|
|
"Exact fit with separating tab stays on one line",
|
|
"a\tb",
|
|
3,
|
|
0,
|
|
"a\tb"
|
|
},
|
|
{
|
|
"Whitespace wrap keeps the trailing word",
|
|
"abc def",
|
|
4,
|
|
2,
|
|
"abc\n"
|
|
" def"
|
|
},
|
|
{
|
|
"Empty string stays empty",
|
|
"",
|
|
3,
|
|
0,
|
|
""
|
|
},
|
|
{
|
|
"Whitespace only stays empty",
|
|
" ",
|
|
3,
|
|
2,
|
|
""
|
|
},
|
|
{
|
|
"Exact fit word stays on one line",
|
|
"abc",
|
|
3,
|
|
0,
|
|
"abc"
|
|
},
|
|
{
|
|
"Internal spaces are preserved when no wrap is needed",
|
|
"a b",
|
|
10,
|
|
0,
|
|
"a b"
|
|
},
|
|
{
|
|
"Whitespace runs at wrap boundaries keep all words",
|
|
"23423 23424 343",
|
|
10,
|
|
3,
|
|
"23423\n"
|
|
" 23424\n"
|
|
" 343"
|
|
},
|
|
{
|
|
"Wrapped line followed by newline",
|
|
"abcd\n",
|
|
3,
|
|
2,
|
|
"abc\n"
|
|
" d\n"
|
|
},
|
|
{
|
|
"Wrapped line followed by newline 2",
|
|
"abcd\nef",
|
|
3,
|
|
2,
|
|
"abc\n"
|
|
" d\n"
|
|
" ef"
|
|
},
|
|
{
|
|
"Edge case of minimum width",
|
|
"abc\n\nx y\n z",
|
|
1,
|
|
1,
|
|
"a\n"
|
|
" b\n"
|
|
" c\n"
|
|
"\n"
|
|
" x\n"
|
|
" y\n"
|
|
" z"
|
|
},
|
|
{
|
|
"0 allowed",
|
|
"abc",
|
|
0,
|
|
1,
|
|
"",
|
|
},
|
|
{
|
|
"trailing spaces before an explicit newline",
|
|
"abc \nxyz",
|
|
5,
|
|
0,
|
|
"abc\n"
|
|
"xyz"
|
|
},
|
|
{
|
|
"Consecutive trailing newlines are preserved",
|
|
"a\n\n",
|
|
3,
|
|
2,
|
|
"a\n"
|
|
"\n"
|
|
}
|
|
};
|
|
|
|
for (auto& tc : tests)
|
|
{
|
|
SECTION(tc.name)
|
|
{
|
|
CHECK(cxxopts::wrap_text(tc.text, tc.allowed, tc.start) == tc.expected);
|
|
}
|
|
}
|
|
}
|