xref: /llvm-project/llvm/unittests/Option/OptionParsingTest.cpp (revision 07bb29d8ffc3b82d5a7bb1217d93e8fa86e6969a)
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/Option.h"
13 #include "gtest/gtest.h"
14 
15 using namespace llvm;
16 using namespace llvm::opt;
17 
18 enum ID {
19   OPT_INVALID = 0, // This is not an option ID.
20 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
21                HELPTEXT, METAVAR, VALUES)                                      \
22   OPT_##ID,
23 #include "Opts.inc"
24   LastOption
25 #undef OPTION
26 };
27 
28 #define PREFIX(NAME, VALUE)                                                    \
29   static constexpr StringLiteral NAME##_init[] = VALUE;                        \
30   static constexpr ArrayRef<StringLiteral> NAME(NAME##_init,                   \
31                                                 std::size(NAME##_init) - 1);
32 #include "Opts.inc"
33 #undef PREFIX
34 
35 static constexpr const StringLiteral PrefixTable_init[] =
36 #define PREFIX_UNION(VALUES) VALUES
37 #include "Opts.inc"
38 #undef PREFIX_UNION
39     ;
40 static constexpr const ArrayRef<StringLiteral>
41     PrefixTable(PrefixTable_init, std::size(PrefixTable_init) - 1);
42 
43 enum OptionFlags {
44   OptFlag1 = (1 << 4),
45   OptFlag2 = (1 << 5),
46   OptFlag3 = (1 << 6)
47 };
48 
49 static constexpr OptTable::Info InfoTable[] = {
50 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
51                HELPTEXT, METAVAR, VALUES)                                      \
52   {PREFIX, NAME,  HELPTEXT,    METAVAR,     OPT_##ID,  Option::KIND##Class,    \
53    PARAM,  FLAGS, OPT_##GROUP, OPT_##ALIAS, ALIASARGS, VALUES},
54 #include "Opts.inc"
55 #undef OPTION
56 };
57 
58 namespace {
59 class TestOptTable : public GenericOptTable {
60 public:
61   TestOptTable(bool IgnoreCase = false)
62       : GenericOptTable(InfoTable, IgnoreCase) {}
63 };
64 
65 class TestPrecomputedOptTable : public PrecomputedOptTable {
66 public:
67   TestPrecomputedOptTable(bool IgnoreCase = false)
68       : PrecomputedOptTable(InfoTable, PrefixTable, IgnoreCase) {}
69 };
70 }
71 
72 const char *Args[] = {
73   "-A",
74   "-Bhi",
75   "--C=desu",
76   "-C", "bye",
77   "-D,adena",
78   "-E", "apple", "bloom",
79   "-Fblarg",
80   "-F", "42",
81   "-Gchuu", "2"
82   };
83 
84 // Test fixture
85 template <typename T> class OptTableTest : public ::testing::Test {};
86 
87 template <typename T> class DISABLED_OptTableTest : public ::testing::Test {};
88 
89 // Test both precomputed and computed OptTables with the same suite of tests.
90 using OptTableTestTypes =
91     ::testing::Types<TestOptTable, TestPrecomputedOptTable>;
92 
93 TYPED_TEST_SUITE(OptTableTest, OptTableTestTypes, );
94 TYPED_TEST_SUITE(DISABLED_OptTableTest, OptTableTestTypes, );
95 
96 TYPED_TEST(OptTableTest, OptionParsing) {
97   TypeParam T;
98   unsigned MAI, MAC;
99   InputArgList AL = T.ParseArgs(Args, MAI, MAC);
100 
101   // Check they all exist.
102   EXPECT_TRUE(AL.hasArg(OPT_A));
103   EXPECT_TRUE(AL.hasArg(OPT_B));
104   EXPECT_TRUE(AL.hasArg(OPT_C));
105   EXPECT_TRUE(AL.hasArg(OPT_D));
106   EXPECT_TRUE(AL.hasArg(OPT_E));
107   EXPECT_TRUE(AL.hasArg(OPT_F));
108   EXPECT_TRUE(AL.hasArg(OPT_G));
109 
110   // Check the values.
111   EXPECT_EQ("hi", AL.getLastArgValue(OPT_B));
112   EXPECT_EQ("bye", AL.getLastArgValue(OPT_C));
113   EXPECT_EQ("adena", AL.getLastArgValue(OPT_D));
114   std::vector<std::string> Es = AL.getAllArgValues(OPT_E);
115   EXPECT_EQ("apple", Es[0]);
116   EXPECT_EQ("bloom", Es[1]);
117   EXPECT_EQ("42", AL.getLastArgValue(OPT_F));
118   std::vector<std::string> Gs = AL.getAllArgValues(OPT_G);
119   EXPECT_EQ("chuu", Gs[0]);
120   EXPECT_EQ("2", Gs[1]);
121 
122   // Check the help text.
123   std::string Help;
124   raw_string_ostream RSO(Help);
125   T.printHelp(RSO, "test", "title!");
126   EXPECT_NE(std::string::npos, Help.find("-A"));
127 
128   // Check usage line.
129   T.printHelp(RSO, "name [options] file...", "title!");
130   EXPECT_NE(std::string::npos, Help.find("USAGE: name [options] file...\n"));
131 
132   // Test aliases.
133   auto Cs = AL.filtered(OPT_C);
134   ASSERT_NE(Cs.begin(), Cs.end());
135   EXPECT_EQ("desu", StringRef((*Cs.begin())->getValue()));
136   ArgStringList ASL;
137   (*Cs.begin())->render(AL, ASL);
138   ASSERT_EQ(2u, ASL.size());
139   EXPECT_EQ("-C", StringRef(ASL[0]));
140   EXPECT_EQ("desu", StringRef(ASL[1]));
141 }
142 
143 TYPED_TEST(OptTableTest, ParseWithFlagExclusions) {
144   TypeParam T;
145   unsigned MAI, MAC;
146 
147   // Exclude flag3 to avoid parsing as OPT_SLASH_C.
148   InputArgList AL = T.ParseArgs(Args, MAI, MAC,
149                                 /*FlagsToInclude=*/0,
150                                 /*FlagsToExclude=*/OptFlag3);
151   EXPECT_TRUE(AL.hasArg(OPT_A));
152   EXPECT_TRUE(AL.hasArg(OPT_C));
153   EXPECT_FALSE(AL.hasArg(OPT_SLASH_C));
154 
155   // Exclude flag1 to avoid parsing as OPT_C.
156   AL = T.ParseArgs(Args, MAI, MAC,
157                    /*FlagsToInclude=*/0,
158                    /*FlagsToExclude=*/OptFlag1);
159   EXPECT_TRUE(AL.hasArg(OPT_B));
160   EXPECT_FALSE(AL.hasArg(OPT_C));
161   EXPECT_TRUE(AL.hasArg(OPT_SLASH_C));
162 
163   const char *NewArgs[] = { "/C", "foo", "--C=bar" };
164   AL = T.ParseArgs(NewArgs, MAI, MAC);
165   EXPECT_TRUE(AL.hasArg(OPT_SLASH_C));
166   EXPECT_TRUE(AL.hasArg(OPT_C));
167   EXPECT_EQ("foo", AL.getLastArgValue(OPT_SLASH_C));
168   EXPECT_EQ("bar", AL.getLastArgValue(OPT_C));
169 }
170 
171 TYPED_TEST(OptTableTest, ParseAliasInGroup) {
172   TypeParam T;
173   unsigned MAI, MAC;
174 
175   const char *MyArgs[] = { "-I" };
176   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
177   EXPECT_TRUE(AL.hasArg(OPT_H));
178 }
179 
180 TYPED_TEST(OptTableTest, AliasArgs) {
181   TypeParam T;
182   unsigned MAI, MAC;
183 
184   const char *MyArgs[] = { "-J", "-Joo" };
185   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
186   EXPECT_TRUE(AL.hasArg(OPT_B));
187   EXPECT_EQ("foo", AL.getAllArgValues(OPT_B)[0]);
188   EXPECT_EQ("bar", AL.getAllArgValues(OPT_B)[1]);
189 }
190 
191 TYPED_TEST(OptTableTest, IgnoreCase) {
192   TypeParam T(true);
193   unsigned MAI, MAC;
194 
195   const char *MyArgs[] = { "-a", "-joo" };
196   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
197   EXPECT_TRUE(AL.hasArg(OPT_A));
198   EXPECT_TRUE(AL.hasArg(OPT_B));
199 }
200 
201 TYPED_TEST(OptTableTest, DoNotIgnoreCase) {
202   TypeParam T;
203   unsigned MAI, MAC;
204 
205   const char *MyArgs[] = { "-a", "-joo" };
206   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
207   EXPECT_FALSE(AL.hasArg(OPT_A));
208   EXPECT_FALSE(AL.hasArg(OPT_B));
209 }
210 
211 TYPED_TEST(OptTableTest, SlurpEmpty) {
212   TypeParam T;
213   unsigned MAI, MAC;
214 
215   const char *MyArgs[] = { "-A", "-slurp" };
216   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
217   EXPECT_TRUE(AL.hasArg(OPT_A));
218   EXPECT_TRUE(AL.hasArg(OPT_Slurp));
219   EXPECT_EQ(0U, AL.getAllArgValues(OPT_Slurp).size());
220 }
221 
222 TYPED_TEST(OptTableTest, Slurp) {
223   TypeParam T;
224   unsigned MAI, MAC;
225 
226   const char *MyArgs[] = { "-A", "-slurp", "-B", "--", "foo" };
227   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
228   EXPECT_EQ(AL.size(), 2U);
229   EXPECT_TRUE(AL.hasArg(OPT_A));
230   EXPECT_FALSE(AL.hasArg(OPT_B));
231   EXPECT_TRUE(AL.hasArg(OPT_Slurp));
232   EXPECT_EQ(3U, AL.getAllArgValues(OPT_Slurp).size());
233   EXPECT_EQ("-B", AL.getAllArgValues(OPT_Slurp)[0]);
234   EXPECT_EQ("--", AL.getAllArgValues(OPT_Slurp)[1]);
235   EXPECT_EQ("foo", AL.getAllArgValues(OPT_Slurp)[2]);
236 }
237 
238 TYPED_TEST(OptTableTest, SlurpJoinedEmpty) {
239   TypeParam T;
240   unsigned MAI, MAC;
241 
242   const char *MyArgs[] = { "-A", "-slurpjoined" };
243   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
244   EXPECT_TRUE(AL.hasArg(OPT_A));
245   EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
246   EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined).size(), 0U);
247 }
248 
249 TYPED_TEST(OptTableTest, SlurpJoinedOneJoined) {
250   TypeParam T;
251   unsigned MAI, MAC;
252 
253   const char *MyArgs[] = { "-A", "-slurpjoinedfoo" };
254   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
255   EXPECT_TRUE(AL.hasArg(OPT_A));
256   EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
257   EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined).size(), 1U);
258   EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined)[0], "foo");
259 }
260 
261 TYPED_TEST(OptTableTest, SlurpJoinedAndSeparate) {
262   TypeParam T;
263   unsigned MAI, MAC;
264 
265   const char *MyArgs[] = { "-A", "-slurpjoinedfoo", "bar", "baz" };
266   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
267   EXPECT_TRUE(AL.hasArg(OPT_A));
268   EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
269   EXPECT_EQ(3U, AL.getAllArgValues(OPT_SlurpJoined).size());
270   EXPECT_EQ("foo", AL.getAllArgValues(OPT_SlurpJoined)[0]);
271   EXPECT_EQ("bar", AL.getAllArgValues(OPT_SlurpJoined)[1]);
272   EXPECT_EQ("baz", AL.getAllArgValues(OPT_SlurpJoined)[2]);
273 }
274 
275 TYPED_TEST(OptTableTest, SlurpJoinedButSeparate) {
276   TypeParam T;
277   unsigned MAI, MAC;
278 
279   const char *MyArgs[] = { "-A", "-slurpjoined", "foo", "bar", "baz" };
280   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
281   EXPECT_TRUE(AL.hasArg(OPT_A));
282   EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
283   EXPECT_EQ(3U, AL.getAllArgValues(OPT_SlurpJoined).size());
284   EXPECT_EQ("foo", AL.getAllArgValues(OPT_SlurpJoined)[0]);
285   EXPECT_EQ("bar", AL.getAllArgValues(OPT_SlurpJoined)[1]);
286   EXPECT_EQ("baz", AL.getAllArgValues(OPT_SlurpJoined)[2]);
287 }
288 
289 TYPED_TEST(OptTableTest, FlagAliasToJoined) {
290   TypeParam T;
291   unsigned MAI, MAC;
292 
293   // Check that a flag alias provides an empty argument to a joined option.
294   const char *MyArgs[] = { "-K" };
295   InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
296   EXPECT_EQ(AL.size(), 1U);
297   EXPECT_TRUE(AL.hasArg(OPT_B));
298   EXPECT_EQ(1U, AL.getAllArgValues(OPT_B).size());
299   EXPECT_EQ("", AL.getAllArgValues(OPT_B)[0]);
300 }
301 
302 TYPED_TEST(OptTableTest, FindNearest) {
303   TypeParam T;
304   std::string Nearest;
305 
306   // Options that are too short should not be considered
307   // "near" other short options.
308   EXPECT_GT(T.findNearest("-A", Nearest), 4U);
309   EXPECT_GT(T.findNearest("/C", Nearest), 4U);
310   EXPECT_GT(T.findNearest("--C=foo", Nearest), 4U);
311 
312   // The nearest candidate should mirror the amount of prefix
313   // characters used in the original string.
314   EXPECT_EQ(1U, T.findNearest("-blorb", Nearest));
315   EXPECT_EQ(Nearest, "-blorp");
316   EXPECT_EQ(1U, T.findNearest("--blorm", Nearest));
317   EXPECT_EQ(Nearest, "--blorp");
318   EXPECT_EQ(1U, T.findNearest("-blarg", Nearest));
319   EXPECT_EQ(Nearest, "-blarn");
320   EXPECT_EQ(1U, T.findNearest("--blarm", Nearest));
321   EXPECT_EQ(Nearest, "--blarn");
322   EXPECT_EQ(1U, T.findNearest("-fjormp", Nearest));
323   EXPECT_EQ(Nearest, "--fjormp");
324 
325   // The nearest candidate respects the prefix and value delimiter
326   // of the original string.
327   EXPECT_EQ(1U, T.findNearest("/framb:foo", Nearest));
328   EXPECT_EQ(Nearest, "/cramb:foo");
329 
330   // `--glormp` should have an editing distance > 0 from `--glormp=`.
331   EXPECT_GT(T.findNearest("--glorrmp", Nearest), 0U);
332   EXPECT_EQ(Nearest, "--glorrmp=");
333   EXPECT_EQ(0U, T.findNearest("--glorrmp=foo", Nearest));
334 
335   // `--blurmps` should correct to `--blurmp`, not `--blurmp=`, even though
336   // both naively have an editing distance of 1.
337   EXPECT_EQ(1U, T.findNearest("--blurmps", Nearest));
338   EXPECT_EQ(Nearest, "--blurmp");
339 
340   // ...but `--blurmps=foo` should correct to `--blurmp=foo`.
341   EXPECT_EQ(1U, T.findNearest("--blurmps=foo", Nearest));
342   EXPECT_EQ(Nearest, "--blurmp=foo");
343 
344   // Flags should be included and excluded as specified.
345   EXPECT_EQ(1U, T.findNearest("-doopf", Nearest, /*FlagsToInclude=*/OptFlag2));
346   EXPECT_EQ(Nearest, "-doopf2");
347   EXPECT_EQ(1U, T.findNearest("-doopf", Nearest,
348                               /*FlagsToInclude=*/0,
349                               /*FlagsToExclude=*/OptFlag2));
350   EXPECT_EQ(Nearest, "-doopf1");
351 }
352 
353 TYPED_TEST(DISABLED_OptTableTest, FindNearestFIXME) {
354   TypeParam T;
355   std::string Nearest;
356 
357   // FIXME: Options with joined values should not have those values considered
358   // when calculating distance. The test below would fail if run, but it should
359   // succeed.
360   EXPECT_EQ(1U, T.findNearest("--erbghFoo", Nearest));
361   EXPECT_EQ(Nearest, "--ermghFoo");
362 }
363 
364 TYPED_TEST(OptTableTest, ParseGroupedShortOptions) {
365   TypeParam T;
366   T.setGroupedShortOptions(true);
367   unsigned MAI, MAC;
368 
369   // Grouped short options can be followed by a long Flag (-Joo), or a non-Flag
370   // option (-C=1).
371   const char *Args1[] = {"-AIJ", "-AIJoo", "-AC=1"};
372   InputArgList AL = T.ParseArgs(Args1, MAI, MAC);
373   EXPECT_TRUE(AL.hasArg(OPT_A));
374   EXPECT_TRUE(AL.hasArg(OPT_H));
375   ASSERT_EQ((size_t)2, AL.getAllArgValues(OPT_B).size());
376   EXPECT_EQ("foo", AL.getAllArgValues(OPT_B)[0]);
377   EXPECT_EQ("bar", AL.getAllArgValues(OPT_B)[1]);
378   ASSERT_TRUE(AL.hasArg(OPT_C));
379   EXPECT_EQ("1", AL.getAllArgValues(OPT_C)[0]);
380 
381   // Prefer a long option to a short option.
382   const char *Args2[] = {"-AB"};
383   InputArgList AL2 = T.ParseArgs(Args2, MAI, MAC);
384   EXPECT_TRUE(!AL2.hasArg(OPT_A));
385   EXPECT_TRUE(AL2.hasArg(OPT_AB));
386 
387   // Short options followed by a long option. We probably should disallow this.
388   const char *Args3[] = {"-AIblorp"};
389   InputArgList AL3 = T.ParseArgs(Args3, MAI, MAC);
390   EXPECT_TRUE(AL3.hasArg(OPT_A));
391   EXPECT_TRUE(AL3.hasArg(OPT_Blorp));
392 }
393 
394 TYPED_TEST(OptTableTest, UnknownOptions) {
395   TypeParam T;
396   unsigned MAI, MAC;
397   const char *Args[] = {"-u", "--long", "0"};
398   for (int I = 0; I < 2; ++I) {
399     T.setGroupedShortOptions(I != 0);
400     InputArgList AL = T.ParseArgs(Args, MAI, MAC);
401     const std::vector<std::string> Unknown = AL.getAllArgValues(OPT_UNKNOWN);
402     ASSERT_EQ((size_t)2, Unknown.size());
403     EXPECT_EQ("-u", Unknown[0]);
404     EXPECT_EQ("--long", Unknown[1]);
405   }
406 }
407 
408 TYPED_TEST(OptTableTest, FlagsWithoutValues) {
409   TypeParam T;
410   T.setGroupedShortOptions(true);
411   unsigned MAI, MAC;
412   const char *Args[] = {"-A=1", "-A="};
413   InputArgList AL = T.ParseArgs(Args, MAI, MAC);
414   const std::vector<std::string> Unknown = AL.getAllArgValues(OPT_UNKNOWN);
415   ASSERT_EQ((size_t)2, Unknown.size());
416   EXPECT_EQ("-A=1", Unknown[0]);
417   EXPECT_EQ("-A=", Unknown[1]);
418 }
419 
420 TYPED_TEST(OptTableTest, UnknownGroupedShortOptions) {
421   TypeParam T;
422   T.setGroupedShortOptions(true);
423   unsigned MAI, MAC;
424   const char *Args[] = {"-AuzK", "-AuzK"};
425   InputArgList AL = T.ParseArgs(Args, MAI, MAC);
426   const std::vector<std::string> Unknown = AL.getAllArgValues(OPT_UNKNOWN);
427   ASSERT_EQ((size_t)4, Unknown.size());
428   EXPECT_EQ("-u", Unknown[0]);
429   EXPECT_EQ("-z", Unknown[1]);
430   EXPECT_EQ("-u", Unknown[2]);
431   EXPECT_EQ("-z", Unknown[3]);
432 }
433