Skip to content

Commit 4d0e8ad

Browse files
committed
Walk down three commands, picking a global up at each
The depth sweep went one level. Two are where a global option declared partway down first has anything above it and below it at once, which is the shape "prog -a remote -b add -c" has and nothing had asked about. The sweep now reads its twenty one command lines back at the root, at depth one and at depth two, and all three have to agree. They do. Beside it, the interleaved shape written out: one global picked up at each step down, the same line with all of them at the end, three arrangements in between, and the two that must fail, which are a global written above the command that declares it. Then the help text two levels down, where both levels of globals are listed under the one heading. All of it already worked. One thing did not, and it turned out to be the case that was wrong. Writing the same global twice, once on each side of a step down, is refused: an option is allowed once unless it says otherwise, and being global buys it no extra turns. The case says that now, and says the other half too, that an option declared multi() counts both and the step down between them is nothing to it. Windows 352, Linux 349 with the asserts live, macOS 348.
1 parent 1c33546 commit 4d0e8ad

1 file changed

Lines changed: 115 additions & 29 deletions

File tree

tests/auto/support/test_commandline.cpp

Lines changed: 115 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -854,45 +854,35 @@ BOOST_AUTO_TEST_CASE(test_the_parser_reads_the_same_a_level_down) {
854854
for (auto options : {Parser::Standard, Parser::AllowUnixGroupFlags}) {
855855
Parser flat(declare("prog"));
856856
Parser deep(Command("prog").addCommand(declare("inner")));
857+
Parser deeper(Command("prog").addCommand(Command("mid").addCommand(declare("inner"))));
857858

858859
for (const auto &line : lines) {
859-
std::vector<std::string> flatArgs = {"prog"};
860-
flatArgs.insert(flatArgs.end(), line.begin(), line.end());
861-
std::vector<std::string> deepArgs = {"prog", "inner"};
862-
deepArgs.insert(deepArgs.end(), line.begin(), line.end());
863-
864860
std::string what;
865861
for (const auto &item : line) {
866862
what += " " + item;
867863
}
868-
BOOST_CHECK_MESSAGE(readBack(flat.parse(flatArgs, options)) ==
869-
readBack(deep.parse(deepArgs, options)),
870-
"[" + what + "] reads one way at the root and another a level "
871-
"down:\n root " +
872-
readBack(flat.parse(flatArgs, options)) + "\n down " +
873-
readBack(deep.parse(deepArgs, options)));
864+
865+
const auto &at = [&line, options](const Parser &parser,
866+
std::vector<std::string> path) {
867+
path.insert(path.end(), line.begin(), line.end());
868+
return parser.parse(path, options);
869+
};
870+
auto root = readBack(at(flat, {"prog"}));
871+
for (const auto &deeperOne : {std::make_pair(std::vector<std::string>{"prog", "inner"},
872+
&deep),
873+
std::make_pair(std::vector<std::string>{"prog", "mid",
874+
"inner"},
875+
&deeper)}) {
876+
auto below = readBack(at(*deeperOne.second, deeperOne.first));
877+
BOOST_CHECK_MESSAGE(root == below,
878+
"[" + what + "] reads one way at the root and another " +
879+
std::to_string(deeperOne.first.size() - 1) +
880+
" down:\n root " + root + "\n down " + below);
881+
}
874882
}
875883
}
876884
}
877885

878-
// A response file is expanded before the command is looked for, so it may name one.
879-
BOOST_AUTO_TEST_CASE(test_a_response_file_may_name_a_subcommand) {
880-
auto path = std::filesystem::temp_directory_path() / "stdc_cli_response_nested.txt";
881-
{
882-
std::ofstream file(path, std::ios::binary);
883-
file << "build\n-j\n4\ntarget\n";
884-
}
885-
886-
Parser parser(Command("prog").addCommand(
887-
Command("build").addArgument(Argument("target")).addOption(Option({"-j"}, "Jobs").arg("n"))));
888-
auto result = ok(parser, {"@" + path.string()}, Parser::EnableResponseFile);
889-
BOOST_CHECK(result.commandPath() == std::vector<std::string>({"prog", "build"}));
890-
BOOST_CHECK_EQUAL(must(result.value(0)), "target");
891-
BOOST_CHECK_EQUAL(must(result.valueForOption<int>("-j")), 4);
892-
893-
std::filesystem::remove(path);
894-
}
895-
896886
// Everything the prior ladder does, asked once at the root and once a level down, because the
897887
// two only tell each other apart there. A root only tree makes "the line was empty" and "this
898888
// command was given nothing" the same value, which is how AutoSetWhenNoSymbols was broken for
@@ -2481,6 +2471,102 @@ BOOST_AUTO_TEST_CASE(test_a_subcommand_lists_what_it_inherited) {
24812471
BOOST_CHECK(!has(parser.parse(argv({})).helpText(), "Global options:"));
24822472
}
24832473

