1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00
CLI11/tests/AppTest.cpp
Philip Top 0f5bf21e91
add some reduction methods to the options on the fuzz tests (#930)
This adds a round trip test for config file generation to the fuzzer. 

(the next step after this PR will be a fuzzer that verifies that the
round trip actually matches the results.
This change ended up requiring quite a few minor changes to fix the
ambiguities between the config file generation and config file reader.

1). There was a number of potential conflicts between positional names
and regular option names that could be triggered in config files, this
required a number of additional checks on the positional naming to
ensure no conflicts.
2). flag options with disable flag override can produce output results
that are not valid by themselves, resolving this required flag input to
be able to handle an array and output the original value set of results.
3). strings with non-printable characters could cause all sorts of chaos
in the config files. This was resolved by generating a binary string
conversion format and handling multiline comments and characters, and
handling escaped characters. Note; I think a better solution is to move
to fully supporting string formatting and escaping along with the binary
strings from TOML now that TOML 1.0 is finalized. That will not be this
PR though, maybe the next one.
4). Lot of ambiguities and edge cases in the string splitter, this was
reworked
5). handling of comments was not done well, especially comment characters in the
name of the option which is allowed.
6). non printable characters in the option naming. This would be weird
in practice but it also cause some big holes in the config file
generation, so the restricted character set for option naming was
expanded. (don't allow spaces or control characters).

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-12-18 05:21:32 -08:00

2683 lines
68 KiB
C++

// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <cmath>
#include <array>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <limits>
TEST_CASE_METHOD(TApp, "OneFlagShort", "[app]") {
app.add_flag("-c,--count");
args = {"-c"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
}
TEST_CASE_METHOD(TApp, "OneFlagShortValues", "[app]") {
app.add_flag("-c{v1},--count{v2}");
args = {"-c"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
const auto &v = app["-c"]->results();
CHECK("v1" == v[0]);
CHECK_THROWS_AS(app["--invalid"], CLI::OptionNotFound);
}
TEST_CASE_METHOD(TApp, "OneFlagShortValuesAs", "[app]") {
auto *flg = app.add_flag("-c{1},--count{2}");
args = {"-c"};
run();
const auto *opt = app["-c"];
CHECK(1 == opt->as<int>());
args = {"--count"};
run();
CHECK(2 == opt->as<int>());
flg->take_first();
args = {"-c", "--count"};
run();
CHECK(1 == opt->as<int>());
flg->take_last();
CHECK(2 == opt->as<int>());
flg->multi_option_policy(CLI::MultiOptionPolicy::Throw);
CHECK_THROWS_AS(opt->as<int>(), CLI::ArgumentMismatch);
flg->multi_option_policy(CLI::MultiOptionPolicy::TakeAll);
auto vec = opt->as<std::vector<int>>();
CHECK(1 == vec[0]);
CHECK(2 == vec[1]);
flg->multi_option_policy(CLI::MultiOptionPolicy::Sum);
vec = opt->as<std::vector<int>>();
CHECK(3 == vec[0]);
CHECK(vec.size() == 1);
flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
CHECK("1\n2" == opt->as<std::string>());
flg->delimiter(',');
CHECK("1,2" == opt->as<std::string>());
flg->multi_option_policy(CLI::MultiOptionPolicy::Reverse)->expected(1, 300);
vec = opt->as<std::vector<int>>();
REQUIRE(vec.size() == 2U);
CHECK(2 == vec[0]);
CHECK(1 == vec[1]);
}
TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") {
app.add_flag("-c,--count");
args = {"/c"};
app.allow_windows_style_options();
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
}
TEST_CASE_METHOD(TApp, "CountNonExist", "[app]") {
app.add_flag("-c,--count");
args = {"-c"};
run();
CHECK_THROWS_AS(app.count("--nonexist"), CLI::OptionNotFound);
}
TEST_CASE_METHOD(TApp, "OneFlagLong", "[app]") {
app.add_flag("-c,--count");
args = {"--count"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
}
TEST_CASE_METHOD(TApp, "DashedOptions", "[app]") {
app.add_flag("-c");
app.add_flag("--q");
app.add_flag("--this,--that");
args = {"-c", "--q", "--this", "--that"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--q") == 1u);
CHECK(app.count("--this") == 2u);
CHECK(app.count("--that") == 2u);
}
TEST_CASE_METHOD(TApp, "DashedOptionsSingleString", "[app]") {
app.add_flag("-c");
app.add_flag("--q");
app.add_flag("--this,--that");
app.parse("-c --q --this --that");
CHECK(app.count("-c") == 1u);
CHECK(app.count("--q") == 1u);
CHECK(app.count("--this") == 2u);
CHECK(app.count("--that") == 2u);
}
TEST_CASE_METHOD(TApp, "StrangeFlagNames", "[app]") {
app.add_flag("-=");
app.add_flag("--t\tt");
app.add_flag("-{");
CHECK_THROWS_AS(app.add_flag("--t t"), CLI::ConstructionError);
args = {"-=", "--t\tt"};
run();
CHECK(app.count("-=") == 1u);
CHECK(app.count("--t\tt") == 1u);
}
TEST_CASE_METHOD(TApp, "RequireOptionsError", "[app]") {
app.add_flag("-c");
app.add_flag("--q");
app.add_flag("--this,--that");
app.set_help_flag("-h,--help");
app.set_help_all_flag("--help_all");
app.require_option(1, 2);
try {
app.parse("-c --q --this --that");
} catch(const CLI::RequiredError &re) {
CHECK_THAT(re.what(), !Contains("-h,--help"));
CHECK_THAT(re.what(), !Contains("help_all"));
}
CHECK_NOTHROW(app.parse("-c --q"));
CHECK_NOTHROW(app.parse("-c --this --that"));
}
TEST_CASE_METHOD(TApp, "BoolFlagOverride", "[app]") {
bool val{false};
auto *flg = app.add_flag("--this,--that", val);
app.parse("--this");
CHECK(val);
app.parse("--this=false");
CHECK(!val);
flg->disable_flag_override(true);
app.parse("--this");
CHECK(val);
// this is allowed since the matching string is the default
app.parse("--this=true");
CHECK(val);
CHECK_THROWS_AS(app.parse("--this=false"), CLI::ArgumentMismatch);
// try a string that specifies 'use default val'
CHECK_NOTHROW(app.parse("--this={}"));
}
TEST_CASE_METHOD(TApp, "OneFlagRef", "[app]") {
int ref{0};
app.add_flag("-c,--count", ref);
args = {"--count"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
CHECK(ref == 1);
}
TEST_CASE_METHOD(TApp, "OneFlagRefValue", "[app]") {
int ref{0};
app.add_flag("-c,--count", ref);
args = {"--count=7"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
CHECK(ref == 7);
}
TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
int ref{0};
auto *flg = app.add_flag("-c,--count", ref);
args = {"--count=false"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
CHECK(ref == -1);
CHECK(!flg->check_fname("c"));
args = {"--count=0"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
CHECK(ref == 0);
args = {"--count=happy"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "FlagNegation", "[app]") {
int ref{0};
auto *flg = app.add_flag("-c,--count,--ncount{false}", ref);
args = {"--count", "-c", "--ncount"};
CHECK(!flg->check_fname("count"));
CHECK(flg->check_fname("ncount"));
run();
CHECK(app.count("-c") == 3u);
CHECK(app.count("--count") == 3u);
CHECK(app.count("--ncount") == 3u);
CHECK(ref == 1);
}
TEST_CASE_METHOD(TApp, "FlagNegationShortcutNotation", "[app]") {
int ref{0};
app.add_flag("-c,--count{true},!--ncount", ref);
args = {"--count=TRUE", "-c", "--ncount"};
run();
CHECK(app.count("-c") == 3u);
CHECK(app.count("--count") == 3u);
CHECK(app.count("--ncount") == 3u);
CHECK(ref == 1);
}
TEST_CASE_METHOD(TApp, "FlagNegationShortcutNotationInvalid", "[app]") {
int ref{0};
app.add_flag("-c,--count,!--ncount", ref);
args = {"--ncount=happy"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "OneString", "[app]") {
std::string str;
app.add_option("-s,--string", str);
args = {"--string", "mystring"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneWideString", "[app]") {
std::wstring str;
app.add_option("-s,--string", str);
args = {"--string", "mystring"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK(L"mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringWideInput", "[app][unicode]") {
std::string str;
app.add_option("-s,--string", str);
std::array<const wchar_t *, 3> cmdline{{L"app", L"--string", L"mystring"}};
app.parse(static_cast<int>(cmdline.size()), cmdline.data());
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") {
std::string str;
app.add_option("-s,--string", str);
args = {"/string", "mystring"};
app.allow_windows_style_options();
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") {
std::string str;
app.add_option("-s,--string", str);
app.parse("--string mystring");
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringSingleWideStringInput", "[app][unicode]") {
std::string str;
app.add_option("-s,--string", str);
app.parse(L"--string mystring");
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") {
std::string str;
app.add_option("-s,--string", str);
args = {"--string=mystring"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionWindowsStyle", "[app]") {
std::string str;
app.add_option("-s,--string", str);
args = {"/string:mystring"};
app.allow_windows_style_options();
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleString", "[app]") {
std::string str;
app.add_option("-s,--string", str);
app.parse("--string=mystring");
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("mystring" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuoted", "[app]") {
std::string str;
app.add_option("-s,--string", str);
app.parse(R"raw(--string="this is my quoted string")raw");
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK("this is my quoted string" == str);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultiple", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.parse(R"raw(--string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)raw");
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringEmbeddedEqual", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.parse(R"raw(--string="app=\"test1 b\" test2=\"frogs\"" -t 'qstring 2' -m=`"quoted string"`)raw");
CHECK("app=\"test1 b\" test2=\"frogs\"" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
app.parse(R"raw(--string="app='test1 b' test2='frogs'" -t 'qstring 2' -m=`"quoted string"`)raw");
CHECK("app='test1 b' test2='frogs'" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringEmbeddedEqualWindowsStyle", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("--mstr", str3);
app.allow_windows_style_options();
app.parse(R"raw(/string:"app:\"test1 b\" test2:\"frogs\"" /t 'qstring 2' /mstr:`"quoted string"`)raw");
CHECK("app:\"test1 b\" test2:\"frogs\"" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
app.parse(R"raw(/string:"app:'test1 b' test2:'frogs'" /t 'qstring 2' /mstr:`"quoted string"`)raw");
CHECK("app:'test1 b' test2:'frogs'" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleMixedStyle", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.allow_windows_style_options();
app.parse(R"raw(/string:"this is my quoted string" /t 'qstring 2' -m=`"quoted string"`)raw");
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleInMiddle", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.parse(R"raw(--string="this is my quoted string" -t "qst\"ring 2" -m=`"quoted string"`)raw");
CHECK("this is my quoted string" == str);
CHECK("qst\"ring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedEscapedCharacters", "[app]") {
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.parse(R"raw(--string="this is my \"quoted\" string" -t 'qst\'ring 2' -m=`"quoted\` string"`")raw");
CHECK("this is my \"quoted\" string" == str);
CHECK("qst\'ring 2" == str2);
CHECK("\"quoted` string\"" == str3);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleWithEqual", "[app]") {
std::string str, str2, str3, str4;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.add_option("-j,--jstr", str4);
app.parse(R"raw(--string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"` --jstr=Unquoted)raw");
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
CHECK("Unquoted" == str4);
}
TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleWithEqualAndProgram", "[app]") {
std::string str, str2, str3, str4;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
app.add_option("-j,--jstr", str4);
app.parse(
R"raw(program --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"` --jstr=Unquoted)raw",
true);
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
CHECK("Unquoted" == str4);
}
TEST_CASE_METHOD(TApp, "OneStringFlagLike", "[app]") {
std::string str{"something"};
app.add_option("-s,--string", str)->expected(0, 1);
args = {"--string"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--string") == 1u);
CHECK(str.empty());
}
TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") {
int val{0};
auto *opt = app.add_option("-i", val)->expected(0, 1);
args = {"-i"};
run();
CHECK(app.count("-i") == 1u);
opt->default_str("7");
run();
CHECK(7 == val);
opt->default_val(9);
run();
CHECK(9 == val);
}
TEST_CASE_METHOD(TApp, "TogetherInt", "[app]") {
int i{0};
app.add_option("-i,--int", i);
args = {"-i4"};
run();
CHECK(app.count("--int") == 1u);
CHECK(app.count("-i") == 1u);
CHECK(4 == i);
CHECK("4" == app["-i"]->as<std::string>());
CHECK(4.0 == app["--int"]->as<double>());
}
TEST_CASE_METHOD(TApp, "SepInt", "[app]") {
int i{0};
app.add_option("-i,--int", i);
args = {"-i", "4"};
run();
CHECK(app.count("--int") == 1u);
CHECK(app.count("-i") == 1u);
CHECK(4 == i);
}
TEST_CASE_METHOD(TApp, "DefaultStringAgain", "[app]") {
std::string str = "previous";
app.add_option("-s,--string", str);
run();
CHECK(app.count("-s") == 0u);
CHECK(app.count("--string") == 0u);
CHECK("previous" == str);
}
TEST_CASE_METHOD(TApp, "DefaultStringAgainEmpty", "[app]") {
std::string str = "previous";
app.add_option("-s,--string", str);
app.parse(" ");
CHECK(app.count("-s") == 0u);
CHECK(app.count("--string") == 0u);
CHECK("previous" == str);
}
TEST_CASE_METHOD(TApp, "DualOptions", "[app]") {
std::string str = "previous";
std::vector<std::string> vstr = {"previous"};
std::vector<std::string> ans = {"one", "two"};
app.add_option("-s,--string", str);
app.add_option("-v,--vector", vstr);
args = {"--vector=one", "--vector=two"};
run();
CHECK(vstr == ans);
args = {"--string=one", "--string=two"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "LotsOfFlags", "[app]") {
app.add_flag("-a");
app.add_flag("-A");
app.add_flag("-b");
args = {"-a", "-b", "-aA"};
run();
CHECK(app.count("-a") == 2u);
CHECK(app.count("-b") == 1u);
CHECK(app.count("-A") == 1u);
CHECK(4u == app.count_all());
}
TEST_CASE_METHOD(TApp, "NumberFlags", "[app]") {
int val{0};
app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val);
args = {"-7"};
run();
CHECK(app.count("-1") == 1u);
CHECK(7 == val);
}
TEST_CASE_METHOD(TApp, "DisableFlagOverrideTest", "[app]") {
int val{0};
auto *opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
CHECK(!opt->get_disable_flag_override());
opt->disable_flag_override();
args = {"--7=5"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
CHECK(opt->get_disable_flag_override());
opt->disable_flag_override(false);
CHECK(!opt->get_disable_flag_override());
CHECK_NOTHROW(run());
CHECK(5 == val);
opt->disable_flag_override();
args = {"--7=7"};
CHECK_NOTHROW(run());
}
TEST_CASE_METHOD(TApp, "LotsOfFlagsSingleString", "[app]") {
app.add_flag("-a");
app.add_flag("-A");
app.add_flag("-b");
app.parse("-a -b -aA");
CHECK(app.count("-a") == 2u);
CHECK(app.count("-b") == 1u);
CHECK(app.count("-A") == 1u);
}
TEST_CASE_METHOD(TApp, "LotsOfFlagsSingleStringExtraSpace", "[app]") {
app.add_flag("-a");
app.add_flag("-A");
app.add_flag("-b");
app.parse(" -a -b -aA ");
CHECK(app.count("-a") == 2u);
CHECK(app.count("-b") == 1u);
CHECK(app.count("-A") == 1u);
}
TEST_CASE_METHOD(TApp, "SingleArgVector", "[app]") {
std::vector<std::string> channels;
std::vector<std::string> iargs;
std::string path;
app.add_option("-c", channels)->type_size(1)->allow_extra_args(false);
app.add_option("args", iargs);
app.add_option("-p", path);
app.parse("-c t1 -c t2 -c t3 a1 a2 a3 a4 -p happy");
CHECK(channels.size() == 3u);
CHECK(iargs.size() == 4u);
CHECK("happy" == path);
app.parse("-c t1 a1 -c t2 -c t3 a2 a3 a4 -p happy");
CHECK(channels.size() == 3u);
CHECK(iargs.size() == 4u);
CHECK("happy" == path);
}
TEST_CASE_METHOD(TApp, "StrangeOptionNames", "[app]") {
app.add_option("-:");
app.add_option("--t\tt");
app.add_option("--{}");
app.add_option("--:)");
CHECK_THROWS_AS(app.add_option("--t t"), CLI::ConstructionError);
args = {"-:)", "--{}", "5"};
run();
CHECK(app.count("-:") == 1u);
CHECK(app.count("--{}") == 1u);
CHECK(app["-:"]->as<char>() == ')');
CHECK(app["--{}"]->as<int>() == 5);
}
TEST_CASE_METHOD(TApp, "singledash", "[app]") {
app.add_option("-t");
try {
app.add_option("-test");
} catch(const CLI::BadNameString &e) {
std::string str = e.what();
CHECK_THAT(str, Contains("2 dashes"));
CHECK_THAT(str, Contains("-test"));
} catch(...) {
CHECK(false);
}
try {
app.add_option("-!");
} catch(const CLI::BadNameString &e) {
std::string str = e.what();
CHECK_THAT(str, Contains("one char"));
CHECK_THAT(str, Contains("-!"));
} catch(...) {
CHECK(false);
}
}
TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") {
bool val{false};
auto *opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
args = {"--flag"};
run();
CHECK(app.count("--flag") == 1u);
CHECK(val);
val = false;
opt->type_size(0, 0); // should be the same as above
CHECK(0 == opt->get_type_size_min());
CHECK(0 == opt->get_type_size_max());
run();
CHECK(app.count("--flag") == 1u);
CHECK(val);
}
TEST_CASE_METHOD(TApp, "FlagLikeIntOption", "[app]") {
int val{-47};
auto *opt = app.add_option("--flag", val)->expected(0, 1);
// normally some default value should be set, but this test is for some paths in the validators checks to skip
// validation on empty string if nothing is expected
opt->check(CLI::PositiveNumber);
args = {"--flag"};
CHECK(opt->as<std::string>().empty());
run();
CHECK(app.count("--flag") == 1u);
CHECK(-47 != val);
args = {"--flag", "12"};
run();
CHECK(12 == val);
args.clear();
run();
CHECK(opt->as<std::string>().empty());
}
TEST_CASE_METHOD(TApp, "BoolOnlyFlag", "[app]") {
bool bflag{false};
app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"-b"};
REQUIRE_NOTHROW(run());
CHECK(bflag);
args = {"-b", "-b"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "ShortOpts", "[app]") {
std::uint64_t funnyint{0};
std::string someopt;
app.add_flag("-z", funnyint);
app.add_option("-y", someopt);
args = {
"-zzyzyz",
};
run();
CHECK(app.count("-z") == 2u);
CHECK(app.count("-y") == 1u);
CHECK(funnyint == std::uint64_t{2});
CHECK(someopt == "zyz");
CHECK(3u == app.count_all());
}
TEST_CASE_METHOD(TApp, "TwoParamTemplateOpts", "[app]") {
double funnyint{0.0};
auto *opt = app.add_option<double, unsigned int>("-y", funnyint);
args = {"-y", "32"};
run();
CHECK(funnyint == 32.0);
args = {"-y", "32.3"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-y", "-19"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
opt->capture_default_str();
CHECK(opt->get_default_str().empty());
}
TEST_CASE_METHOD(TApp, "DefaultOpts", "[app]") {
int i{3};
std::string s = "HI";
app.add_option("-i,i", i);
app.add_option("-s,s", s)->capture_default_str(); // Used to be different
args = {"-i2", "9"};
run();
CHECK(app.count("i") == 1u);
CHECK(app.count("-s") == 1u);
CHECK(i == 2);
CHECK(s == "9");
}
TEST_CASE_METHOD(TApp, "TakeLastOpt", "[app]") {
std::string str;
app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
args = {"--str=one", "--str=two"};
run();
CHECK("two" == str);
}
TEST_CASE_METHOD(TApp, "TakeLastOpt2", "[app]") {
std::string str;
app.add_option("--str", str)->take_last();
args = {"--str=one", "--str=two"};
run();
CHECK("two" == str);
}
TEST_CASE_METHOD(TApp, "TakeFirstOpt", "[app]") {
std::string str;
app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
args = {"--str=one", "--str=two"};
run();
CHECK("one" == str);
}
TEST_CASE_METHOD(TApp, "TakeFirstOpt2", "[app]") {
std::string str;
app.add_option("--str", str)->take_first();
args = {"--str=one", "--str=two"};
run();
CHECK("one" == str);
}
TEST_CASE_METHOD(TApp, "JoinOpt", "[app]") {
std::string str;
app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::Join);
args = {"--str=one", "--str=two"};
run();
CHECK("one\ntwo" == str);
}
TEST_CASE_METHOD(TApp, "SumOpt", "[app]") {
int val = 0;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=1", "--val=4"};
run();
CHECK(5 == val);
}
TEST_CASE_METHOD(TApp, "SumOptFloat", "[app]") {
double val = NAN;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=1.3", "--val=-0.7"};
run();
CHECK(std::fabs(0.6 - val) <= std::numeric_limits<double>::epsilon());
}
TEST_CASE_METHOD(TApp, "SumOptString", "[app]") {
std::string val;
app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
args = {"--val=i", "--val=2"};
run();
CHECK("i2" == val);
}
TEST_CASE_METHOD(TApp, "ReverseOpt", "[app]") {
std::vector<std::string> val;
auto *opt1 = app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Reverse);
args = {"--val=string1", "--val=string2", "--val", "string3", "string4"};
run();
CHECK(val.size() == 4U);
CHECK(val.front() == "string4");
CHECK(val.back() == "string1");
opt1->expected(1, 2);
run();
CHECK(val.size() == 2U);
CHECK(val.front() == "string4");
CHECK(val.back() == "string3");
CHECK(opt1->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse);
}
TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
std::string str;
app.add_option("--str", str)->join();
args = {"--str=one", "--str=two"};
run();
CHECK("one\ntwo" == str);
}
TEST_CASE_METHOD(TApp, "TakeLastOptMulti", "[app]") {
std::vector<int> vals;
app.add_option("--long", vals)->expected(2)->take_last();
args = {"--long", "1", "2", "3"};
run();
CHECK(std::vector<int>({2, 3}) == vals);
}
TEST_CASE_METHOD(TApp, "TakeLastOptMulti_alternative_path", "[app]") {
std::vector<int> vals;
app.add_option("--long", vals)->expected(2, -1)->take_last();
args = {"--long", "1", "2", "3"};
run();
CHECK(std::vector<int>({2, 3}) == vals);
}
TEST_CASE_METHOD(TApp, "TakeLastOptMultiCheck", "[app]") {
std::vector<int> vals;
auto *opt = app.add_option("--long", vals)->expected(-2)->take_last();
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
args = {"--long", "-1", "2", "-3"};
CHECK_NOTHROW(run());
CHECK(std::vector<int>({2, -3}) == vals);
}
TEST_CASE_METHOD(TApp, "TakeFirstOptMulti", "[app]") {
std::vector<int> vals;
app.add_option("--long", vals)->expected(2)->take_first();
args = {"--long", "1", "2", "3"};
run();
CHECK(std::vector<int>({1, 2}) == vals);
}
TEST_CASE_METHOD(TApp, "ComplexOptMulti", "[app]") {
std::complex<double> val;
app.add_option("--long", val)->take_first()->allow_extra_args();
args = {"--long", "1", "2", "3", "4"};
run();
CHECK(1 == Approx(val.real()));
CHECK(2 == Approx(val.imag()));
}
TEST_CASE_METHOD(TApp, "MissingValueNonRequiredOpt", "[app]") {
int count{0};
app.add_option("-c,--count", count);
args = {"-c"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"--count"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "MissingValueMoreThan", "[app]") {
std::vector<int> vals1;
std::vector<int> vals2;
app.add_option("-v", vals1)->expected(-2);
app.add_option("--vals", vals2)->expected(-2);
args = {"-v", "2"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"--vals", "4"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "NoMissingValueMoreThan", "[app]") {
std::vector<int> vals1;
std::vector<int> vals2;
app.add_option("-v", vals1)->expected(-2);
app.add_option("--vals", vals2)->expected(-2);
args = {"-v", "2", "3", "4"};
run();
CHECK(std::vector<int>({2, 3, 4}) == vals1);
args = {"--vals", "2", "3", "4"};
run();
CHECK(std::vector<int>({2, 3, 4}) == vals2);
}
TEST_CASE_METHOD(TApp, "NotRequiredOptsSingle", "[app]") {
std::string str;
app.add_option("--str", str);
args = {"--str"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "NotRequiredOptsSingleShort", "[app]") {
std::string str;
app.add_option("-s", str);
args = {"-s"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "RequiredOptsSingle", "[app]") {
std::string str;
app.add_option("--str", str)->required();
args = {"--str"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "RequiredOptsSingleShort", "[app]") {
std::string str;
app.add_option("-s", str)->required();
args = {"-s"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "RequiredOptsDouble", "[app]") {
std::vector<std::string> strs;
app.add_option("--str", strs)->required()->expected(2);
args = {"--str", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"--str", "one", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
}
TEST_CASE_METHOD(TApp, "emptyVectorReturn", "[app]") {
std::vector<std::string> strs;
std::vector<std::string> strs2;
std::vector<std::string> strs3;
auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2);
app.add_option("--str3", strs3)->expected(1, 3);
app.add_option("--str2", strs2);
args = {"--str"};
CHECK_NOTHROW(run());
CHECK(std::vector<std::string>({""}) == strs);
args = {"--str", "one", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
args = {"--str", "{}", "--str2", "{}"};
run();
CHECK(strs.empty());
CHECK(std::vector<std::string>{"{}"} == strs2);
opt1->default_str("{}");
args = {"--str"};
CHECK_NOTHROW(run());
CHECK(strs.empty());
opt1->required(false);
args = {"--str3", "{}"};
CHECK_NOTHROW(run());
CHECK_FALSE(strs3.empty());
}
TEST_CASE_METHOD(TApp, "emptyVectorReturnReduce", "[app]") {
std::vector<std::string> strs;
std::vector<std::string> strs2;
std::vector<std::string> strs3;
auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2);
app.add_option("--str3", strs3)->expected(1, 3);
app.add_option("--str2", strs2)->expected(1, 1)->take_first();
args = {"--str"};
CHECK_NOTHROW(run());
CHECK(std::vector<std::string>({""}) == strs);
args = {"--str", "one", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
args = {"--str", "{}", "--str2", "{}", "test"};
run();
CHECK(strs.empty());
CHECK(std::vector<std::string>{"{}"} == strs2);
opt1->default_str("{}");
args = {"--str"};
CHECK_NOTHROW(run());
CHECK(strs.empty());
opt1->required(false);
args = {"--str3", "{}"};
CHECK_NOTHROW(run());
CHECK_FALSE(strs3.empty());
}
TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") {
std::vector<std::string> strs;
app.add_option("-s", strs)->required()->expected(2);
args = {"-s", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"-s", "one", "-s", "one", "-s", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "RequiredOptsDoubleNeg", "[app]") {
std::vector<std::string> strs;
app.add_option("-s", strs)->required()->expected(-2);
args = {"-s", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"-s", "one", "two", "-s", "three"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"one", "two", "three"}) == strs);
args = {"-s", "one", "two"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"one", "two"}) == strs);
}
// This makes sure unlimited option priority is
// correct for space vs. no space #90
TEST_CASE_METHOD(TApp, "PositionalNoSpace", "[app]") {
std::vector<std::string> options;
std::string foo, bar;
app.add_option("-O", options);
app.add_option("foo", foo)->required();
app.add_option("bar", bar)->required();
args = {"-O", "Test", "param1", "param2"};
run();
CHECK(1u == options.size());
CHECK("Test" == options.at(0));
args = {"-OTest", "param1", "param2"};
run();
CHECK(1u == options.size());
CHECK("Test" == options.at(0));
}
// Tests positionals at end
TEST_CASE_METHOD(TApp, "PositionalAtEnd", "[app]") {
std::string options;
std::string foo;
app.add_option("-O", options);
app.add_option("foo", foo);
app.positionals_at_end();
CHECK(app.get_positionals_at_end());
args = {"-O", "Test", "param1"};
run();
CHECK("Test" == options);
CHECK("param1" == foo);
args = {"param2", "-O", "Test"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
}
// Tests positionals at end
TEST_CASE_METHOD(TApp, "PositionalInjectSeparator", "[app]") {
std::string options;
std::vector<std::vector<std::string>> foo;
app.add_option("-O", options);
auto *fooopt = app.add_option("foo", foo);
fooopt->inject_separator();
args = {"test1", "-O", "Test", "test2"};
run();
CHECK("Test" == options);
CHECK(foo.size() == 2U);
}
// Tests positionals at end
TEST_CASE_METHOD(TApp, "RequiredPositionals", "[app]") {
std::vector<std::string> sources;
std::string dest;
app.add_option("src", sources);
app.add_option("dest", dest)->required();
app.positionals_at_end();
args = {"1", "2", "3"};
run();
CHECK(2u == sources.size());
CHECK("3" == dest);
args = {"a"};
sources.clear();
run();
CHECK(sources.empty());
CHECK("a" == dest);
}
TEST_CASE_METHOD(TApp, "RequiredPositionalVector", "[app]") {
std::string d1;
std::string d2;
std::string d3;
std::vector<std::string> sources;
app.add_option("dest1", d1);
app.add_option("dest2", d2);
app.add_option("dest3", d3);
app.add_option("src", sources)->required();
app.positionals_at_end();
args = {"1", "2", "3"};
run();
CHECK(1u == sources.size());
CHECK("1" == d1);
CHECK("2" == d2);
CHECK(d3.empty());
args = {"a"};
sources.clear();
run();
CHECK(1u == sources.size());
}
// Tests positionals at end
TEST_CASE_METHOD(TApp, "RequiredPositionalValidation", "[app]") {
std::vector<std::string> sources;
int dest = 0; // required
std::string d2;
app.add_option("src", sources);
app.add_option("dest", dest)->required()->check(CLI::PositiveNumber);
app.add_option("dest2", d2)->required();
app.positionals_at_end()->validate_positionals();
args = {"1", "2", "string", "3"};
run();
CHECK(2u == sources.size());
CHECK(3 == dest);
CHECK("string" == d2);
}
// Tests positionals at end
TEST_CASE_METHOD(TApp, "PositionalValidation", "[app]") {
std::string options;
std::string foo;
app.add_option("bar", options)->check(CLI::Number.name("valbar"));
// disable the check on foo
app.add_option("foo", foo)->check(CLI::Number.active(false));
app.validate_positionals();
args = {"1", "param1"};
run();
CHECK("1" == options);
CHECK("param1" == foo);
args = {"param1", "1"};
CHECK_NOTHROW(run());
CHECK("1" == options);
CHECK("param1" == foo);
CHECK(nullptr != app.get_option("bar")->get_validator("valbar"));
}
TEST_CASE_METHOD(TApp, "PositionalNoSpaceLong", "[app]") {
std::vector<std::string> options;
std::string foo, bar;
app.add_option("--option", options);
app.add_option("foo", foo)->required();
app.add_option("bar", bar)->required();
args = {"--option", "Test", "param1", "param2"};
run();
CHECK(1u == options.size());
CHECK("Test" == options.at(0));
args = {"--option=Test", "param1", "param2"};
run();
CHECK(1u == options.size());
CHECK("Test" == options.at(0));
}
TEST_CASE_METHOD(TApp, "RequiredOptsUnlimited", "[app]") {
std::vector<std::string> strs;
app.add_option("--str", strs)->required();
args = {"--str"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"--str", "one", "--str", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
args = {"--str", "one", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
// It's better to feed a hungry option than to feed allow_extras
app.allow_extras();
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
CHECK(std::vector<std::string>({}) == app.remaining());
app.allow_extras(false);
std::vector<std::string> remain;
auto *popt = app.add_option("positional", remain);
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
CHECK(remain.empty());
args = {"--str", "one", "--", "two"};
run();
CHECK(std::vector<std::string>({"one"}) == strs);
CHECK(std::vector<std::string>({"two"}) == remain);
args = {"one", "--str", "two"};
run();
CHECK(std::vector<std::string>({"two"}) == strs);
CHECK(std::vector<std::string>({"one"}) == remain);
args = {"--str", "one", "two"};
popt->required();
run();
CHECK(std::vector<std::string>({"one"}) == strs);
CHECK(std::vector<std::string>({"two"}) == remain);
}
TEST_CASE_METHOD(TApp, "RequiredOptsUnlimitedShort", "[app]") {
std::vector<std::string> strs;
app.add_option("-s", strs)->required();
args = {"-s"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"-s", "one", "-s", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
args = {"-s", "one", "two"};
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
// It's better to feed a hungry option than to feed allow_extras
app.allow_extras();
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
CHECK(std::vector<std::string>({}) == app.remaining());
app.allow_extras(false);
std::vector<std::string> remain;
app.add_option("positional", remain);
run();
CHECK(std::vector<std::string>({"one", "two"}) == strs);
CHECK(remain.empty());
args = {"-s", "one", "--", "two"};
run();
CHECK(std::vector<std::string>({"one"}) == strs);
CHECK(std::vector<std::string>({"two"}) == remain);
args = {"one", "-s", "two"};
run();
CHECK(std::vector<std::string>({"two"}) == strs);
CHECK(std::vector<std::string>({"one"}) == remain);
}
TEST_CASE_METHOD(TApp, "OptsUnlimitedEnd", "[app]") {
std::vector<std::string> strs;
app.add_option("-s,--str", strs);
app.allow_extras();
args = {"one", "-s", "two", "three", "--", "four"};
run();
CHECK(std::vector<std::string>({"two", "three"}) == strs);
CHECK(std::vector<std::string>({"one", "four"}) == app.remaining());
}
TEST_CASE_METHOD(TApp, "RequireOptPriority", "[app]") {
std::vector<std::string> strs;
app.add_option("--str", strs);
std::vector<std::string> remain;
app.add_option("positional", remain)->expected(2)->required();
args = {"--str", "one", "two", "three"};
run();
CHECK(std::vector<std::string>({"one"}) == strs);
CHECK(std::vector<std::string>({"two", "three"}) == remain);
args = {"two", "three", "--str", "one", "four"};
run();
CHECK(std::vector<std::string>({"one", "four"}) == strs);
CHECK(std::vector<std::string>({"two", "three"}) == remain);
}
TEST_CASE_METHOD(TApp, "RequireOptPriorityShort", "[app]") {
std::vector<std::string> strs;
app.add_option("-s", strs)->required();
std::vector<std::string> remain;
app.add_option("positional", remain)->expected(2)->required();
args = {"-s", "one", "two", "three"};
run();
CHECK(std::vector<std::string>({"one"}) == strs);
CHECK(std::vector<std::string>({"two", "three"}) == remain);
args = {"two", "three", "-s", "one", "four"};
run();
CHECK(std::vector<std::string>({"one", "four"}) == strs);
CHECK(std::vector<std::string>({"two", "three"}) == remain);
}
TEST_CASE_METHOD(TApp, "NotRequiredExpectedDouble", "[app]") {
std::vector<std::string> strs;
app.add_option("--str", strs)->expected(2);
args = {"--str", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "NotRequiredExpectedDoubleShort", "[app]") {
std::vector<std::string> strs;
app.add_option("-s", strs)->expected(2);
args = {"-s", "one"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "RequiredFlags", "[app]") {
app.add_flag("-a")->required();
app.add_flag("-b")->mandatory(); // Alternate term
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"-a"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"-b"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"-a", "-b"};
run();
}
TEST_CASE_METHOD(TApp, "CallbackFlags", "[app]") {
std::int64_t value{0};
auto func = [&value](std::int64_t x) { value = x; };
app.add_flag_function("-v", func);
run();
CHECK(0u == value);
args = {"-v"};
run();
CHECK(1u == value);
args = {"-vv"};
run();
CHECK(2u == value);
CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "CallbackFlagsFalse", "[app]") {
std::int64_t value = 0;
auto func = [&value](std::int64_t x) { value = x; };
app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
run();
CHECK(0 == value);
args = {"-f"};
run();
CHECK(-1 == value);
args = {"-vfv"};
run();
CHECK(1 == value);
args = {"--fval"};
run();
CHECK(-1 == value);
args = {"--fval=2"};
run();
CHECK(-2 == value);
CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "CallbackFlagsFalseShortcut", "[app]") {
std::int64_t value = 0;
auto func = [&value](std::int64_t x) { value = x; };
app.add_flag_function("-v,!-f,--val,!--fval", func);
run();
CHECK(0 == value);
args = {"-f"};
run();
CHECK(-1 == value);
args = {"-vfv"};
run();
CHECK(1 == value);
args = {"--fval"};
run();
CHECK(-1 == value);
args = {"--fval=2"};
run();
CHECK(-2 == value);
CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
}
#if __cplusplus >= 201402L || _MSC_VER >= 1900
TEST_CASE_METHOD(TApp, "CallbackFlagsAuto", "[app]") {
std::int64_t value{0};
auto func = [&value](std::int64_t x) { value = x; };
app.add_flag("-v", func);
run();
CHECK(0u == value);
args = {"-v"};
run();
CHECK(1u == value);
args = {"-vv"};
run();
CHECK(2u == value);
CHECK_THROWS_AS(app.add_flag("hi", func), CLI::IncorrectConstruction);
}
#endif
TEST_CASE_METHOD(TApp, "Positionals", "[app]") {
std::string posit1;
std::string posit2;
app.add_option("posit1", posit1);
app.add_option("posit2", posit2);
args = {"thing1", "thing2"};
run();
CHECK(app.count("posit1") == 1u);
CHECK(app.count("posit2") == 1u);
CHECK(posit1 == "thing1");
CHECK(posit2 == "thing2");
}
TEST_CASE_METHOD(TApp, "ForcedPositional", "[app]") {
std::vector<std::string> posit;
auto *one = app.add_flag("--one");
app.add_option("posit", posit);
args = {"--one", "two", "three"};
run();
std::vector<std::string> answers1 = {"two", "three"};
CHECK(one->count());
CHECK(posit == answers1);
args = {"--", "--one", "two", "three"};
std::vector<std::string> answers2 = {"--one", "two", "three"};
run();
CHECK(!one->count());
CHECK(posit == answers2);
}
TEST_CASE_METHOD(TApp, "MixedPositionals", "[app]") {
int positional_int{0};
std::string positional_string;
app.add_option("posit1,--posit1", positional_int, "");
app.add_option("posit2,--posit2", positional_string, "");
args = {"--posit2", "thing2", "7"};
run();
CHECK(app.count("posit2") == 1u);
CHECK(app.count("--posit1") == 1u);
CHECK(positional_int == 7);
CHECK(positional_string == "thing2");
}
TEST_CASE_METHOD(TApp, "BigPositional", "[app]") {
std::vector<std::string> vec;
app.add_option("pos", vec);
args = {"one"};
run();
CHECK(vec == args);
args = {"one", "two"};
run();
CHECK(vec == args);
}
TEST_CASE_METHOD(TApp, "VectorArgAndPositional", "[app]") {
std::vector<std::string> vec;
std::vector<int> ivec;
app.add_option("pos", vec);
app.add_option("--args", ivec)->check(CLI::Number);
app.validate_optional_arguments();
args = {"one"};
run();
CHECK(vec == args);
args = {"--args", "1", "2"};
run();
CHECK(ivec.size() == 2);
vec.clear();
ivec.clear();
args = {"--args", "1", "2", "one", "two"};
run();
CHECK(vec.size() == 2);
CHECK(ivec.size() == 2);
app.validate_optional_arguments(false);
CHECK_THROWS(run());
}
TEST_CASE_METHOD(TApp, "Reset", "[app]") {
app.add_flag("--simple");
double doub{0.0};
app.add_option("-d,--double", doub);
args = {"--simple", "--double", "1.2"};
run();
CHECK(app.count("--simple") == 1u);
CHECK(app.count("-d") == 1u);
CHECK(doub == Approx(1.2));
app.clear();
CHECK(app.count("--simple") == 0u);
CHECK(app.count("-d") == 0u);
run();
CHECK(app.count("--simple") == 1u);
CHECK(app.count("-d") == 1u);
CHECK(doub == Approx(1.2));
}
TEST_CASE_METHOD(TApp, "RemoveOption", "[app]") {
app.add_flag("--one");
auto *opt = app.add_flag("--two");
CHECK(app.remove_option(opt));
CHECK(!app.remove_option(opt));
args = {"--two"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
}
TEST_CASE_METHOD(TApp, "RemoveNeedsLinks", "[app]") {
auto *one = app.add_flag("--one");
auto *two = app.add_flag("--two");
two->needs(one);
one->needs(two);
CHECK(app.remove_option(one));
args = {"--two"};
run();
}
TEST_CASE_METHOD(TApp, "RemoveExcludesLinks", "[app]") {
auto *one = app.add_flag("--one");
auto *two = app.add_flag("--two");
two->excludes(one);
one->excludes(two);
CHECK(app.remove_option(one));
args = {"--two"};
run(); // Mostly hoping it does not crash
}
TEST_CASE_METHOD(TApp, "FileNotExists", "[app]") {
std::string myfile{"TestNonFileNotUsed.txt"};
REQUIRE_NOTHROW(CLI::NonexistentPath(myfile));
std::string filename;
auto *opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
args = {"--file", myfile};
run();
CHECK(filename == myfile);
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
CHECK_THROWS_AS(run(), CLI::ValidationError);
// deactivate the check, so it should run now
opt->get_validator("path_check")->active(false);
CHECK_NOTHROW(run());
std::remove(myfile.c_str());
CHECK(!CLI::ExistingFile(myfile).empty());
}
TEST_CASE_METHOD(TApp, "FileExists", "[app]") {
std::string myfile{"TestNonFileNotUsed.txt"};
CHECK(!CLI::ExistingFile(myfile).empty());
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::ExistingFile);
args = {"--file", myfile};
CHECK_THROWS_AS(run(), CLI::ValidationError);
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
run();
CHECK(filename == myfile);
std::remove(myfile.c_str());
CHECK(!CLI::ExistingFile(myfile).empty());
}
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 && defined(_MSC_VER)
TEST_CASE_METHOD(TApp, "filesystemWideName", "[app]") {
std::filesystem::path myfile{L"voil\u20ac.txt"};
std::filesystem::path fpath;
app.add_option("--file", fpath)->check(CLI::ExistingFile, "existing file");
CHECK_THROWS_AS(app.parse(L"--file voil\u20ac.txt"), CLI::ValidationError);
bool ok = static_cast<bool>(std::ofstream(myfile).put('a')); // create file
CHECK(ok);
// deactivate the check, so it should run now
CHECK_NOTHROW(app.parse(L"--file voil\u20ac.txt"));
CHECK(fpath == myfile);
CHECK(std::filesystem::exists(fpath));
std::filesystem::remove(myfile);
CHECK(!std::filesystem::exists(fpath));
}
#endif
TEST_CASE_METHOD(TApp, "NotFileExists", "[app]") {
std::string myfile{"TestNonFileNotUsed.txt"};
CHECK(!CLI::ExistingFile(myfile).empty());
std::string filename = "Failed";
app.add_option("--file", filename)->check(!CLI::ExistingFile);
args = {"--file", myfile};
CHECK_NOTHROW(run());
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
CHECK_THROWS_AS(run(), CLI::ValidationError);
std::remove(myfile.c_str());
CHECK(!CLI::ExistingFile(myfile).empty());
}
TEST_CASE_METHOD(TApp, "DefaultedResult", "[app]") {
std::string sval = "NA";
int ival{0};
auto *opts = app.add_option("--string", sval)->capture_default_str();
auto *optv = app.add_option("--val", ival);
args = {};
run();
CHECK("NA" == sval);
std::string nString;
opts->results(nString);
CHECK("NA" == nString);
int newIval = 0;
// CHECK_THROWS_AS (optv->results(newIval), CLI::ConversionError);
optv->default_str("442");
optv->results(newIval);
CHECK(442 == newIval);
}
TEST_CASE_METHOD(TApp, "OriginalOrder", "[app]") {
std::vector<int> st1;
CLI::Option *op1 = app.add_option("-a", st1);
std::vector<int> st2;
CLI::Option *op2 = app.add_option("-b", st2);
args = {"-a", "1", "-b", "2", "-a3", "-a", "4"};
run();
CHECK(std::vector<int>({1, 3, 4}) == st1);
CHECK(std::vector<int>({2}) == st2);
CHECK(std::vector<CLI::Option *>({op1, op2, op1, op1}) == app.parse_order());
}
TEST_CASE_METHOD(TApp, "NeedsFlags", "[app]") {
CLI::Option *opt = app.add_flag("-s,--string");
app.add_flag("--both")->needs(opt);
run();
args = {"-s"};
run();
args = {"-s", "--both"};
run();
args = {"--both"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
CHECK_NOTHROW(opt->needs(opt));
}
TEST_CASE_METHOD(TApp, "ExcludesFlags", "[app]") {
CLI::Option *opt = app.add_flag("-s,--string");
app.add_flag("--nostr")->excludes(opt);
run();
args = {"-s"};
run();
args = {"--nostr"};
run();
args = {"--nostr", "-s"};
CHECK_THROWS_AS(run(), CLI::ExcludesError);
args = {"--string", "--nostr"};
CHECK_THROWS_AS(run(), CLI::ExcludesError);
CHECK_THROWS_AS(opt->excludes(opt), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "ExcludesMixedFlags", "[app]") {
CLI::Option *opt1 = app.add_flag("--opt1");
app.add_flag("--opt2");
CLI::Option *opt3 = app.add_flag("--opt3");
app.add_flag("--no")->excludes(opt1, "--opt2", opt3);
run();
args = {"--no"};
run();
args = {"--opt2"};
run();
args = {"--no", "--opt1"};
CHECK_THROWS_AS(run(), CLI::ExcludesError);
args = {"--no", "--opt2"};
CHECK_THROWS_AS(run(), CLI::ExcludesError);
}
TEST_CASE_METHOD(TApp, "NeedsMultiFlags", "[app]") {
CLI::Option *opt1 = app.add_flag("--opt1");
CLI::Option *opt2 = app.add_flag("--opt2");
CLI::Option *opt3 = app.add_flag("--opt3");
app.add_flag("--optall")->needs(opt1, opt2, opt3); // NOLINT(readability-suspicious-call-argument)
run();
args = {"--opt1"};
run();
args = {"--opt2"};
run();
args = {"--optall"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt1"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt2", "--opt1"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt1", "--opt2", "--opt3"};
run();
}
TEST_CASE_METHOD(TApp, "NeedsMixedFlags", "[app]") {
CLI::Option *opt1 = app.add_flag("--opt1");
app.add_flag("--opt2");
app.add_flag("--opt3");
app.add_flag("--optall")->needs(opt1, "--opt2", "--opt3");
run();
args = {"--opt1"};
run();
args = {"--opt2"};
run();
args = {"--optall"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt1"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt2", "--opt1"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--optall", "--opt1", "--opt2", "--opt3"};
run();
}
TEST_CASE_METHOD(TApp, "NeedsChainedFlags", "[app]") {
CLI::Option *opt1 = app.add_flag("--opt1");
CLI::Option *opt2 = app.add_flag("--opt2")->needs(opt1);
app.add_flag("--opt3")->needs(opt2);
run();
args = {"--opt1"};
run();
args = {"--opt2"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--opt3"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--opt3", "--opt2"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--opt3", "--opt1"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
args = {"--opt2", "--opt1"};
run();
args = {"--opt1", "--opt2", "--opt3"};
run();
}
TEST_CASE_METHOD(TApp, "Env", "[app]") {
put_env("CLI11_TEST_ENV_TMP", "2");
int val{1};
CLI::Option *vopt = app.add_option("--tmp", val)->envname("CLI11_TEST_ENV_TMP");
run();
CHECK(val == 2);
CHECK(vopt->count() == 1u);
vopt->required();
run();
unset_env("CLI11_TEST_ENV_TMP");
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
// curiously check if an environmental only option works
TEST_CASE_METHOD(TApp, "EnvOnly", "[app]") {
put_env("CLI11_TEST_ENV_TMP", "2");
int val{1};
CLI::Option *vopt = app.add_option("", val)->envname("CLI11_TEST_ENV_TMP");
run();
CHECK(val == 2);
CHECK(vopt->count() == 1u);
vopt->required();
run();
unset_env("CLI11_TEST_ENV_TMP");
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
TEST_CASE_METHOD(TApp, "RangeInt", "[app]") {
int x{0};
app.add_option("--one", x)->check(CLI::Range(3, 6));
args = {"--one=1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=7"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=3"};
run();
args = {"--one=5"};
run();
args = {"--one=6"};
run();
}
TEST_CASE_METHOD(TApp, "RangeDouble", "[app]") {
double x{0.0};
/// Note that this must be a double in Range, too
app.add_option("--one", x)->check(CLI::Range(3.0, 6.0));
args = {"--one=1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=7"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=3"};
run();
args = {"--one=5"};
run();
args = {"--one=6"};
run();
}
TEST_CASE_METHOD(TApp, "RangeFloat", "[app]") {
float x{0.0f};
/// Note that this must be a float in Range, too
app.add_option("--one", x, "testing floats")->check(CLI::Range(3.0, 6.0));
args = {"--one=1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=7"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=3"};
run();
args = {"--one=5"};
run();
args = {"--one=6"};
run();
}
TEST_CASE_METHOD(TApp, "NonNegative", "[app]") {
std::string res;
/// Note that this must be a double in Range, too
app.add_option("--one", res)->check(CLI::NonNegativeNumber);
args = {"--one=crazy"};
try {
// this should throw
run();
CHECK(false);
} catch(const CLI::ValidationError &e) {
std::string emess = e.what();
CHECK(emess.size() < 70U);
}
}
TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
/// Note that this must be a double in Range, too
app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
args = {"--one=1"};
CHECK_NOTHROW(run());
args = {"--one=-7"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=error"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=4.568"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NeedsTrue", "[app]") {
std::string str;
app.add_option("-s,--string", str);
app.add_flag("--opt1")->check([&](const std::string &) {
return (str != "val_with_opt1") ? std::string("--opt1 requires --string val_with_opt1") : std::string{};
});
run();
args = {"--opt1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--string", "val"};
run();
args = {"--string", "val", "--opt1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--string", "val_with_opt1", "--opt1"};
run();
args = {"--opt1", "--string", "val_with_opt1"}; // order is not revelant
run();
}
// Check to make sure programmatic access to left over is available
TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") {
app.allow_extras();
bool val{true};
app.add_flag("-f", val);
args = {"-x", "-f"};
REQUIRE_NOTHROW(run());
CHECK(val);
CHECK(std::vector<std::string>({"-x"}) == app.remaining());
}
TEST_CASE_METHOD(TApp, "AllowExtrasOrder", "[app]") {
app.allow_extras();
args = {"-x", "-f"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"-x", "-f"}) == app.remaining());
std::vector<std::string> left_over = app.remaining();
app.parse(left_over);
CHECK(std::vector<std::string>({"-f", "-x"}) == app.remaining());
CHECK(left_over == app.remaining_for_passthrough());
}
TEST_CASE_METHOD(TApp, "AllowExtrasCascade", "[app]") {
app.allow_extras();
args = {"-x", "45", "-f", "27"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"-x", "45", "-f", "27"}) == app.remaining());
std::vector<std::string> left_over = app.remaining_for_passthrough();
CLI::App capp{"cascade_program"};
int v1 = 0;
int v2 = 0;
capp.add_option("-x", v1);
capp.add_option("-f", v2);
capp.parse(left_over);
CHECK(45 == v1);
CHECK(27 == v2);
}
// makes sure the error throws on the rValue version of the parse
TEST_CASE_METHOD(TApp, "ExtrasErrorRvalueParse", "[app]") {
args = {"-x", "45", "-f", "27"};
CHECK_THROWS_AS(app.parse(std::vector<std::string>({"-x", "45", "-f", "27"})), CLI::ExtrasError);
}
TEST_CASE_METHOD(TApp, "AllowExtrasCascadeDirect", "[app]") {
app.allow_extras();
args = {"-x", "45", "-f", "27"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"-x", "45", "-f", "27"}) == app.remaining());
CLI::App capp{"cascade_program"};
int v1{0};
int v2{0};
capp.add_option("-x", v1);
capp.add_option("-f", v2);
capp.parse(app.remaining_for_passthrough());
CHECK(45 == v1);
CHECK(27 == v2);
}
TEST_CASE_METHOD(TApp, "AllowExtrasArgModify", "[app]") {
int v1{0};
int v2{0};
app.allow_extras();
app.add_option("-f", v2);
args = {"27", "-f", "45", "-x"};
app.parse(args);
CHECK(std::vector<std::string>({"45", "-x"}) == args);
CLI::App capp{"cascade_program"};
capp.add_option("-x", v1);
capp.parse(args);
CHECK(45 == v1);
CHECK(27 == v2);
}
// Test horrible error
TEST_CASE_METHOD(TApp, "CheckShortFail", "[app]") {
args = {"--two"};
CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::SHORT, false),
CLI::HorribleError);
}
// Test horrible error
TEST_CASE_METHOD(TApp, "CheckLongFail", "[app]") {
args = {"-t"};
CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::LONG, false),
CLI::HorribleError);
}
// Test horrible error
TEST_CASE_METHOD(TApp, "CheckWindowsFail", "[app]") {
args = {"-t"};
CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::WINDOWS_STYLE, false),
CLI::HorribleError);
}
// Test horrible error
TEST_CASE_METHOD(TApp, "CheckOtherFail", "[app]") {
args = {"-t"};
CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::NONE, false),
CLI::HorribleError);
}
// Test horrible error
TEST_CASE_METHOD(TApp, "CheckSubcomFail", "[app]") {
args = {"subcom"};
CHECK_THROWS_AS(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError);
}
TEST_CASE_METHOD(TApp, "FallthroughParentFail", "[app]") {
CHECK_THROWS_AS(CLI::detail::AppFriend::get_fallthrough_parent(&app), CLI::HorribleError);
}
TEST_CASE_METHOD(TApp, "FallthroughParents", "[app]") {
auto *sub = app.add_subcommand("test");
CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(sub));
auto *ssub = sub->add_subcommand("sub2");
CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ssub));
auto *og1 = app.add_option_group("g1");
auto *og2 = og1->add_option_group("g2");
auto *og3 = og2->add_option_group("g3");
CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(og3));
auto *ogb1 = sub->add_option_group("g1");
auto *ogb2 = ogb1->add_option_group("g2");
auto *ogb3 = ogb2->add_option_group("g3");
CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ogb3));
ogb2->name("groupb");
CHECK(ogb2 == CLI::detail::AppFriend::get_fallthrough_parent(ogb3));
}
TEST_CASE_METHOD(TApp, "OptionWithDefaults", "[app]") {
int someint{2};
app.add_option("-a", someint)->capture_default_str();
args = {"-a1", "-a2"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
// Added to test ->transform
TEST_CASE_METHOD(TApp, "OrderedModifyingTransforms", "[app]") {
std::vector<std::string> val;
auto *m = app.add_option("-m", val);
m->transform([](std::string x) { return x + "1"; });
m->transform([](std::string x) { return x + "2"; });
args = {"-mone", "-mtwo"};
run();
CHECK(std::vector<std::string>({"one21", "two21"}) == val);
}
TEST_CASE_METHOD(TApp, "ThrowingTransform", "[app]") {
std::string val;
auto *m = app.add_option("-m,--mess", val);
m->transform([](std::string) -> std::string { throw CLI::ValidationError("My Message"); });
REQUIRE_NOTHROW(run());
args = {"-mone"};
REQUIRE_THROWS_AS(run(), CLI::ValidationError);
try {
run();
} catch(const CLI::ValidationError &e) {
CHECK(std::string("--mess: My Message") == e.what());
}
}
// This was added to make running a simple function on each item easier
TEST_CASE_METHOD(TApp, "EachItem", "[app]") {
std::vector<std::string> results;
std::vector<std::string> dummy;
auto *opt = app.add_option("--vec", dummy);
opt->each([&results](std::string item) { results.push_back(item); });
args = {"--vec", "one", "two", "three"};
run();
CHECK(dummy == results);
}
// #128
TEST_CASE_METHOD(TApp, "RepeatingMultiArgumentOptions", "[app]") {
std::vector<std::string> entries;
app.add_option("--entry", entries, "set a key and value")->type_name("KEY VALUE")->type_size(-2);
args = {"--entry", "key1", "value1", "--entry", "key2", "value2"};
REQUIRE_NOTHROW(run());
CHECK(std::vector<std::string>({"key1", "value1", "key2", "value2"}) == entries);
args.pop_back();
REQUIRE_THROWS_AS(run(), CLI::ArgumentMismatch);
}
// #122
TEST_CASE_METHOD(TApp, "EmptyOptionEach", "[app]") {
std::string q;
app.add_option("--each")->each([&q](std::string s) { q = s; });
args = {"--each", "that"};
run();
CHECK("that" == q);
}
// #122
TEST_CASE_METHOD(TApp, "EmptyOptionFail", "[app]") {
std::string q;
app.add_option("--each");
args = {"--each", "that"};
run();
}
TEST_CASE_METHOD(TApp, "BeforeRequirements", "[app]") {
app.add_flag_function("-a", [](std::int64_t) { throw CLI::Success(); });
app.add_flag_function("-b", [](std::int64_t) { throw CLI::CallForHelp(); });
args = {"extra"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
args = {"-a", "extra"};
CHECK_THROWS_AS(run(), CLI::Success);
args = {"-b", "extra"};
CHECK_THROWS_AS(run(), CLI::CallForHelp);
// These run in definition order.
args = {"-a", "-b", "extra"};
CHECK_THROWS_AS(run(), CLI::Success);
// Currently, the original order is not preserved when calling callbacks
// args = {"-b", "-a", "extra"};
// CHECK_THROWS_AS (run(), CLI::CallForHelp);
}
// #209
TEST_CASE_METHOD(TApp, "CustomUserSepParse", "[app]") {
std::vector<int> vals{1, 2, 3};
args = {"--idx", "1,2,3"};
auto *opt = app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<int>({1, 2, 3}) == vals);
std::vector<int> vals2;
// check that the results vector gets the results in the same way
opt->results(vals2);
CHECK(vals == vals2);
app.remove_option(opt);
app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run();
CHECK(std::vector<int>({1, 2, 3}) == vals);
}
// #209
TEST_CASE_METHOD(TApp, "DefaultUserSepParse", "[app]") {
std::vector<std::string> vals;
args = {"--idx", "1 2 3", "4 5 6"};
auto *opt = app.add_option("--idx", vals, "");
run();
CHECK(std::vector<std::string>({"1 2 3", "4 5 6"}) == vals);
opt->delimiter(',');
run();
CHECK(std::vector<std::string>({"1 2 3", "4 5 6"}) == vals);
}
// #209
TEST_CASE_METHOD(TApp, "BadUserSepParse", "[app]") {
std::vector<int> vals;
app.add_option("--idx", vals);
args = {"--idx", "1,2,3"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
// #209
TEST_CASE_METHOD(TApp, "CustomUserSepParse2", "[app]") {
std::vector<int> vals{1, 2, 3};
args = {"--idx", "1,2,"};
auto *opt = app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<int>({1, 2}) == vals);
app.remove_option(opt);
app.add_option("--idx", vals, "")->delimiter(',')->capture_default_str();
run();
CHECK(std::vector<int>({1, 2}) == vals);
}
TEST_CASE_METHOD(TApp, "CustomUserSepParseFunction", "[app]") {
std::vector<int> vals{1, 2, 3};
args = {"--idx", "1,2,3"};
app.add_option_function<std::vector<int>>("--idx", [&vals](std::vector<int> v) { vals = std::move(v); })
->delimiter(',');
run();
CHECK(std::vector<int>({1, 2, 3}) == vals);
}
// delimiter removal
TEST_CASE_METHOD(TApp, "CustomUserSepParseToggle", "[app]") {
std::vector<std::string> vals;
args = {"--idx", "1,2,3"};
auto *opt = app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<std::string>({"1", "2", "3"}) == vals);
opt->delimiter('\0');
run();
CHECK(std::vector<std::string>({"1,2,3"}) == vals);
opt->delimiter(',');
run();
CHECK(std::vector<std::string>({"1", "2", "3"}) == vals);
}
// #209
TEST_CASE_METHOD(TApp, "CustomUserSepParse3", "[app]") {
std::vector<int> vals = {1, 2, 3};
args = {"--idx",
"1",
","
"2"};
auto *opt = app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<int>({1, 2}) == vals);
app.remove_option(opt);
app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<int>({1, 2}) == vals);
}
// #209
TEST_CASE_METHOD(TApp, "CustomUserSepParse4", "[app]") {
std::vector<int> vals;
args = {"--idx", "1, 2"};
auto *opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run();
CHECK(std::vector<int>({1, 2}) == vals);
app.remove_option(opt);
app.add_option("--idx", vals)->delimiter(',');
run();
CHECK(std::vector<int>({1, 2}) == vals);
}
// #218
TEST_CASE_METHOD(TApp, "CustomUserSepParse5", "[app]") {
std::vector<std::string> bar;
args = {"this", "is", "a", "test"};
auto *opt = app.add_option("bar", bar, "bar");
run();
CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
app.remove_option(opt);
args = {"this", "is", "a", "test"};
app.add_option("bar", bar, "bar")->capture_default_str();
run();
CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
}
// #218
TEST_CASE_METHOD(TApp, "logFormSingleDash", "[app]") {
bool verbose{false};
bool veryverbose{false};
bool veryveryverbose{false};
app.name("testargs");
app.allow_extras();
args = {"-v", "-vv", "-vvv"};
app.final_callback([&]() {
auto rem = app.remaining();
for(auto &arg : rem) {
if(arg == "-v") {
verbose = true;
}
if(arg == "-vv") {
veryverbose = true;
}
if(arg == "-vvv") {
veryveryverbose = true;
}
}
});
run();
CHECK(app.remaining().size() == 3U);
CHECK(verbose);
CHECK(veryverbose);
CHECK(veryveryverbose);
}
TEST_CASE("C20_compile", "simple") {
CLI::App app{"test"};
auto *flag = app.add_flag("--flag", "desc");
app.parse("--flag");
CHECK_FALSE(flag->empty());
}
// #14
TEST_CASE("System Args", "[app]") {
const char *commandline = CLI11_SYSTEM_ARGS_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported different argc count");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
}
}
// #845
TEST_CASE("Ensure UTF-8", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
}
}
// #845
TEST_CASE("Ensure UTF-8 called twice", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_TWICE_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
}
}