#include "app_helper.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::HasSubstr; using ::testing::Not; using vs_t = std::vector; 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, RequiredAndSubcoms) { // #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, 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, 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, NamelessSubComPositionals) { auto sub = app.add_subcommand(); int val = 1; sub->add_option("val", val); args = {"2"}; run(); EXPECT_EQ(val, 2); } 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, 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({"extra"})); EXPECT_EQ(sub->remaining(), std::vector()); args = {"extra1", "extra2", "sub"}; run(); EXPECT_EQ(app.remaining(), std::vector({"extra1", "extra2"})); EXPECT_EQ(sub->remaining(), std::vector()); args = {"sub", "extra1", "extra2"}; run(); EXPECT_EQ(app.remaining(), std::vector()); EXPECT_EQ(sub->remaining(), std::vector({"extra1", "extra2"})); args = {"extra1", "extra2", "sub", "extra3", "extra4"}; run(); EXPECT_EQ(app.remaining(), std::vector({"extra1", "extra2"})); EXPECT_EQ(app.remaining(true), std::vector({"extra1", "extra2", "extra3", "extra4"})); EXPECT_EQ(sub->remaining(), std::vector({"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, BadSubcomSearch) { 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({"other", "--simple", "--mine"})); } TEST_F(TApp, PrefixNoSeparation) { app.prefix_command(); std::vector 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 vals; app.add_option("--vals", vals); args = {"--vals", "1", "2", "3", "--", "other"}; run(); EXPECT_EQ(app.remaining(), std::vector({"other"})); EXPECT_EQ(vals, std::vector({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({"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); } struct SubcommandProgram : public TApp { CLI::App *start; CLI::App *stop; int dummy; std::string file; int count; 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({"one", "two"})); EXPECT_EQ(start->remaining(), std::vector({"three", "four"})); EXPECT_EQ(app.remaining(true), std::vector({"one", "two", "three", "four"})); args = {"one", "two", "start", "three", "--", "four"}; run(); EXPECT_EQ(app.remaining(), std::vector({"one", "two"})); EXPECT_EQ(start->remaining(), std::vector({"three", "--", "four"})); EXPECT_EQ(app.remaining(true), std::vector({"one", "two", "three", "--", "four"})); } 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({"one", "two"})); EXPECT_EQ(start->remaining(), std::vector({"three", "four"})); EXPECT_EQ(stop->remaining(), std::vector({"five", "six"})); EXPECT_EQ(app.remaining(true), std::vector({"one", "two", "three", "four", "five", "six"})); args = {"one", "two", "stop", "three", "four", "start", "five", "six"}; run(); EXPECT_EQ(app.remaining(), std::vector({"one", "two"})); EXPECT_EQ(stop->remaining(), std::vector({"three", "four"})); EXPECT_EQ(start->remaining(), std::vector({"five", "six"})); EXPECT_EQ(app.remaining(true), std::vector({"one", "two", "three", "four", "five", "six"})); } TEST_F(SubcommandProgram, CallbackOrder) { std::vector 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({1, 2})); callback_order.clear(); args = {"stop", "start"}; run(); EXPECT_EQ(callback_order, std::vector({2, 1})); } struct ManySubcommands : public TApp { CLI::App *sub1; CLI::App *sub2; CLI::App *sub3; CLI::App *sub4; 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"}; } }; 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, 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()); // the option comes later so doesn't exclude args = {"sub1", "sub3", "--exclude"}; EXPECT_THROW(run(), CLI::ExcludesError); args = {"--exclude", "sub2", "sub4"}; EXPECT_THROW(run(), CLI::ExcludesError); 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, 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(TApp, UnnamedSub) { double val; 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, val2, val3; 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, val2; 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, val2; 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(SharedSubTests, SharedSubcommand) { double val, val2, val3, val4; 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, val2, val4; CLI::App_p app1 = std::make_shared("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, val2, val4; CLI::App_p app1 = std::make_shared("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); }