From 9d66d263ad4371160320f4f91720a345eb241471 Mon Sep 17 00:00:00 2001 From: Igor Kudrin Date: Sat, 9 Dec 2023 01:32:07 +0700 Subject: [CommandLine] Show '[subcommand]' in the help for less than 3 subcommands (#74557) When a tool defines only one or two subcommands, the `[subcommand]` part is not displayed in the `USAGE` help line. Note that a similar issue for printing the list of the subcommands has been fixed in https://reviews.llvm.org/D25463. --- llvm/lib/Support/CommandLine.cpp | 2 +- llvm/unittests/Support/CommandLineTest.cpp | 73 +++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp index a7e0cae..31f7997 100644 --- a/llvm/lib/Support/CommandLine.cpp +++ b/llvm/lib/Support/CommandLine.cpp @@ -2372,7 +2372,7 @@ public: if (Sub == &SubCommand::getTopLevel()) { outs() << "USAGE: " << GlobalParser->ProgramName; - if (Subs.size() > 2) + if (!Subs.empty()) outs() << " [subcommand]"; outs() << " [options]"; } else { diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp index 381fe70..a7db564 100644 --- a/llvm/unittests/Support/CommandLineTest.cpp +++ b/llvm/unittests/Support/CommandLineTest.cpp @@ -1347,29 +1347,32 @@ struct AutoDeleteFile { } }; +static std::string interceptStdout(std::function F) { + outs().flush(); // flush any output from previous tests + AutoDeleteFile File; + { + OutputRedirector Stdout(fileno(stdout)); + if (!Stdout.Valid) + return ""; + File.FilePath = Stdout.FilePath; + F(); + outs().flush(); + } + auto Buffer = MemoryBuffer::getFile(File.FilePath); + if (!Buffer) + return ""; + return Buffer->get()->getBuffer().str(); +} + template class PrintOptionTestBase : public ::testing::Test { public: // Return std::string because the output of a failing EXPECT check is // unreadable for StringRef. It also avoids any lifetime issues. template std::string runTest(Ts... OptionAttributes) { - outs().flush(); // flush any output from previous tests - AutoDeleteFile File; - { - OutputRedirector Stdout(fileno(stdout)); - if (!Stdout.Valid) - return ""; - File.FilePath = Stdout.FilePath; - - StackOption TestOption(Opt, cl::desc(HelpText), - OptionAttributes...); - Func(TestOption); - outs().flush(); - } - auto Buffer = MemoryBuffer::getFile(File.FilePath); - if (!Buffer) - return ""; - return Buffer->get()->getBuffer().str(); + StackOption TestOption(Opt, cl::desc(HelpText), + OptionAttributes...); + return interceptStdout([&]() { Func(TestOption); }); } enum class OptionValue { Val }; @@ -2206,4 +2209,40 @@ TEST(CommandLineTest, DefaultValue) { EXPECT_EQ(1, StrInitOption.getNumOccurrences()); } +TEST(CommandLineTest, HelpWithoutSubcommands) { + // Check that the help message does not contain the "[subcommand]" placeholder + // and the "SUBCOMMANDS" section if there are no subcommands. + cl::ResetCommandLineParser(); + StackOption Opt("opt", cl::init(false)); + const char *args[] = {"prog"}; + EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), + &llvm::nulls())); + auto Output = interceptStdout([]() { cl::PrintHelpMessage(); }); + EXPECT_NE(std::string::npos, Output.find("USAGE: prog [options]")) << Output; + EXPECT_EQ(std::string::npos, Output.find("SUBCOMMANDS:")) << Output; + cl::ResetCommandLineParser(); +} + +TEST(CommandLineTest, HelpWithSubcommands) { + // Check that the help message contains the "[subcommand]" placeholder in the + // "USAGE" line and describes subcommands. + cl::ResetCommandLineParser(); + StackSubCommand SC1("sc1", "First Subcommand"); + StackSubCommand SC2("sc2", "Second Subcommand"); + StackOption SC1Opt("sc1", cl::sub(SC1), cl::init(false)); + StackOption SC2Opt("sc2", cl::sub(SC2), cl::init(false)); + const char *args[] = {"prog"}; + EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), + &llvm::nulls())); + auto Output = interceptStdout([]() { cl::PrintHelpMessage(); }); + EXPECT_NE(std::string::npos, + Output.find("USAGE: prog [subcommand] [options]")) + << Output; + EXPECT_NE(std::string::npos, Output.find("SUBCOMMANDS:")) << Output; + EXPECT_NE(std::string::npos, Output.find("sc1 - First Subcommand")) << Output; + EXPECT_NE(std::string::npos, Output.find("sc2 - Second Subcommand")) + << Output; + cl::ResetCommandLineParser(); +} + } // anonymous namespace -- cgit v1.1