[llvm] [Support] Show '[subcommand]' in the help for less than 3 subcommands (PR #74557)

Igor Kudrin via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 7 22:29:16 PST 2023


https://github.com/igorkudrin updated https://github.com/llvm/llvm-project/pull/74557

>From 7b056b8d26180e5b0aa4680942ae45f76052e206 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 5 Dec 2023 20:16:01 -0800
Subject: [PATCH] [CommandLine] Show '[subcommand]' in the help for less than 3
 subcommands

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
with 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 a7e0cae8b855d7..31f79972125da9 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -2372,7 +2372,7 @@ class HelpPrinter {
 
     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 381fe70b6b4815..a7db564e5fa240 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<void()> 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 <void (*Func)(const cl::Option &)>
 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 <typename... Ts> 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<OptionValue> 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<OptionValue> 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<bool> 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<bool> SC1Opt("sc1", cl::sub(SC1), cl::init(false));
+  StackOption<bool> 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



More information about the llvm-commits mailing list