Skip to content

Commit 66af1aa

Browse files
committed
Add subcommand unit tests.
1 parent 1feae67 commit 66af1aa

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-3
lines changed

llvm/lib/Option/ArgList.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,16 @@ StringRef ArgList::getSubcommand(
225225
if (SubCommands.size() == OldSize)
226226
OtherPositionals.push_back(A->getValue());
227227
}
228+
228229
// Invoke callbacks if necessary.
229-
if (SubCommands.size() > 1)
230+
if (SubCommands.size() > 1) {
230231
HandleMultipleSubcommands(SubCommands);
231-
if (!OtherPositionals.empty())
232+
return {};
233+
}
234+
if (!OtherPositionals.empty()) {
232235
HandleOtherPositionals(OtherPositionals);
236+
return {};
237+
}
233238

234239
if (SubCommands.size() == 1)
235240
return SubCommands.front();

llvm/unittests/Option/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ set(LLVM_LINK_COMPONENTS
44
)
55

66
set(LLVM_TARGET_DEFINITIONS Opts.td)
7-
87
tablegen(LLVM Opts.inc -gen-opt-parser-defs)
8+
9+
set(LLVM_TARGET_DEFINITIONS SubCommandOpts.td)
10+
tablegen(LLVM SubCommandOpts.inc -gen-opt-parser-defs)
11+
912
add_public_tablegen_target(OptsTestTableGen)
1013