2474+
// A global written wherever it is in scope, on a line that walks down through three commands and
2475+
// picks one up at each. Every command below where it was declared can be written before, and the
2476+
// ones above have nothing to say about it.
2477+
BOOST_AUTO_TEST_CASE(test_globals_written_between_the_commands_that_declare_them) {
2478+
const auto &tree = [] {
2479+
Parser parser(Command("prog")
2480+
.addOption(Option({"--root-wide"}, "From the top").global())
2481+
.addCommand(Command("remote")
2482+
.addOption(Option({"--mid"}, "From the middle").global())
2483+
.addCommand(Command("add")
2484+
.addOption(Option({"--leaf"}, "Here"))
2485+
.addArgument(Argument("name")))));
2486+
parser.setTextWidth(80);
2487+
return parser;
2488+
};
2489+
2490+
// One picked up at each step down, which is the shape a program with layered options has.
2491+
auto parser = tree();
2492+
auto walked = ok(parser, {"--root-wide", "remote", "--mid", "add", "--leaf", "x"});
2493+
BOOST_CHECK(walked.commandPath() == std::vector<std::string>({"prog", "remote", "add"}));
2494+
BOOST_CHECK(walked.option("--root-wide").has_value());
2495+
BOOST_CHECK(walked.option("--mid").has_value());
2496+
BOOST_CHECK(walked.option("--leaf").has_value());
2497+
BOOST_CHECK_EQUAL(must(walked.value(0)), "x");
2498+
2499+
// All of them at the end reads the same, since a global stays in scope rather than being
2500+
// spent where it was written.
2501+
auto trailing = ok(parser, {"remote", "add", "--root-wide", "--mid", "--leaf", "x"});
2502+
BOOST_CHECK(trailing.option("--root-wide").has_value());
2503+
BOOST_CHECK(trailing.option("--mid").has_value());
2504+
BOOST_CHECK(trailing.option("--leaf").has_value());
2505+
2506+
// And so does every arrangement in between.
2507+
for (const auto &line : {
2508+
std::vector<std::string>{"--root-wide", "remote", "add", "--mid", "--leaf", "x"},
2509+
std::vector<std::string>{"remote", "--root-wide", "--mid", "add", "--leaf", "x"},
2510+
std::vector<std::string>{"remote", "--mid", "add", "--root-wide", "x", "--leaf"},
2511+
}) {
2512+
std::vector<std::string> args{"prog"};
2513+
args.insert(args.end(), line.begin(), line.end());
2514+
auto result = parser.parse(args);
2515+
BOOST_REQUIRE_MESSAGE(result.isValid(), result.errorText());
2516+
BOOST_CHECK(result.option("--root-wide").has_value());
2517+
BOOST_CHECK(result.option("--mid").has_value());
2518+
BOOST_CHECK(result.option("--leaf").has_value());
2519+
BOOST_CHECK_EQUAL(must(result.value(0)), "x");
2520+
}
2521+
2522+
// Written above where it was declared it is nobody's option, which is the rule that lets a
2523+
// subcommand name one thing what its parent names another.
2524+
bad(parser, {"--mid", "remote", "add", "x"}, ParseResult::UnknownOption);
2525+
bad(parser, {"remote", "--leaf", "add", "x"}, ParseResult::UnknownOption);
2526+
2527+
// Twice is once too many unless it said otherwise, on either side of a step down the same
2528+
// as anywhere else. Being global buys it no extra turns.
2529+
bad(parser, {"--root-wide", "remote", "--root-wide", "add", "x"},
2530+
ParseResult::OptionOccurTooMuch);
2531+
2532+
// Having said otherwise, both are counted, and the step down between them is nothing to it.
2533+
Parser repeatable(Command("prog")
2534+
.addOption(Option({"-v"}, "Say more").global().multi())
2535+
.addCommand(Command("remote").addCommand(
2536+
Command("add").addArgument(Argument("name")))));
2537+
auto twice = ok(repeatable, {"-v", "remote", "-v", "add", "x"});
2538+
BOOST_REQUIRE(twice.option("-v").has_value());
2539+
BOOST_CHECK_EQUAL(twice.option("-v")->count(), 2);
2540+
2541+
// Two levels of them are both listed, under the heading that says they came from above.
2542+
auto text = parser.parse(argv({"remote", "add", "x"})).helpText();
2543+
BOOST_CHECK(has(text, "Global options:"));
2544+
BOOST_CHECK(has(text, "--root-wide"));
2545+
BOOST_CHECK(has(text, "--mid"));
2546+
BOOST_CHECK(has(text, "Usage:\n prog remote add"));
2547+
// Its own stays where its own goes.
2548+
BOOST_CHECK(at(text, "Options:") < at(text, "Global options:"));
2549+
BOOST_CHECK(has(text, "--leaf"));
2550+
}
2551+
2552+
// A response file is expanded before the command is looked for, so it may name one.
2553+
BOOST_AUTO_TEST_CASE(test_a_response_file_may_name_a_subcommand) {
2554+
auto path = std::filesystem::temp_directory_path() / "stdc_cli_response_nested.txt";
2555+
{
2556+
std::ofstream file(path, std::ios::binary);
2557+
file << "build\n-j\n4\ntarget\n";
2558+
}
2559+
2560+
Parser parser(Command("prog").addCommand(
2561+
Command("build").addArgument(Argument("target")).addOption(Option({"-j"}, "Jobs").arg("n"))));
2562+
auto result = ok(parser, {"@" + path.string()}, Parser::EnableResponseFile);
2563+
BOOST_CHECK(result.commandPath() == std::vector<std::string>({"prog", "build"}));
2564+
BOOST_CHECK_EQUAL(must(result.value(0)), "target");
2565+
BOOST_CHECK_EQUAL(must(result.valueForOption<int>("-j")), 4);
2566+
2567+
std::filesystem::remove(path);
2568+
}
2569+
24842570
// Inheritance is from every command above, not only the one directly above.
24852571
BOOST_AUTO_TEST_CASE(test_globals_reach_a_grandchild) {
24862572
Parser parser(Command("prog")

0 commit comments

Comments
 (0)