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