1114
add_llvm_unittest(OptionTests
1215
OptionParsingTest.cpp
1316
OptionMarshallingTest.cpp
17+
OptionSubCommandsTest.cpp
1418
)
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/ADT/STLExtras.h"
10+
#include "llvm/Option/Arg.h"
11+
#include "llvm/Option/ArgList.h"
12+
#include "llvm/Option/OptTable.h"
13+
#include "llvm/Option/Option.h"
14+
#include "llvm/Support/raw_ostream.h"
15+
#include "gtest/gtest.h"
16+
17+
using namespace llvm;
18+
using namespace llvm::opt;
19+
20+
#if defined(__clang__)
21+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
22+
#endif
23+
24+
namespace {
25+
enum ID {
26+
OPT_INVALID = 0,
27+
#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \
28+
VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \
29+
VALUES, SUBCOMMANDIDS_OFFSET) \
30+
OPT_##ID,
31+
#include "SubCommandOpts.inc"
32+
#undef OPTION
33+
};
34+
#define OPTTABLE_STR_TABLE_CODE
35+
#include "SubCommandOpts.inc"
36+
#undef OPTTABLE_STR_TABLE_CODE
37+
38+
#define OPTTABLE_PREFIXES_TABLE_CODE
39+
#include "SubCommandOpts.inc"
40+
#undef OPTTABLE_PREFIXES_TABLE_CODE
41+
42+
#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
43+
#include "SubCommandOpts.inc"
44+
#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
45+
46+
#define OPTTABLE_SUBCOMMANDS_CODE
47+
#include "SubCommandOpts.inc"
48+
#undef OPTTABLE_SUBCOMMANDS_CODE
49+
50+
static constexpr OptTable::Info InfoTable[] = {
51+
#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
52+
#include "SubCommandOpts.inc"
53+
#undef OPTION
54+
};
55+
56+
class TestOptSubCommandTable : public GenericOptTable {
57+
public:
58+
TestOptSubCommandTable(bool IgnoreCase = false)
59+
: GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
60+
/*IgnoreCase=*/false, OptionSubCommands,
61+
OptionSubCommandIDsTable) {}
62+
};
63+
64+
// Test fixture
65+
template <typename T> class OptSubCommandTableTest : public ::testing::Test {};
66+
67+
// Test both precomputed and computed OptTables with the same suite of tests.
68+
using OptSubCommandTableTestTypes = ::testing::Types<TestOptSubCommandTable>;
69+
70+
TYPED_TEST_SUITE(OptSubCommandTableTest, OptSubCommandTableTestTypes, );
71+
72+
TYPED_TEST(OptSubCommandTableTest, SubCommandParsing) {
73+
TypeParam T;
74+
unsigned MAI, MAC;
75+
76+
std::string ErrMsg;
77+
raw_string_ostream RSO1(ErrMsg);
78+
79+
auto HandleMultipleSubcommands = [&](ArrayRef<StringRef> SubCommands) {
80+
ErrMsg.clear();
81+
RSO1 << "Multiple subcommands passed\n";
82+
for (auto SC : SubCommands) {
83+
RSO1 << "\n" << SC;
84+
}
85+
};
86+
87+
auto HandleOtherPositionals = [&](ArrayRef<StringRef> Positionals) {
88+
ErrMsg.clear();
89+
RSO1 << "Unregistered positionals passed\n";
90+
for (auto SC : Positionals) {
91+
RSO1 << "\n" << SC;
92+
}
93+
};
94+
95+
{
96+
// Test case 1: Toplevel option, no subcommand
97+
const char *Args[] = {"-version"};
98+
InputArgList AL = T.ParseArgs(Args, MAI, MAC);
99+
EXPECT_TRUE(AL.hasArg(OPT_version));
100+
StringRef SC = AL.getSubcommand(
101+
T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
102+
EXPECT_TRUE(SC.empty());
103+
EXPECT_FALSE(AL.hasArg(OPT_uppercase));
104+
EXPECT_FALSE(AL.hasArg(OPT_lowercase));
105+
}
106+
107+
{
108+
// Test case 2: Subcommand 'foo' with its valid options
109+
const char *Args[] = {"foo", "-uppercase"};
110+
InputArgList AL = T.ParseArgs(Args, MAI, MAC);
111+
StringRef SC = AL.getSubcommand(
112+
T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
113+
EXPECT_EQ(SC, "foo");
114+
EXPECT_TRUE(AL.hasArg(OPT_uppercase));
115+
EXPECT_FALSE(AL.hasArg(OPT_lowercase));
116+
EXPECT_FALSE(AL.hasArg(OPT_version));
117+
// Do not expect any error messages as this is a valid use case.
118+
EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"));
119+
EXPECT_EQ(std::string::npos,
120+
ErrMsg.find("Unregistered positionals passed"));
121+
}
122+
123+
{
124+
// Test case 3: Check valid use of subcommand which follows a valid
125+
// subcommand option.
126+
const char *Args[] = {"-uppercase", "foo"};
127+
InputArgList AL = T.ParseArgs(Args, MAI, MAC);
128+
StringRef SC = AL.getSubcommand(
129+
T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
130+
EXPECT_EQ(SC, "foo");
131+
EXPECT_TRUE(AL.hasArg(OPT_uppercase));
132+
EXPECT_FALSE(AL.hasArg(OPT_lowercase));
133+
EXPECT_FALSE(AL.hasArg(OPT_version));
134+
// Do not expect any error messages as this is a valid use case.
135+
EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"));
136+
EXPECT_EQ(std::string::npos,
137+
ErrMsg.find("Unregistered positionals passed"));
138+
}
139+
140+
{
141+
// Test case 4: Check invalid use of passing multiple subcommands.
142+
const char *Args[] = {"-uppercase", "foo", "bar"};
143+
InputArgList AL = T.ParseArgs(Args, MAI, MAC);
144+
StringRef SC = AL.getSubcommand(
145+
T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
146+
// No valid subcommand should be returned as this is an invalid invocation.
147+
EXPECT_TRUE(SC.empty());
148+
// Expect the multiple subcommands error message.
149+
EXPECT_NE(std::string::npos, ErrMsg.find("Multiple subcommands passed"));
150+
// Do not expect the rnregistered subcommands error message.
151+
EXPECT_EQ(std::string::npos,
152+
ErrMsg.find("Unregistered positionals passed"));
153+
}
154+
}
155+
156+
TYPED_TEST(OptSubCommandTableTest, SubCommandHelp) {
157+
TypeParam T;
158+
std::string Help;
159+
raw_string_ostream RSO(Help);
160+
161+
// Toplevel help
162+
T.printHelp(RSO, "Test Usage String", "OverviewString");
163+
EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
164+
EXPECT_NE(std::string::npos, Help.find("OverviewString"));
165+
EXPECT_NE(std::string::npos, Help.find("USAGE:"));
166+
EXPECT_NE(std::string::npos, Help.find("Test Usage String"));
167+
EXPECT_NE(std::string::npos, Help.find("SUBCOMMANDS:"));
168+
EXPECT_NE(std::string::npos, Help.find("foo"));
169+
EXPECT_NE(std::string::npos, Help.find("bar"));
170+
EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));
171+
EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));
172+
EXPECT_NE(std::string::npos, Help.find("OPTIONS:"));
173+
EXPECT_NE(std::string::npos, Help.find("--help"));
174+
EXPECT_NE(std::string::npos, Help.find("-version"));
175+
// uppercase is not a global option and should not be shown.
176+
EXPECT_EQ(std::string::npos, Help.find("-uppercase"));
177+
178+
// Help for subcommand foo
179+
Help.clear();
180+
StringRef SC1 = "foo";
181+
T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,
182+
Visibility(), SC1);
183+
EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
184+
EXPECT_NE(std::string::npos, Help.find("OverviewString"));
185+
// SubCommand "foo" definition for tablegen has NO dedicated usage string so
186+
// not expected to see USAGE.
187+
EXPECT_EQ(std::string::npos, Help.find("USAGE:"));
188+
EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));
189+
EXPECT_NE(std::string::npos, Help.find("-uppercase"));
190+
EXPECT_NE(std::string::npos, Help.find("-lowercase"));
191+
EXPECT_EQ(std::string::npos, Help.find("-version"));
192+
EXPECT_EQ(std::string::npos, Help.find("SUBCOMMANDS:"));
193+
194+
// Help for subcommand bar
195+
Help.clear();
196+
StringRef SC2 = "bar";
197+
T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,
198+
Visibility(), SC2);
199+
EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
200+
EXPECT_NE(std::string::npos, Help.find("OverviewString"));
201+
// SubCommand "bar" definition for tablegen has a dedicated usage string.
202+
EXPECT_NE(std::string::npos, Help.find("USAGE:"));
203+
EXPECT_NE(std::string::npos, Help.find("Subcommand bar <options>"));
204+
EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));
205+
EXPECT_NE(std::string::npos, Help.find("-uppercase"));
206+
// lowercase is not an option for bar and should not be shown.
207+
EXPECT_EQ(std::string::npos, Help.find("-lowercase"));
208+
// version is a global option and should not be shown.
209+
EXPECT_EQ(std::string::npos, Help.find("-version"));
210+
}
211+
} // end anonymous namespace
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
include "llvm/Option/OptParser.td"
2+
3+
def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">;
4+
5+
def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.",
6+
"Subcommand bar <options>">;
7+
8+
def help : Flag<["--"], "help">, HelpText<"Subcommand <subcommand> <options>">;
9+
10+
def version : Flag<["-"], "version">, HelpText<"Display the version number">;
11+
12+
def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>,
13+
HelpText<"Print in uppercase">;
14+
15+
def lowercase : Flag<["-"], "lowercase", [sc_foo]>,
16+
HelpText<"Print in lowercase">;

0 commit comments

Comments
 (0)