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