1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00
CLI11/tests/SubcommandTest.cpp
Christoph Bachhuber d8a5bdc294
Add and fix cpplint whitespace/comments (#434)
* Add whitespace/comments check

* Adapt spacing in clang-format

* Fix cpplint whitespace/comments issues

* Grammar

* Do not use clang-format for comment spacing

* Fix with clang-format pre-commit hook
2020-03-06 00:04:59 -05:00

1831 lines
48 KiB
C++

#include "app_helper.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::HasSubstr;
using ::testing::Not;
using vs_t = std::vector<std::string>;
TEST_F(TApp, BasicSubcommands) {
auto sub1 = app.add_subcommand("sub1");
auto sub2 = app.add_subcommand("sub2");
EXPECT_EQ(sub1->get_parent(), &app);
EXPECT_EQ(sub1, app.get_subcommand(sub1));
EXPECT_EQ(sub1, app.get_subcommand("sub1"));
EXPECT_THROW(app.get_subcommand("sub3"), CLI::OptionNotFound);
run();
EXPECT_EQ(0u, app.get_subcommands().size());
args = {"sub1"};
run();
EXPECT_EQ(sub1, app.get_subcommands().at(0));
EXPECT_EQ(1u, app.get_subcommands().size());
app.clear();
EXPECT_EQ(0u, app.get_subcommands().size());
args = {"sub2"};
run();
EXPECT_EQ(1u, app.get_subcommands().size());
EXPECT_EQ(sub2, app.get_subcommands().at(0));
args = {"SUb2"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"SUb2"};
try {
run();
} catch(const CLI::ExtrasError &e) {
EXPECT_THAT(e.what(), HasSubstr("SUb2"));
}
args = {"sub1", "extra"};
try {
run();
} catch(const CLI::ExtrasError &e) {
EXPECT_THAT(e.what(), HasSubstr("extra"));
}
}
TEST_F(TApp, MultiSubFallthrough) {
// No explicit fallthrough
auto sub1 = app.add_subcommand("sub1");
auto sub2 = app.add_subcommand("sub2");
args = {"sub1", "sub2"};
run();
EXPECT_TRUE(app.got_subcommand("sub1"));
EXPECT_TRUE(app.got_subcommand(sub1));
EXPECT_TRUE(*sub1);
EXPECT_TRUE(sub1->parsed());
EXPECT_EQ(sub1->count(), 1u);
EXPECT_TRUE(app.got_subcommand("sub2"));
EXPECT_TRUE(app.got_subcommand(sub2));
EXPECT_TRUE(*sub2);
app.require_subcommand();
run();
app.require_subcommand(2);
run();
app.require_subcommand(1);
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sub1"};
run();
EXPECT_TRUE(app.got_subcommand("sub1"));
EXPECT_FALSE(app.got_subcommand("sub2"));
EXPECT_TRUE(*sub1);
EXPECT_FALSE(*sub2);
EXPECT_FALSE(sub2->parsed());
EXPECT_EQ(sub2->count(), 0u);
EXPECT_THROW(app.got_subcommand("sub3"), CLI::OptionNotFound);
}
TEST_F(TApp, CrazyNameSubcommand) {
auto sub1 = app.add_subcommand("sub1");
// name can be set to whatever
EXPECT_NO_THROW(sub1->name("crazy name with spaces"));
args = {"crazy name with spaces"};
run();
EXPECT_TRUE(app.got_subcommand("crazy name with spaces"));
EXPECT_EQ(sub1->count(), 1u);
}
TEST_F(TApp, RequiredAndSubcommands) { // #23
std::string baz;
app.add_option("baz", baz, "Baz Description", true)->required();
auto foo = app.add_subcommand("foo");
auto bar = app.add_subcommand("bar");
args = {"bar", "foo"};
ASSERT_NO_THROW(run());
EXPECT_TRUE(*foo);
EXPECT_FALSE(*bar);
EXPECT_EQ(baz, "bar");
args = {"foo"};
ASSERT_NO_THROW(run());
EXPECT_FALSE(*foo);
EXPECT_EQ(baz, "foo");
args = {"foo", "foo"};
ASSERT_NO_THROW(run());
EXPECT_TRUE(*foo);
EXPECT_EQ(baz, "foo");
args = {"foo", "other"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, RequiredAndSubcomFallthrough) {
std::string baz;
app.add_option("baz", baz)->required();
app.add_subcommand("foo");
auto bar = app.add_subcommand("bar");
app.fallthrough();
args = {"other", "bar"};
run();
EXPECT_TRUE(bar);
EXPECT_EQ(baz, "other");
args = {"bar", "other2"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, FooFooProblem) {
std::string baz_str, other_str;
auto baz = app.add_option("baz", baz_str);
auto foo = app.add_subcommand("foo");
auto other = foo->add_option("other", other_str);
args = {"foo", "foo"};
run();
EXPECT_TRUE(*foo);
EXPECT_FALSE(*baz);
EXPECT_TRUE(*other);
EXPECT_EQ(baz_str, "");
EXPECT_EQ(other_str, "foo");
baz_str = "";
other_str = "";
baz->required();
run();
EXPECT_TRUE(*foo);
EXPECT_TRUE(*baz);
EXPECT_FALSE(*other);
EXPECT_EQ(baz_str, "foo");
EXPECT_EQ(other_str, "");
}
TEST_F(TApp, DuplicateSubcommands) {
auto foo = app.add_subcommand("foo");
args = {"foo", "foo"};
run();
EXPECT_TRUE(*foo);
EXPECT_EQ(foo->count(), 2u);
args = {"foo", "foo", "foo"};
run();
EXPECT_TRUE(*foo);
EXPECT_EQ(foo->count(), 3u);
}
TEST_F(TApp, DuplicateSubcommandCallbacks) {
auto foo = app.add_subcommand("foo");
int count{0};
foo->callback([&count]() { ++count; });
foo->immediate_callback();
EXPECT_TRUE(foo->get_immediate_callback());
args = {"foo", "foo"};
run();
EXPECT_EQ(count, 2);
count = 0;
args = {"foo", "foo", "foo"};
run();
EXPECT_EQ(count, 3);
}
TEST_F(TApp, DuplicateSubcommandCallbacksValues) {
auto foo = app.add_subcommand("foo");
int val{0};
foo->add_option("--val", val);
std::vector<int> vals;
foo->callback([&vals, &val]() { vals.push_back(val); });
foo->immediate_callback();
args = {"foo", "--val=45", "foo", "--val=27"};
run();
EXPECT_EQ(vals.size(), 2u);
EXPECT_EQ(vals[0], 45);
EXPECT_EQ(vals[1], 27);
vals.clear();
args = {"foo", "--val=45", "foo", "--val=27", "foo", "--val=36"};
run();
EXPECT_EQ(vals.size(), 3u);
EXPECT_EQ(vals[0], 45);
EXPECT_EQ(vals[1], 27);
EXPECT_EQ(vals[2], 36);
}
TEST_F(TApp, Callbacks) {
auto sub1 = app.add_subcommand("sub1");
sub1->callback([]() { throw CLI::Success(); });
auto sub2 = app.add_subcommand("sub2");
bool val{false};
sub2->callback([&val]() { val = true; });
args = {"sub2"};
EXPECT_FALSE(val);
run();
EXPECT_TRUE(val);
}
TEST_F(TApp, CallbackOrder) {
std::vector<std::string> cb;
app.parse_complete_callback([&cb]() { cb.push_back("ac1"); });
app.final_callback([&cb]() { cb.push_back("ac2"); });
auto sub1 =
app.add_subcommand("sub1")
->parse_complete_callback([&cb]() { cb.push_back("c1"); })
->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc1-") + std::to_string(v1)); });
auto sub2 =
app.add_subcommand("sub2")
->final_callback([&cb]() { cb.push_back("c2"); })
->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc2-") + std::to_string(v1)); });
app.preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pa-") + std::to_string(v1)); });
app.add_option("--opt1");
sub1->add_flag("--sub1opt");
sub1->add_option("--sub1optb");
sub1->add_flag("--sub1opt2");
sub2->add_flag("--sub2opt");
sub2->add_option("--sub2opt2");
args = {"--opt1",
"opt1_val",
"sub1",
"--sub1opt",
"--sub1optb",
"val",
"sub2",
"--sub2opt",
"sub1",
"--sub1opt2",
"sub2",
"--sub2opt2",
"val"};
run();
EXPECT_EQ(cb.size(), 8u);
EXPECT_EQ(cb[0], "pa-13");
EXPECT_EQ(cb[1], "pc1-10");
EXPECT_EQ(cb[2], "c1");
EXPECT_EQ(cb[3], "pc2-6");
EXPECT_EQ(cb[4], "c1");
EXPECT_EQ(cb[5], "ac1");
EXPECT_EQ(cb[6], "c2");
EXPECT_EQ(cb[7], "ac2");
}
TEST_F(TApp, CallbackOrder2) {
std::vector<std::string> cb;
app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); });
app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
run();
EXPECT_EQ(cb.size(), 7u);
EXPECT_EQ(cb[0], "sub1");
EXPECT_EQ(cb[1], "sub2");
EXPECT_EQ(cb[2], "sub3");
EXPECT_EQ(cb[3], "sub1");
EXPECT_EQ(cb[4], "sub1");
EXPECT_EQ(cb[5], "sub2");
EXPECT_EQ(cb[6], "sub1");
}
TEST_F(TApp, CallbackOrder2_withFallthrough) {
std::vector<std::string> cb;
app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); })->fallthrough();
app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
run();
EXPECT_EQ(cb.size(), 7u);
EXPECT_EQ(cb[0], "sub1");
EXPECT_EQ(cb[1], "sub2");
EXPECT_EQ(cb[2], "sub3");
EXPECT_EQ(cb[3], "sub1");
EXPECT_EQ(cb[4], "sub1");
EXPECT_EQ(cb[5], "sub2");
EXPECT_EQ(cb[6], "sub1");
}
TEST_F(TApp, RuntimeErrorInCallback) {
auto sub1 = app.add_subcommand("sub1");
sub1->callback([]() { throw CLI::RuntimeError(); });
auto sub2 = app.add_subcommand("sub2");
sub2->callback([]() { throw CLI::RuntimeError(2); });
args = {"sub1"};
EXPECT_THROW(run(), CLI::RuntimeError);
args = {"sub1"};
try {
run();
} catch(const CLI::RuntimeError &e) {
EXPECT_EQ(1, e.get_exit_code());
}
args = {"sub2"};
EXPECT_THROW(run(), CLI::RuntimeError);
args = {"sub2"};
try {
run();
} catch(const CLI::RuntimeError &e) {
EXPECT_EQ(2, e.get_exit_code());
}
}
TEST_F(TApp, NoFallThroughOpts) {
int val{1};
app.add_option("--val", val);
app.add_subcommand("sub");
args = {"sub", "--val", "2"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, NoFallThroughPositionals) {
int val{1};
app.add_option("val", val);
app.add_subcommand("sub");
args = {"sub", "2"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, NoFallThroughOptsWithTerminator) {
int val{1};
app.add_option("--val", val);
app.add_subcommand("sub");
args = {"sub", "++", "--val", "2"};
run();
EXPECT_EQ(val, 2);
}
TEST_F(TApp, NoFallThroughPositionalsWithTerminator) {
int val{1};
app.add_option("val", val);
app.add_subcommand("sub");
args = {"sub", "++", "2"};
run();
EXPECT_EQ(val, 2);
// try with positional only mark
args = {"sub", "--", "3"};
run();
EXPECT_EQ(val, 3);
}
TEST_F(TApp, NamelessSubComPositionals) {
auto sub = app.add_subcommand();
int val{1};
sub->add_option("val", val);
args = {"2"};
run();
EXPECT_EQ(val, 2);
}
TEST_F(TApp, NamelessSubWithSub) {
auto sub = app.add_subcommand();
auto subsub = sub->add_subcommand("val");
args = {"val"};
run();
EXPECT_TRUE(subsub->parsed());
EXPECT_TRUE(app.got_subcommand("val"));
}
TEST_F(TApp, NamelessSubWithMultipleSub) {
auto sub1 = app.add_subcommand();
auto sub2 = app.add_subcommand();
auto sub1sub1 = sub1->add_subcommand("val1");
auto sub1sub2 = sub1->add_subcommand("val2");
auto sub2sub1 = sub2->add_subcommand("val3");
auto sub2sub2 = sub2->add_subcommand("val4");
args = {"val1"};
run();
EXPECT_TRUE(sub1sub1->parsed());
EXPECT_TRUE(app.got_subcommand("val1"));
args = {"val2"};
run();
EXPECT_TRUE(sub1sub2->parsed());
EXPECT_TRUE(app.got_subcommand("val2"));
args = {"val3"};
run();
EXPECT_TRUE(sub2sub1->parsed());
EXPECT_TRUE(app.got_subcommand("val3"));
args = {"val4"};
run();
EXPECT_TRUE(sub2sub2->parsed());
EXPECT_TRUE(app.got_subcommand("val4"));
args = {"val4", "val1"};
run();
EXPECT_TRUE(sub2sub2->parsed());
EXPECT_TRUE(app.got_subcommand("val4"));
EXPECT_TRUE(sub1sub1->parsed());
EXPECT_TRUE(app.got_subcommand("val1"));
}
TEST_F(TApp, Nameless4LayerDeep) {
auto sub = app.add_subcommand();
auto ssub = sub->add_subcommand();
auto sssub = ssub->add_subcommand();
auto ssssub = sssub->add_subcommand();
auto sssssub = ssssub->add_subcommand("val");
args = {"val"};
run();
EXPECT_TRUE(sssssub->parsed());
EXPECT_TRUE(app.got_subcommand("val"));
}
/// Put subcommands in some crazy pattern and make everything still works
TEST_F(TApp, Nameless4LayerDeepMulti) {
auto sub1 = app.add_subcommand();
auto sub2 = app.add_subcommand();
auto ssub1 = sub1->add_subcommand();
auto ssub2 = sub2->add_subcommand();
auto sssub1 = ssub1->add_subcommand();
auto sssub2 = ssub2->add_subcommand();
sssub1->add_subcommand("val1");
ssub2->add_subcommand("val2");
sub2->add_subcommand("val3");
ssub1->add_subcommand("val4");
sssub2->add_subcommand("val5");
args = {"val1"};
run();
EXPECT_TRUE(app.got_subcommand("val1"));
args = {"val2"};
run();
EXPECT_TRUE(app.got_subcommand("val2"));
args = {"val3"};
run();
EXPECT_TRUE(app.got_subcommand("val3"));
args = {"val4"};
run();
EXPECT_TRUE(app.got_subcommand("val4"));
args = {"val5"};
run();
EXPECT_TRUE(app.got_subcommand("val5"));
args = {"val4", "val1", "val5"};
run();
EXPECT_TRUE(app.got_subcommand("val4"));
EXPECT_TRUE(app.got_subcommand("val1"));
EXPECT_TRUE(app.got_subcommand("val5"));
}
TEST_F(TApp, FallThroughRegular) {
app.fallthrough();
int val{1};
app.add_option("--val", val);
app.add_subcommand("sub");
args = {"sub", "--val", "2"};
// Should not throw
run();
}
TEST_F(TApp, FallThroughShort) {
app.fallthrough();
int val{1};
app.add_option("-v", val);
app.add_subcommand("sub");
args = {"sub", "-v", "2"};
// Should not throw
run();
}
TEST_F(TApp, FallThroughPositional) {
app.fallthrough();
int val{1};
app.add_option("val", val);
app.add_subcommand("sub");
args = {"sub", "2"};
// Should not throw
run();
}
TEST_F(TApp, FallThroughEquals) {
app.fallthrough();
int val{1};
app.add_option("--val", val);
app.add_subcommand("sub");
args = {"sub", "--val=2"};
// Should not throw
run();
}
TEST_F(TApp, EvilParseFallthrough) {
app.fallthrough();
int val1{0}, val2{0};
app.add_option("--val1", val1);
auto sub = app.add_subcommand("sub");
sub->add_option("val2", val2);
args = {"sub", "--val1", "1", "2"};
// Should not throw
run();
EXPECT_EQ(1, val1);
EXPECT_EQ(2, val2);
}
TEST_F(TApp, CallbackOrdering) {
app.fallthrough();
int val{1}, sub_val{0};
app.add_option("--val", val);
auto sub = app.add_subcommand("sub");
sub->callback([&val, &sub_val]() { sub_val = val; });
args = {"sub", "--val=2"};
run();
EXPECT_EQ(2, val);
EXPECT_EQ(2, sub_val);
args = {"--val=2", "sub"};
run();
EXPECT_EQ(2, val);
EXPECT_EQ(2, sub_val);
}
TEST_F(TApp, CallbackOrderingImmediate) {
app.fallthrough();
int val{1}, sub_val{0};
app.add_option("--val", val);
auto sub = app.add_subcommand("sub")->immediate_callback();
sub->callback([&val, &sub_val]() { sub_val = val; });
args = {"sub", "--val=2"};
run();
EXPECT_EQ(2, val);
EXPECT_EQ(1, sub_val);
args = {"--val=2", "sub"};
run();
EXPECT_EQ(2, val);
EXPECT_EQ(2, sub_val);
}
TEST_F(TApp, CallbackOrderingImmediateMain) {
app.fallthrough();
int val{0}, sub_val{0};
auto sub = app.add_subcommand("sub");
sub->callback([&val, &sub_val]() {
sub_val = val;
val = 2;
});
app.callback([&val]() { val = 1; });
args = {"sub"};
run();
EXPECT_EQ(1, val);
EXPECT_EQ(0, sub_val);
// the main app callback should run before the subcommand callbacks
app.immediate_callback();
val = 0; // reset value
run();
EXPECT_EQ(2, val);
EXPECT_EQ(1, sub_val);
// the subcommand callback now runs immediately after processing and before the main app callback again
sub->immediate_callback();
val = 0; // reset value
run();
EXPECT_EQ(1, val);
EXPECT_EQ(0, sub_val);
}
// Test based on issue #308
TEST_F(TApp, CallbackOrderingImmediateModeOrder) {
app.require_subcommand(1, 1);
std::vector<int> v;
app.callback([&v]() { v.push_back(1); })->immediate_callback(true);
auto sub = app.add_subcommand("hello")->callback([&v]() { v.push_back(2); })->immediate_callback(false);
args = {"hello"};
run();
// immediate_callback inherited
ASSERT_EQ(v.size(), 2u);
EXPECT_EQ(v[0], 1);
EXPECT_EQ(v[1], 2);
v.clear();
sub->immediate_callback(true);
run();
// immediate_callback is now triggered for the main first
ASSERT_EQ(v.size(), 2u);
EXPECT_EQ(v[0], 2);
EXPECT_EQ(v[1], 1);
}
TEST_F(TApp, RequiredSubCom) {
app.add_subcommand("sub1");
app.add_subcommand("sub2");
app.require_subcommand();
EXPECT_THROW(run(), CLI::RequiredError);
args = {"sub1"};
run();
}
TEST_F(TApp, SubComExtras) {
app.allow_extras();
auto sub = app.add_subcommand("sub");
args = {"extra", "sub"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra"}));
EXPECT_EQ(sub->remaining(), std::vector<std::string>());
args = {"extra1", "extra2", "sub"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra1", "extra2"}));
EXPECT_EQ(sub->remaining(), std::vector<std::string>());
args = {"sub", "extra1", "extra2"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>());
EXPECT_EQ(sub->remaining(), std::vector<std::string>({"extra1", "extra2"}));
args = {"extra1", "extra2", "sub", "extra3", "extra4"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"extra1", "extra2"}));
EXPECT_EQ(app.remaining(true), std::vector<std::string>({"extra1", "extra2", "extra3", "extra4"}));
EXPECT_EQ(sub->remaining(), std::vector<std::string>({"extra3", "extra4"}));
}
TEST_F(TApp, Required1SubCom) {
app.require_subcommand(1);
app.add_subcommand("sub1");
app.add_subcommand("sub2");
app.add_subcommand("sub3");
EXPECT_THROW(run(), CLI::RequiredError);
args = {"sub1"};
run();
args = {"sub1", "sub2"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, BadSubcommandSearch) {
auto one = app.add_subcommand("one");
auto two = one->add_subcommand("two");
EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound);
EXPECT_THROW(app.get_subcommand_ptr(two), CLI::OptionNotFound);
}
TEST_F(TApp, PrefixProgram) {
app.prefix_command();
app.add_flag("--simple");
args = {"--simple", "other", "--simple", "--mine"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"other", "--simple", "--mine"}));
}
TEST_F(TApp, PrefixNoSeparation) {
app.prefix_command();
std::vector<int> vals;
app.add_option("--vals", vals);
args = {"--vals", "1", "2", "3", "other"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, PrefixSeparation) {
app.prefix_command();
std::vector<int> vals;
app.add_option("--vals", vals);
args = {"--vals", "1", "2", "3", "--", "other"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"other"}));
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
}
TEST_F(TApp, PrefixSubcom) {
auto subc = app.add_subcommand("subc");
subc->prefix_command();
app.add_flag("--simple");
args = {"--simple", "subc", "other", "--simple", "--mine"};
run();
EXPECT_EQ(app.remaining_size(), 0u);
EXPECT_EQ(app.remaining_size(true), 3u);
EXPECT_EQ(subc->remaining(), std::vector<std::string>({"other", "--simple", "--mine"}));
}
TEST_F(TApp, InheritHelpAllFlag) {
app.set_help_all_flag("--help-all");
auto subc = app.add_subcommand("subc");
auto help_opt_list = subc->get_options([](const CLI::Option *opt) { return opt->get_name() == "--help-all"; });
EXPECT_EQ(help_opt_list.size(), 1u);
}
TEST_F(TApp, RequiredPosInSubcommand) {
app.require_subcommand();
std::string bar;
CLI::App *fooApp = app.add_subcommand("foo", "Foo a bar");
fooApp->add_option("bar", bar, "A bar to foo")->required();
CLI::App *bazApp = app.add_subcommand("baz", "Baz a bar");
bazApp->add_option("bar", bar, "A bar a baz")->required();
args = {"foo", "abc"};
run();
EXPECT_EQ(bar, "abc");
args = {"baz", "cba"};
run();
EXPECT_EQ(bar, "cba");
args = {};
EXPECT_THROW(run(), CLI::RequiredError);
}
struct SubcommandProgram : public TApp {
CLI::App *start{nullptr};
CLI::App *stop{nullptr};
int dummy{0};
std::string file{};
int count{0};
SubcommandProgram(const SubcommandProgram &) = delete;
SubcommandProgram &operator=(const SubcommandProgram &) = delete;
SubcommandProgram() {
app.set_help_all_flag("--help-all");
start = app.add_subcommand("start", "Start prog");
stop = app.add_subcommand("stop", "Stop prog");
app.add_flag("-d", dummy, "My dummy var");
start->add_option("-f,--file", file, "File name");
stop->add_flag("-c,--count", count, "Some flag opt");
}
};
TEST_F(SubcommandProgram, Working) {
args = {"-d", "start", "-ffilename"};
run();
EXPECT_EQ(1, dummy);
EXPECT_EQ(start, app.get_subcommands().at(0));
EXPECT_EQ("filename", file);
}
TEST_F(SubcommandProgram, Spare) {
args = {"extra", "-d", "start", "-ffilename"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(SubcommandProgram, SpareSub) {
args = {"-d", "start", "spare", "-ffilename"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(SubcommandProgram, Multiple) {
args = {"-d", "start", "-ffilename", "stop"};
run();
EXPECT_EQ(2u, app.get_subcommands().size());
EXPECT_EQ(1, dummy);
EXPECT_EQ("filename", file);
}
TEST_F(SubcommandProgram, MultipleOtherOrder) {
args = {"start", "-d", "-ffilename", "stop"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(SubcommandProgram, MultipleArgs) {
args = {"start", "stop"};
run();
EXPECT_EQ(2u, app.get_subcommands().size());
}
TEST_F(SubcommandProgram, CaseCheck) {
args = {"Start"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"start"};
run();
start->ignore_case();
run();
args = {"Start"};
run();
}
TEST_F(TApp, SubcomInheritCaseCheck) {
app.ignore_case();
auto sub1 = app.add_subcommand("sub1");
auto sub2 = app.add_subcommand("sub2");
run();
EXPECT_EQ(0u, app.get_subcommands().size());
EXPECT_EQ(2u, app.get_subcommands({}).size());
EXPECT_EQ(1u, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size());
args = {"SuB1"};
run();
EXPECT_EQ(sub1, app.get_subcommands().at(0));
EXPECT_EQ(1u, app.get_subcommands().size());
app.clear();
EXPECT_EQ(0u, app.get_subcommands().size());
args = {"sUb2"};
run();
EXPECT_EQ(sub2, app.get_subcommands().at(0));
}
TEST_F(SubcommandProgram, UnderscoreCheck) {
args = {"start_"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"start"};
run();
start->ignore_underscore();
run();
args = {"_start_"};
run();
}
TEST_F(TApp, SubcomInheritUnderscoreCheck) {
app.ignore_underscore();
auto sub1 = app.add_subcommand("sub_option1");
auto sub2 = app.add_subcommand("sub_option2");
run();
EXPECT_EQ(0u, app.get_subcommands().size());
EXPECT_EQ(2u, app.get_subcommands({}).size());
EXPECT_EQ(1u, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub_option1"; }).size());
args = {"suboption1"};
run();
EXPECT_EQ(sub1, app.get_subcommands().at(0));
EXPECT_EQ(1u, app.get_subcommands().size());
app.clear();
EXPECT_EQ(0u, app.get_subcommands().size());
args = {"_suboption2"};
run();
EXPECT_EQ(sub2, app.get_subcommands().at(0));
}
TEST_F(SubcommandProgram, HelpOrder) {
args = {"-h"};
EXPECT_THROW(run(), CLI::CallForHelp);
args = {"start", "-h"};
EXPECT_THROW(run(), CLI::CallForHelp);
args = {"-h", "start"};
EXPECT_THROW(run(), CLI::CallForHelp);
}
TEST_F(SubcommandProgram, HelpAllOrder) {
args = {"--help-all"};
EXPECT_THROW(run(), CLI::CallForAllHelp);
args = {"start", "--help-all"};
EXPECT_THROW(run(), CLI::CallForAllHelp);
args = {"--help-all", "start"};
EXPECT_THROW(run(), CLI::CallForAllHelp);
}
TEST_F(SubcommandProgram, Callbacks) {
start->callback([]() { throw CLI::Success(); });
run();
args = {"start"};
EXPECT_THROW(run(), CLI::Success);
}
TEST_F(SubcommandProgram, Groups) {
std::string help = app.help();
EXPECT_THAT(help, Not(HasSubstr("More Commands:")));
EXPECT_THAT(help, HasSubstr("Subcommands:"));
start->group("More Commands");
help = app.help();
EXPECT_THAT(help, HasSubstr("More Commands:"));
EXPECT_THAT(help, HasSubstr("Subcommands:"));
// Case is ignored but for the first subcommand in a group.
stop->group("more commands");
help = app.help();
EXPECT_THAT(help, HasSubstr("More Commands:"));
EXPECT_THAT(help, Not(HasSubstr("Subcommands:")));
}
TEST_F(SubcommandProgram, ExtrasErrors) {
args = {"one", "two", "start", "three", "four"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"start", "three", "four"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"one", "two"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(SubcommandProgram, OrderedExtras) {
app.allow_extras();
args = {"one", "two", "start", "three", "four"};
EXPECT_THROW(run(), CLI::ExtrasError);
start->allow_extras();
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two"}));
EXPECT_EQ(start->remaining(), std::vector<std::string>({"three", "four"}));
EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "four"}));
args = {"one", "two", "start", "three", "--", "four"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two", "four"}));
EXPECT_EQ(start->remaining(), std::vector<std::string>({"three"}));
EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "four", "three"}));
}
TEST_F(SubcommandProgram, MixedOrderExtras) {
app.allow_extras();
start->allow_extras();
stop->allow_extras();
args = {"one", "two", "start", "three", "four", "stop", "five", "six"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two"}));
EXPECT_EQ(start->remaining(), std::vector<std::string>({"three", "four"}));
EXPECT_EQ(stop->remaining(), std::vector<std::string>({"five", "six"}));
EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "four", "five", "six"}));
args = {"one", "two", "stop", "three", "four", "start", "five", "six"};
run();
EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two"}));
EXPECT_EQ(stop->remaining(), std::vector<std::string>({"three", "four"}));
EXPECT_EQ(start->remaining(), std::vector<std::string>({"five", "six"}));
EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "four", "five", "six"}));
}
TEST_F(SubcommandProgram, CallbackOrder) {
std::vector<int> callback_order;
start->callback([&callback_order]() { callback_order.push_back(1); });
stop->callback([&callback_order]() { callback_order.push_back(2); });
args = {"start", "stop"};
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 2}));
callback_order.clear();
args = {"stop", "start"};
run();
EXPECT_EQ(callback_order, std::vector<int>({2, 1}));
}
TEST_F(SubcommandProgram, CallbackOrderImmediate) {
std::vector<int> callback_order;
start->callback([&callback_order]() { callback_order.push_back(1); })->immediate_callback();
stop->callback([&callback_order]() { callback_order.push_back(2); });
args = {"start", "stop", "start"};
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 1, 2}));
callback_order.clear();
args = {"stop", "start", "stop", "start"};
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 1, 2}));
}
struct ManySubcommands : public TApp {
CLI::App *sub1{nullptr};
CLI::App *sub2{nullptr};
CLI::App *sub3{nullptr};
CLI::App *sub4{nullptr};
ManySubcommands() {
app.allow_extras();
sub1 = app.add_subcommand("sub1");
sub2 = app.add_subcommand("sub2");
sub3 = app.add_subcommand("sub3");
sub4 = app.add_subcommand("sub4");
args = {"sub1", "sub2", "sub3"};
}
ManySubcommands(const ManySubcommands &) = delete;
ManySubcommands &operator=(const ManySubcommands &) = delete;
};
TEST_F(ManySubcommands, Required1Exact) {
app.require_subcommand(1);
run();
EXPECT_EQ(sub1->remaining(), vs_t({"sub2", "sub3"}));
EXPECT_EQ(app.remaining(true), vs_t({"sub2", "sub3"}));
}
TEST_F(ManySubcommands, Required2Exact) {
app.require_subcommand(2);
run();
EXPECT_EQ(sub2->remaining(), vs_t({"sub3"}));
}
TEST_F(ManySubcommands, Required4Failure) {
app.require_subcommand(4);
EXPECT_THROW(run(), CLI::RequiredError);
}
TEST_F(ManySubcommands, RemoveSub) {
run();
EXPECT_EQ(app.remaining_size(true), 0u);
app.remove_subcommand(sub1);
app.allow_extras();
run();
EXPECT_EQ(app.remaining_size(true), 1u);
}
TEST_F(ManySubcommands, RemoveSubFail) {
auto sub_sub = sub1->add_subcommand("subsub");
EXPECT_FALSE(app.remove_subcommand(sub_sub));
EXPECT_TRUE(sub1->remove_subcommand(sub_sub));
EXPECT_FALSE(app.remove_subcommand(nullptr));
}
TEST_F(ManySubcommands, manyIndexQuery) {
auto s1 = app.get_subcommand(0);
auto s2 = app.get_subcommand(1);
auto s3 = app.get_subcommand(2);
auto s4 = app.get_subcommand(3);
EXPECT_EQ(s1, sub1);
EXPECT_EQ(s2, sub2);
EXPECT_EQ(s3, sub3);
EXPECT_EQ(s4, sub4);
EXPECT_THROW(app.get_subcommand(4), CLI::OptionNotFound);
auto s0 = app.get_subcommand();
EXPECT_EQ(s0, sub1);
}
TEST_F(ManySubcommands, manyIndexQueryPtr) {
auto s1 = app.get_subcommand_ptr(0);
auto s2 = app.get_subcommand_ptr(1);
auto s3 = app.get_subcommand_ptr(2);
auto s4 = app.get_subcommand_ptr(3);
EXPECT_EQ(s1.get(), sub1);
EXPECT_EQ(s2.get(), sub2);
EXPECT_EQ(s3.get(), sub3);
EXPECT_EQ(s4.get(), sub4);
EXPECT_THROW(app.get_subcommand_ptr(4), CLI::OptionNotFound);
}
TEST_F(ManySubcommands, Required1Fuzzy) {
app.require_subcommand(0, 1);
run();
EXPECT_EQ(sub1->remaining(), vs_t({"sub2", "sub3"}));
app.require_subcommand(-1);
run();
EXPECT_EQ(sub1->remaining(), vs_t({"sub2", "sub3"}));
}
TEST_F(ManySubcommands, Required2Fuzzy) {
app.require_subcommand(0, 2);
run();
EXPECT_EQ(sub2->remaining(), vs_t({"sub3"}));
EXPECT_EQ(app.remaining(true), vs_t({"sub3"}));
app.require_subcommand(-2);
run();
EXPECT_EQ(sub2->remaining(), vs_t({"sub3"}));
}
TEST_F(ManySubcommands, Unlimited) {
run();
EXPECT_EQ(app.remaining(true), vs_t());
app.require_subcommand();
run();
EXPECT_EQ(app.remaining(true), vs_t());
app.require_subcommand(2, 0); // 2 or more
run();
EXPECT_EQ(app.remaining(true), vs_t());
}
TEST_F(ManySubcommands, HelpFlags) {
args = {"-h"};
EXPECT_THROW(run(), CLI::CallForHelp);
args = {"sub2", "-h"};
EXPECT_THROW(run(), CLI::CallForHelp);
args = {"-h", "sub2"};
EXPECT_THROW(run(), CLI::CallForHelp);
}
TEST_F(ManySubcommands, MaxCommands) {
app.require_subcommand(2);
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
// The extra subcommand counts as an extra
args = {"sub1", "sub2", "sub3"};
EXPECT_NO_THROW(run());
EXPECT_EQ(sub2->remaining().size(), 1u);
EXPECT_EQ(app.count_all(), 2u);
// Currently, setting sub2 to throw causes an extras error
// In the future, would passing on up to app's extras be better?
app.allow_extras(false);
sub1->allow_extras(false);
sub2->allow_extras(false);
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub2", "sub3"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(ManySubcommands, SubcommandExclusion) {
sub1->excludes(sub3);
sub2->excludes(sub3);
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub2", "sub3"};
EXPECT_THROW(run(), CLI::ExcludesError);
args = {"sub1", "sub2", "sub4"};
EXPECT_NO_THROW(run());
EXPECT_EQ(app.count_all(), 3u);
args = {"sub3", "sub4"};
EXPECT_NO_THROW(run());
}
TEST_F(ManySubcommands, SubcommandOptionExclusion) {
auto excluder_flag = app.add_flag("--exclude");
sub1->excludes(excluder_flag)->fallthrough();
sub2->excludes(excluder_flag)->fallthrough();
sub3->fallthrough();
sub4->fallthrough();
args = {"sub3", "sub4", "--exclude"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub3", "--exclude"};
EXPECT_THROW(run(), CLI::ExcludesError);
EXPECT_TRUE(sub1->remove_excludes(excluder_flag));
EXPECT_NO_THROW(run());
EXPECT_FALSE(sub1->remove_excludes(excluder_flag));
args = {"--exclude", "sub2", "sub4"};
EXPECT_THROW(run(), CLI::ExcludesError);
EXPECT_EQ(sub1->excludes(excluder_flag), sub1);
args = {"sub1", "--exclude", "sub2", "sub4"};
try {
run();
} catch(const CLI::ExcludesError &ee) {
EXPECT_NE(std::string(ee.what()).find("sub1"), std::string::npos);
}
}
TEST_F(ManySubcommands, SubcommandNeeds) {
sub1->needs(sub2);
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub2"};
EXPECT_NO_THROW(run());
args = {"sub1"};
EXPECT_THROW(run(), CLI::RequiresError);
sub1->needs(sub3);
args = {"sub1", "sub2", "sub3"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub2", "sub4"};
EXPECT_THROW(run(), CLI::RequiresError);
args = {"sub1", "sub2", "sub4"};
sub1->remove_needs(sub3);
EXPECT_NO_THROW(run());
}
TEST_F(ManySubcommands, SubcommandNeedsOptions) {
auto opt = app.add_flag("--subactive");
sub1->needs(opt);
sub1->fallthrough();
args = {"sub1", "--subactive"};
EXPECT_NO_THROW(run());
args = {"sub1"};
EXPECT_THROW(run(), CLI::RequiresError);
args = {"--subactive"};
EXPECT_NO_THROW(run());
auto opt2 = app.add_flag("--subactive2");
sub1->needs(opt2);
args = {"sub1", "--subactive"};
EXPECT_THROW(run(), CLI::RequiresError);
args = {"--subactive", "--subactive2", "sub1"};
EXPECT_NO_THROW(run());
sub1->remove_needs(opt2);
args = {"sub1", "--subactive"};
EXPECT_NO_THROW(run());
}
TEST_F(ManySubcommands, SubcommandNeedsOptionsCallbackOrdering) {
int count{0};
auto opt = app.add_flag("--subactive");
app.add_flag("--flag1");
sub1->needs(opt);
sub1->fallthrough();
sub1->parse_complete_callback([&count]() { ++count; });
args = {"sub1", "--flag1", "sub1", "--subactive"};
EXPECT_THROW(run(), CLI::RequiresError);
// the subcommand has to pass validation by the first callback
sub1->immediate_callback(false);
// now since the callback executes after
EXPECT_NO_THROW(run());
EXPECT_EQ(count, 1);
sub1->immediate_callback();
args = {"--subactive", "sub1"};
// now the required is processed first
EXPECT_NO_THROW(run());
}
TEST_F(ManySubcommands, SubcommandNeedsFail) {
auto opt = app.add_flag("--subactive");
auto opt2 = app.add_flag("--dummy");
sub1->needs(opt);
EXPECT_THROW(sub1->needs((CLI::Option *)nullptr), CLI::OptionNotFound);
EXPECT_THROW(sub1->needs((CLI::App *)nullptr), CLI::OptionNotFound);
EXPECT_THROW(sub1->needs(sub1), CLI::OptionNotFound);
EXPECT_TRUE(sub1->remove_needs(opt));
EXPECT_FALSE(sub1->remove_needs(opt2));
EXPECT_FALSE(sub1->remove_needs(sub1));
}
TEST_F(ManySubcommands, SubcommandRequired) {
sub1->required();
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub2", "sub3"};
EXPECT_NO_THROW(run());
args = {"sub3", "sub4"};
EXPECT_THROW(run(), CLI::RequiredError);
}
TEST_F(ManySubcommands, SubcommandDisabled) {
sub3->disabled();
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub1", "sub2", "sub3"};
app.allow_extras(false);
sub2->allow_extras(false);
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sub3", "sub4"};
EXPECT_THROW(run(), CLI::ExtrasError);
sub3->disabled(false);
args = {"sub3", "sub4"};
EXPECT_NO_THROW(run());
}
TEST_F(ManySubcommands, SubcommandTriggeredOff) {
app.allow_extras(false);
sub1->allow_extras(false);
sub2->allow_extras(false);
CLI::TriggerOff(sub1, sub2);
args = {"sub1", "sub2"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sub2", "sub1", "sub3"};
EXPECT_NO_THROW(run());
CLI::TriggerOff(sub1, {sub3, sub4});
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sub1", "sub2", "sub4"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(ManySubcommands, SubcommandTriggeredOn) {
app.allow_extras(false);
sub1->allow_extras(false);
sub2->allow_extras(false);
CLI::TriggerOn(sub1, sub2);
args = {"sub1", "sub2"};
EXPECT_NO_THROW(run());
args = {"sub2", "sub1", "sub4"};
EXPECT_THROW(run(), CLI::ExtrasError);
CLI::TriggerOn(sub1, {sub3, sub4});
sub2->disabled_by_default(false);
sub2->disabled(false);
EXPECT_NO_THROW(run());
args = {"sub3", "sub1", "sub2"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, UnnamedSub) {
double val{0.0};
auto sub = app.add_subcommand("", "empty name");
auto opt = sub->add_option("-v,--value", val);
args = {"-v", "4.56"};
run();
EXPECT_EQ(val, 4.56);
// make sure unnamed sub options can be found from the main app
auto opt2 = app.get_option("-v");
EXPECT_EQ(opt, opt2);
EXPECT_THROW(app.get_option("--vvvv"), CLI::OptionNotFound);
// now test in the constant context
const auto &appC = app;
auto opt3 = appC.get_option("-v");
EXPECT_EQ(opt3->get_name(), "--value");
EXPECT_THROW(appC.get_option("--vvvv"), CLI::OptionNotFound);
}
TEST_F(TApp, UnnamedSubMix) {
double val{0.0}, val2{0.0}, val3{0.0};
app.add_option("-t", val2);
auto sub1 = app.add_subcommand("", "empty name");
sub1->add_option("-v,--value", val);
auto sub2 = app.add_subcommand("", "empty name2");
sub2->add_option("-m,--mix", val3);
args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val3, 4.56);
EXPECT_EQ(app.count_all(), 3u);
}
TEST_F(TApp, UnnamedSubMixExtras) {
double val{0.0}, val2{0.0};
app.add_option("-t", val2);
auto sub = app.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
app.allow_extras();
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 2u);
EXPECT_EQ(sub->remaining_size(), 0u);
}
TEST_F(TApp, UnnamedSubNoExtras) {
double val{0.0}, val2{0.0};
app.add_option("-t", val2);
auto sub = app.add_subcommand();
sub->add_option("-v,--value", val);
args = {"-t", "5.93", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 0u);
EXPECT_EQ(sub->remaining_size(), 0u);
}
TEST_F(TApp, SubcommandAlias) {
double val{0.0};
auto sub = app.add_subcommand("sub1");
sub->alias("sub2");
sub->alias("sub3");
sub->add_option("-v,--value", val);
args = {"sub1", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
args = {"sub2", "--value", "-5"};
run();
EXPECT_EQ(val, -5.0);
args = {"sub3", "-v", "7"};
run();
EXPECT_EQ(val, 7);
auto &al = sub->get_aliases();
ASSERT_GE(al.size(), 2U);
EXPECT_EQ(al[0], "sub2");
EXPECT_EQ(al[1], "sub3");
sub->clear_aliases();
EXPECT_TRUE(al.empty());
}
TEST_F(TApp, SubcommandAliasIgnoreCaseUnderscore) {
double val{0.0};
auto sub = app.add_subcommand("sub1");
sub->alias("sub2");
sub->alias("sub3");
sub->ignore_case();
sub->add_option("-v,--value", val);
args = {"sub1", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
args = {"SUB2", "--value", "-5"};
run();
EXPECT_EQ(val, -5.0);
args = {"sUb3", "-v", "7"};
run();
EXPECT_EQ(val, 7);
sub->ignore_underscore();
args = {"sub_1", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
args = {"SUB_2", "--value", "-5"};
run();
EXPECT_EQ(val, -5.0);
args = {"sUb_3", "-v", "7"};
run();
EXPECT_EQ(val, 7);
sub->ignore_case(false);
args = {"sub_1", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
args = {"SUB_2", "--value", "-5"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sUb_3", "-v", "7"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, OptionGroupAlias) {
double val{0.0};
auto sub = app.add_option_group("sub1");
sub->alias("sub2");
sub->alias("sub3");
sub->add_option("-v,--value", val);
args = {"sub1", "-v", "-3"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"sub2", "--value", "-5"};
run();
EXPECT_EQ(val, -5.0);
args = {"sub3", "-v", "7"};
run();
EXPECT_EQ(val, 7);
args = {"-v", "-3"};
run();
EXPECT_EQ(val, -3);
}
TEST_F(TApp, AliasErrors) {
auto sub1 = app.add_subcommand("sub1");
auto sub2 = app.add_subcommand("sub2");
EXPECT_THROW(sub2->alias("this is a not a valid alias"), CLI::IncorrectConstruction);
EXPECT_THROW(sub2->alias("-alias"), CLI::IncorrectConstruction);
EXPECT_THROW(sub2->alias("alia$"), CLI::IncorrectConstruction);
EXPECT_THROW(app.add_subcommand("--bad_subcommand_name", "documenting the bad subcommand"),
CLI::IncorrectConstruction);
EXPECT_THROW(app.add_subcommand("documenting a subcommand", "sub3"), CLI::IncorrectConstruction);
// cannot alias to an existing subcommand
EXPECT_THROW(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
// aliasing to an existing name should be allowed
EXPECT_NO_THROW(sub1->alias(sub1->get_name()));
sub1->alias("les1")->alias("les2")->alias("les_3");
sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
EXPECT_THROW(sub2->alias("les2"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub2->name("sub1"), CLI::OptionAlreadyAdded);
sub2->ignore_underscore();
EXPECT_THROW(sub2->alias("les3"), CLI::OptionAlreadyAdded);
}
// test adding a subcommand via the pointer
TEST_F(TApp, ExistingSubcommandMatch) {
auto sshared = std::make_shared<CLI::App>("documenting the subcommand", "sub1");
sshared->alias("sub2")->alias("sub3");
EXPECT_EQ(sshared->get_name(), "sub1");
app.add_subcommand("sub1");
try {
app.add_subcommand(sshared);
// this should throw the next line should never be reached
EXPECT_FALSE(true);
} catch(const CLI::OptionAlreadyAdded &oaa) {
EXPECT_THAT(oaa.what(), HasSubstr("sub1"));
}
sshared->name("osub");
app.add_subcommand("sub2");
// now check that the aliases don't overlap
try {
app.add_subcommand(sshared);
// this should throw the next line should never be reached
EXPECT_FALSE(true);
} catch(const CLI::OptionAlreadyAdded &oaa) {
EXPECT_THAT(oaa.what(), HasSubstr("sub2"));
}
// now check that disabled subcommands can be added regardless of name
sshared->name("sub1");
sshared->disabled();
EXPECT_NO_THROW(app.add_subcommand(sshared));
}
TEST_F(TApp, AliasErrorsInOptionGroup) {
auto sub1 = app.add_subcommand("sub1");
auto g2 = app.add_option_group("g1");
auto sub2 = g2->add_subcommand("sub2");
// cannot alias to an existing subcommand even if it is in an option group
EXPECT_THROW(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
sub1->alias("les1")->alias("les2")->alias("les3");
sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
EXPECT_THROW(sub2->alias("les2"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
EXPECT_THROW(sub2->name("sub1"), CLI::OptionAlreadyAdded);
}
TEST(SharedSubTests, SharedSubcommand) {
double val{0.0}, val2{0.0}, val3{0.0}, val4{0.0};
CLI::App app1{"test program1"};
app1.add_option("-t", val2);
auto sub = app1.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
CLI::App app2{"test program2"};
app2.add_option("-m", val3);
// extract an owning ptr from app1 and add it to app2
auto subown = app1.get_subcommand_ptr(sub);
// add the extracted subcommand to a different app
app2.add_subcommand(std::move(subown));
EXPECT_THROW(app2.add_subcommand(CLI::App_p{}), CLI::IncorrectConstruction);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
input_t args2 = {"-m", "4.56", "-g", "8.235"};
std::reverse(std::begin(args1), std::end(args1));
std::reverse(std::begin(args2), std::end(args2));
app1.allow_extras();
app1.parse(args1);
app2.parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val3, 4.56);
EXPECT_EQ(val4, 8.235);
}
TEST(SharedSubTests, SharedSubIndependent) {
double val{0.0}, val2{0.0}, val4{0.0};
CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
app1->allow_extras();
app1->add_option("-t", val2);
auto sub = app1->add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
// extract an owning ptr from app1 and add it to app2
auto subown = app1->get_subcommand_ptr(sub);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
input_t args2 = {"-m", "4.56", "-g", "8.235"};
std::reverse(std::begin(args1), std::end(args1));
std::reverse(std::begin(args2), std::end(args2));
app1->parse(args1);
// destroy the first parser
app1 = nullptr;
// parse with the extracted subcommand
subown->parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val4, 8.235);
}
TEST(SharedSubTests, SharedSubIndependentReuse) {
double val{0.0}, val2{0.0}, val4{0.0};
CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
app1->allow_extras();
app1->add_option("-t", val2);
auto sub = app1->add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
// extract an owning ptr from app1 and add it to app2
auto subown = app1->get_subcommand_ptr(sub);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
std::reverse(std::begin(args1), std::end(args1));
auto args2 = args1;
app1->parse(args1);
// parse with the extracted subcommand
subown->parse("program1 -m 4.56 -g 8.235", true);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val4, 8.235);
val = 0.0;
val2 = 0.0;
EXPECT_EQ(subown->get_name(), "program1");
// this tests the name reset in subcommand since it was automatic
app1->parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
}
TEST_F(ManySubcommands, getSubtests) {
CLI::App_p sub2p = app.get_subcommand_ptr(sub2);
EXPECT_EQ(sub2p.get(), sub2);
EXPECT_THROW(app.get_subcommand_ptr(nullptr), CLI::OptionNotFound);
EXPECT_THROW(app.get_subcommand(nullptr), CLI::OptionNotFound);
CLI::App_p sub3p = app.get_subcommand_ptr(2);
EXPECT_EQ(sub3p.get(), sub3);
}
TEST_F(ManySubcommands, defaultDisabledSubcommand) {
sub1->fallthrough();
sub2->disabled_by_default();
run();
auto rem = app.remaining();
EXPECT_EQ(rem.size(), 1u);
EXPECT_EQ(rem[0], "sub2");
EXPECT_TRUE(sub2->get_disabled_by_default());
sub2->disabled(false);
EXPECT_FALSE(sub2->get_disabled());
run();
// this should disable it again even though it was disabled
rem = app.remaining();
EXPECT_EQ(rem.size(), 1u);
EXPECT_EQ(rem[0], "sub2");
EXPECT_TRUE(sub2->get_disabled_by_default());
EXPECT_TRUE(sub2->get_disabled());
}
TEST_F(ManySubcommands, defaultEnabledSubcommand) {
sub2->enabled_by_default();
run();
auto rem = app.remaining();
EXPECT_EQ(rem.size(), 0u);
EXPECT_TRUE(sub2->get_enabled_by_default());
sub2->disabled();
EXPECT_TRUE(sub2->get_disabled());
run();
// this should disable it again even though it was disabled
rem = app.remaining();
EXPECT_EQ(rem.size(), 0u);
EXPECT_TRUE(sub2->get_enabled_by_default());
EXPECT_FALSE(sub2->get_disabled());
}