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