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