1 | //===- llvm/unittest/Support/CommandLineTest.cpp - CommandLine 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/Support/CommandLine.h" |
10 | #include "llvm/ADT/STLExtras.h" |
11 | #include "llvm/ADT/SmallString.h" |
12 | #include "llvm/ADT/StringRef.h" |
13 | #include "llvm/Config/config.h" |
14 | #include "llvm/Support/Allocator.h" |
15 | #include "llvm/Support/FileSystem.h" |
16 | #include "llvm/Support/InitLLVM.h" |
17 | #include "llvm/Support/MemoryBuffer.h" |
18 | #include "llvm/Support/Path.h" |
19 | #include "llvm/Support/Program.h" |
20 | #include "llvm/Support/StringSaver.h" |
21 | #include "llvm/Support/VirtualFileSystem.h" |
22 | #include "llvm/Support/raw_ostream.h" |
23 | #include "llvm/TargetParser/Host.h" |
24 | #include "llvm/TargetParser/Triple.h" |
25 | #include "llvm/Testing/Support/SupportHelpers.h" |
26 | #include "gmock/gmock.h" |
27 | #include "gtest/gtest.h" |
28 | #include <fstream> |
29 | #include <stdlib.h> |
30 | #include <string> |
31 | |
32 | using namespace llvm; |
33 | using llvm::unittest::TempDir; |
34 | using llvm::unittest::TempFile; |
35 | |
36 | namespace { |
37 | |
38 | MATCHER(StringEquality, "Checks if two char* are equal as strings" ) { |
39 | return std::string(std::get<0>(arg)) == std::string(std::get<1>(arg)); |
40 | } |
41 | |
42 | class TempEnvVar { |
43 | public: |
44 | TempEnvVar(const char *name, const char *value) |
45 | : name(name) { |
46 | const char *old_value = getenv(name: name); |
47 | EXPECT_EQ(nullptr, old_value) << old_value; |
48 | #if HAVE_SETENV |
49 | setenv(name: name, value: value, replace: true); |
50 | #endif |
51 | } |
52 | |
53 | ~TempEnvVar() { |
54 | #if HAVE_SETENV |
55 | // Assume setenv and unsetenv come together. |
56 | unsetenv(name: name); |
57 | #else |
58 | (void)name; // Suppress -Wunused-private-field. |
59 | #endif |
60 | } |
61 | |
62 | private: |
63 | const char *const name; |
64 | }; |
65 | |
66 | template <typename T, typename Base = cl::opt<T>> |
67 | class StackOption : public Base { |
68 | public: |
69 | template <class... Ts> |
70 | explicit StackOption(Ts &&... Ms) : Base(std::forward<Ts>(Ms)...) {} |
71 | |
72 | ~StackOption() override { this->removeArgument(); } |
73 | |
74 | template <class DT> StackOption<T> &operator=(const DT &V) { |
75 | Base::operator=(V); |
76 | return *this; |
77 | } |
78 | }; |
79 | |
80 | class StackSubCommand : public cl::SubCommand { |
81 | public: |
82 | StackSubCommand(StringRef Name, |
83 | StringRef Description = StringRef()) |
84 | : SubCommand(Name, Description) {} |
85 | |
86 | StackSubCommand() : SubCommand() {} |
87 | |
88 | ~StackSubCommand() { unregisterSubCommand(); } |
89 | }; |
90 | |
91 | |
92 | cl::OptionCategory TestCategory("Test Options" , "Description" ); |
93 | TEST(CommandLineTest, ModifyExisitingOption) { |
94 | StackOption<int> TestOption("test-option" , cl::desc("old description" )); |
95 | |
96 | static const char Description[] = "New description" ; |
97 | static const char ArgString[] = "new-test-option" ; |
98 | static const char ValueString[] = "Integer" ; |
99 | |
100 | StringMap<cl::Option *> &Map = |
101 | cl::getRegisteredOptions(Sub&: cl::SubCommand::getTopLevel()); |
102 | |
103 | ASSERT_EQ(Map.count("test-option" ), 1u) << "Could not find option in map." ; |
104 | |
105 | cl::Option *Retrieved = Map["test-option" ]; |
106 | ASSERT_EQ(&TestOption, Retrieved) << "Retrieved wrong option." ; |
107 | |
108 | ASSERT_NE(Retrieved->Categories.end(), |
109 | find_if(Retrieved->Categories, |
110 | [&](const llvm::cl::OptionCategory *Cat) { |
111 | return Cat == &cl::getGeneralCategory(); |
112 | })) |
113 | << "Incorrect default option category." ; |
114 | |
115 | Retrieved->addCategory(C&: TestCategory); |
116 | ASSERT_NE(Retrieved->Categories.end(), |
117 | find_if(Retrieved->Categories, |
118 | [&](const llvm::cl::OptionCategory *Cat) { |
119 | return Cat == &TestCategory; |
120 | })) |
121 | << "Failed to modify option's option category." ; |
122 | |
123 | Retrieved->setDescription(Description); |
124 | ASSERT_STREQ(Retrieved->HelpStr.data(), Description) |
125 | << "Changing option description failed." ; |
126 | |
127 | Retrieved->setArgStr(ArgString); |
128 | ASSERT_STREQ(ArgString, Retrieved->ArgStr.data()) |
129 | << "Failed to modify option's Argument string." ; |
130 | |
131 | Retrieved->setValueStr(ValueString); |
132 | ASSERT_STREQ(Retrieved->ValueStr.data(), ValueString) |
133 | << "Failed to modify option's Value string." ; |
134 | |
135 | Retrieved->setHiddenFlag(cl::Hidden); |
136 | ASSERT_EQ(cl::Hidden, TestOption.getOptionHiddenFlag()) << |
137 | "Failed to modify option's hidden flag." ; |
138 | } |
139 | |
140 | TEST(CommandLineTest, UseOptionCategory) { |
141 | StackOption<int> TestOption2("test-option" , cl::cat(TestCategory)); |
142 | |
143 | ASSERT_NE(TestOption2.Categories.end(), |
144 | find_if(TestOption2.Categories, |
145 | [&](const llvm::cl::OptionCategory *Cat) { |
146 | return Cat == &TestCategory; |
147 | })) |
148 | << "Failed to assign Option Category." ; |
149 | } |
150 | |
151 | TEST(CommandLineTest, UseMultipleCategories) { |
152 | StackOption<int> TestOption2("test-option2" , cl::cat(TestCategory), |
153 | cl::cat(cl::getGeneralCategory()), |
154 | cl::cat(cl::getGeneralCategory())); |
155 | |
156 | // Make sure cl::getGeneralCategory() wasn't added twice. |
157 | ASSERT_EQ(TestOption2.Categories.size(), 2U); |
158 | |
159 | ASSERT_NE(TestOption2.Categories.end(), |
160 | find_if(TestOption2.Categories, |
161 | [&](const llvm::cl::OptionCategory *Cat) { |
162 | return Cat == &TestCategory; |
163 | })) |
164 | << "Failed to assign Option Category." ; |
165 | ASSERT_NE(TestOption2.Categories.end(), |
166 | find_if(TestOption2.Categories, |
167 | [&](const llvm::cl::OptionCategory *Cat) { |
168 | return Cat == &cl::getGeneralCategory(); |
169 | })) |
170 | << "Failed to assign General Category." ; |
171 | |
172 | cl::OptionCategory AnotherCategory("Additional test Options" , "Description" ); |
173 | StackOption<int> TestOption("test-option" , cl::cat(TestCategory), |
174 | cl::cat(AnotherCategory)); |
175 | ASSERT_EQ(TestOption.Categories.end(), |
176 | find_if(TestOption.Categories, |
177 | [&](const llvm::cl::OptionCategory *Cat) { |
178 | return Cat == &cl::getGeneralCategory(); |
179 | })) |
180 | << "Failed to remove General Category." ; |
181 | ASSERT_NE(TestOption.Categories.end(), |
182 | find_if(TestOption.Categories, |
183 | [&](const llvm::cl::OptionCategory *Cat) { |
184 | return Cat == &TestCategory; |
185 | })) |
186 | << "Failed to assign Option Category." ; |
187 | ASSERT_NE(TestOption.Categories.end(), |
188 | find_if(TestOption.Categories, |
189 | [&](const llvm::cl::OptionCategory *Cat) { |
190 | return Cat == &AnotherCategory; |
191 | })) |
192 | << "Failed to assign Another Category." ; |
193 | } |
194 | |
195 | typedef void ParserFunction(StringRef Source, StringSaver &Saver, |
196 | SmallVectorImpl<const char *> &NewArgv, |
197 | bool MarkEOLs); |
198 | |
199 | void testCommandLineTokenizer(ParserFunction *parse, StringRef Input, |
200 | ArrayRef<const char *> Output, |
201 | bool MarkEOLs = false) { |
202 | SmallVector<const char *, 0> Actual; |
203 | BumpPtrAllocator A; |
204 | StringSaver Saver(A); |
205 | parse(Input, Saver, Actual, MarkEOLs); |
206 | EXPECT_EQ(Output.size(), Actual.size()); |
207 | for (unsigned I = 0, E = Actual.size(); I != E; ++I) { |
208 | if (I < Output.size()) { |
209 | EXPECT_STREQ(Output[I], Actual[I]); |
210 | } |
211 | } |
212 | } |
213 | |
214 | TEST(CommandLineTest, TokenizeGNUCommandLine) { |
215 | const char Input[] = |
216 | "foo\\ bar \"foo bar\" \'foo bar\' 'foo\\\\bar' -DFOO=bar\\(\\) " |
217 | "foo\"bar\"baz C:\\\\src\\\\foo.cpp \"C:\\src\\foo.cpp\"" ; |
218 | const char *const Output[] = { |
219 | "foo bar" , "foo bar" , "foo bar" , "foo\\bar" , |
220 | "-DFOO=bar()" , "foobarbaz" , "C:\\src\\foo.cpp" , "C:srcfoo.cpp" }; |
221 | testCommandLineTokenizer(parse: cl::TokenizeGNUCommandLine, Input, Output); |
222 | } |
223 | |
224 | TEST(CommandLineTest, TokenizeWindowsCommandLine1) { |
225 | const char Input[] = |
226 | R"(a\b c\\d e\\"f g" h\"i j\\\"k "lmn" o pqr "st \"u" \v)" ; |
227 | const char *const Output[] = { "a\\b" , "c\\\\d" , "e\\f g" , "h\"i" , "j\\\"k" , |
228 | "lmn" , "o" , "pqr" , "st \"u" , "\\v" }; |
229 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input, Output); |
230 | } |
231 | |
232 | TEST(CommandLineTest, TokenizeWindowsCommandLine2) { |
233 | const char Input[] = "clang -c -DFOO=\"\"\"ABC\"\"\" x.cpp" ; |
234 | const char *const Output[] = { "clang" , "-c" , "-DFOO=\"ABC\"" , "x.cpp" }; |
235 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input, Output); |
236 | } |
237 | |
238 | TEST(CommandLineTest, TokenizeWindowsCommandLineQuotedLastArgument) { |
239 | // Whitespace at the end of the command line doesn't cause an empty last word |
240 | const char Input0[] = R"(a b c d )" ; |
241 | const char *const Output0[] = {"a" , "b" , "c" , "d" }; |
242 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input: Input0, Output: Output0); |
243 | |
244 | // But an explicit "" does |
245 | const char Input1[] = R"(a b c d "")" ; |
246 | const char *const Output1[] = {"a" , "b" , "c" , "d" , "" }; |
247 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input: Input1, Output: Output1); |
248 | |
249 | // An unterminated quoted string is also emitted as an argument word, empty |
250 | // or not |
251 | const char Input2[] = R"(a b c d ")" ; |
252 | const char *const Output2[] = {"a" , "b" , "c" , "d" , "" }; |
253 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input: Input2, Output: Output2); |
254 | const char Input3[] = R"(a b c d "text)" ; |
255 | const char *const Output3[] = {"a" , "b" , "c" , "d" , "text" }; |
256 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input: Input3, Output: Output3); |
257 | } |
258 | |
259 | TEST(CommandLineTest, TokenizeWindowsCommandLineExeName) { |
260 | const char Input1[] = |
261 | R"("C:\Program Files\Whatever\"clang.exe z.c -DY=\"x\")" ; |
262 | const char *const Output1[] = {"C:\\Program Files\\Whatever\\clang.exe" , |
263 | "z.c" , "-DY=\"x\"" }; |
264 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLineFull, Input: Input1, Output: Output1); |
265 | |
266 | const char Input2[] = "\"a\\\"b c\\\"d\n\"e\\\"f g\\\"h\n" ; |
267 | const char *const Output2[] = {"a\\b" , "c\"d" , nullptr, |
268 | "e\\f" , "g\"h" , nullptr}; |
269 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLineFull, Input: Input2, Output: Output2, |
270 | /*MarkEOLs=*/true); |
271 | |
272 | const char Input3[] = R"(\\server\share\subdir\clang.exe)" ; |
273 | const char *const Output3[] = {"\\\\server\\share\\subdir\\clang.exe" }; |
274 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLineFull, Input: Input3, Output: Output3); |
275 | } |
276 | |
277 | TEST(CommandLineTest, TokenizeAndMarkEOLs) { |
278 | // Clang uses EOL marking in response files to support options that consume |
279 | // the rest of the arguments on the current line, but do not consume arguments |
280 | // from subsequent lines. For example, given these rsp files contents: |
281 | // /c /Zi /O2 |
282 | // /Oy- /link /debug /opt:ref |
283 | // /Zc:ThreadsafeStatics- |
284 | // |
285 | // clang-cl needs to treat "/debug /opt:ref" as linker flags, and everything |
286 | // else as compiler flags. The tokenizer inserts nullptr sentinels into the |
287 | // output so that clang-cl can find the end of the current line. |
288 | const char Input[] = "clang -Xclang foo\n\nfoo\"bar\"baz\n x.cpp\n" ; |
289 | const char *const Output[] = {"clang" , "-Xclang" , "foo" , |
290 | nullptr, nullptr, "foobarbaz" , |
291 | nullptr, "x.cpp" , nullptr}; |
292 | testCommandLineTokenizer(parse: cl::TokenizeWindowsCommandLine, Input, Output, |
293 | /*MarkEOLs=*/true); |
294 | testCommandLineTokenizer(parse: cl::TokenizeGNUCommandLine, Input, Output, |
295 | /*MarkEOLs=*/true); |
296 | } |
297 | |
298 | TEST(CommandLineTest, TokenizeConfigFile1) { |
299 | const char *Input = "\\" ; |
300 | const char *const Output[] = { "\\" }; |
301 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
302 | } |
303 | |
304 | TEST(CommandLineTest, TokenizeConfigFile2) { |
305 | const char *Input = "\\abc" ; |
306 | const char *const Output[] = { "abc" }; |
307 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
308 | } |
309 | |
310 | TEST(CommandLineTest, TokenizeConfigFile3) { |
311 | const char *Input = "abc\\" ; |
312 | const char *const Output[] = { "abc\\" }; |
313 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
314 | } |
315 | |
316 | TEST(CommandLineTest, TokenizeConfigFile4) { |
317 | const char *Input = "abc\\\n123" ; |
318 | const char *const Output[] = { "abc123" }; |
319 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
320 | } |
321 | |
322 | TEST(CommandLineTest, TokenizeConfigFile5) { |
323 | const char *Input = "abc\\\r\n123" ; |
324 | const char *const Output[] = { "abc123" }; |
325 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
326 | } |
327 | |
328 | TEST(CommandLineTest, TokenizeConfigFile6) { |
329 | const char *Input = "abc\\\n" ; |
330 | const char *const Output[] = { "abc" }; |
331 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
332 | } |
333 | |
334 | TEST(CommandLineTest, TokenizeConfigFile7) { |
335 | const char *Input = "abc\\\r\n" ; |
336 | const char *const Output[] = { "abc" }; |
337 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
338 | } |
339 | |
340 | TEST(CommandLineTest, TokenizeConfigFile8) { |
341 | SmallVector<const char *, 0> Actual; |
342 | BumpPtrAllocator A; |
343 | StringSaver Saver(A); |
344 | cl::tokenizeConfigFile(Source: "\\\n" , Saver, NewArgv&: Actual, /*MarkEOLs=*/false); |
345 | EXPECT_TRUE(Actual.empty()); |
346 | } |
347 | |
348 | TEST(CommandLineTest, TokenizeConfigFile9) { |
349 | SmallVector<const char *, 0> Actual; |
350 | BumpPtrAllocator A; |
351 | StringSaver Saver(A); |
352 | cl::tokenizeConfigFile(Source: "\\\r\n" , Saver, NewArgv&: Actual, /*MarkEOLs=*/false); |
353 | EXPECT_TRUE(Actual.empty()); |
354 | } |
355 | |
356 | TEST(CommandLineTest, TokenizeConfigFile10) { |
357 | const char *Input = "\\\nabc" ; |
358 | const char *const Output[] = { "abc" }; |
359 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
360 | } |
361 | |
362 | TEST(CommandLineTest, TokenizeConfigFile11) { |
363 | const char *Input = "\\\r\nabc" ; |
364 | const char *const Output[] = { "abc" }; |
365 | testCommandLineTokenizer(parse: cl::tokenizeConfigFile, Input, Output); |
366 | } |
367 | |
368 | TEST(CommandLineTest, AliasesWithArguments) { |
369 | static const size_t ARGC = 3; |
370 | const char *const Inputs[][ARGC] = { |
371 | { "-tool" , "-actual=x" , "-extra" }, |
372 | { "-tool" , "-actual" , "x" }, |
373 | { "-tool" , "-alias=x" , "-extra" }, |
374 | { "-tool" , "-alias" , "x" } |
375 | }; |
376 | |
377 | for (size_t i = 0, e = std::size(Inputs); i < e; ++i) { |
378 | StackOption<std::string> Actual("actual" ); |
379 | StackOption<bool> ("extra" ); |
380 | StackOption<std::string> Input(cl::Positional); |
381 | |
382 | cl::alias Alias("alias" , llvm::cl::aliasopt(Actual)); |
383 | |
384 | cl::ParseCommandLineOptions(argc: ARGC, argv: Inputs[i]); |
385 | EXPECT_EQ("x" , Actual); |
386 | EXPECT_EQ(0, Input.getNumOccurrences()); |
387 | |
388 | Alias.removeArgument(); |
389 | } |
390 | } |
391 | |
392 | void testAliasRequired(int argc, const char *const *argv) { |
393 | StackOption<std::string> Option("option" , cl::Required); |
394 | cl::alias Alias("o" , llvm::cl::aliasopt(Option)); |
395 | |
396 | cl::ParseCommandLineOptions(argc, argv); |
397 | EXPECT_EQ("x" , Option); |
398 | EXPECT_EQ(1, Option.getNumOccurrences()); |
399 | |
400 | Alias.removeArgument(); |
401 | } |
402 | |
403 | TEST(CommandLineTest, AliasRequired) { |
404 | const char *opts1[] = { "-tool" , "-option=x" }; |
405 | const char *opts2[] = { "-tool" , "-o" , "x" }; |
406 | testAliasRequired(argc: std::size(opts1), argv: opts1); |
407 | testAliasRequired(argc: std::size(opts2), argv: opts2); |
408 | } |
409 | |
410 | TEST(CommandLineTest, HideUnrelatedOptions) { |
411 | StackOption<int> TestOption1("hide-option-1" ); |
412 | StackOption<int> TestOption2("hide-option-2" , cl::cat(TestCategory)); |
413 | |
414 | cl::HideUnrelatedOptions(Category&: TestCategory); |
415 | |
416 | ASSERT_EQ(cl::ReallyHidden, TestOption1.getOptionHiddenFlag()) |
417 | << "Failed to hide extra option." ; |
418 | ASSERT_EQ(cl::NotHidden, TestOption2.getOptionHiddenFlag()) |
419 | << "Hid extra option that should be visable." ; |
420 | |
421 | StringMap<cl::Option *> &Map = |
422 | cl::getRegisteredOptions(Sub&: cl::SubCommand::getTopLevel()); |
423 | ASSERT_TRUE(Map.count("help" ) == (size_t)0 || |
424 | cl::NotHidden == Map["help" ]->getOptionHiddenFlag()) |
425 | << "Hid default option that should be visable." ; |
426 | } |
427 | |
428 | cl::OptionCategory TestCategory2("Test Options set 2" , "Description" ); |
429 | |
430 | TEST(CommandLineTest, HideUnrelatedOptionsMulti) { |
431 | StackOption<int> TestOption1("multi-hide-option-1" ); |
432 | StackOption<int> TestOption2("multi-hide-option-2" , cl::cat(TestCategory)); |
433 | StackOption<int> TestOption3("multi-hide-option-3" , cl::cat(TestCategory2)); |
434 | |
435 | const cl::OptionCategory *VisibleCategories[] = {&TestCategory, |
436 | &TestCategory2}; |
437 | |
438 | cl::HideUnrelatedOptions(Categories: ArrayRef(VisibleCategories)); |
439 | |
440 | ASSERT_EQ(cl::ReallyHidden, TestOption1.getOptionHiddenFlag()) |
441 | << "Failed to hide extra option." ; |
442 | ASSERT_EQ(cl::NotHidden, TestOption2.getOptionHiddenFlag()) |
443 | << "Hid extra option that should be visable." ; |
444 | ASSERT_EQ(cl::NotHidden, TestOption3.getOptionHiddenFlag()) |
445 | << "Hid extra option that should be visable." ; |
446 | |
447 | StringMap<cl::Option *> &Map = |
448 | cl::getRegisteredOptions(Sub&: cl::SubCommand::getTopLevel()); |
449 | ASSERT_TRUE(Map.count("help" ) == (size_t)0 || |
450 | cl::NotHidden == Map["help" ]->getOptionHiddenFlag()) |
451 | << "Hid default option that should be visable." ; |
452 | } |
453 | |
454 | TEST(CommandLineTest, SetMultiValues) { |
455 | StackOption<int> Option("option" ); |
456 | const char *args[] = {"prog" , "-option=1" , "-option=2" }; |
457 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), |
458 | &llvm::nulls())); |
459 | EXPECT_EQ(Option, 2); |
460 | } |
461 | |
462 | TEST(CommandLineTest, SetValueInSubcategories) { |
463 | cl::ResetCommandLineParser(); |
464 | |
465 | StackSubCommand SC1("sc1" , "First subcommand" ); |
466 | StackSubCommand SC2("sc2" , "Second subcommand" ); |
467 | |
468 | StackOption<bool> TopLevelOpt("top-level" , cl::init(Val: false)); |
469 | StackOption<bool> SC1Opt("sc1" , cl::sub(SC1), cl::init(Val: false)); |
470 | StackOption<bool> SC2Opt("sc2" , cl::sub(SC2), cl::init(Val: false)); |
471 | |
472 | EXPECT_FALSE(TopLevelOpt); |
473 | EXPECT_FALSE(SC1Opt); |
474 | EXPECT_FALSE(SC2Opt); |
475 | const char *args[] = {"prog" , "-top-level" }; |
476 | EXPECT_TRUE( |
477 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
478 | EXPECT_TRUE(TopLevelOpt); |
479 | EXPECT_FALSE(SC1Opt); |
480 | EXPECT_FALSE(SC2Opt); |
481 | |
482 | TopLevelOpt = false; |
483 | |
484 | cl::ResetAllOptionOccurrences(); |
485 | EXPECT_FALSE(TopLevelOpt); |
486 | EXPECT_FALSE(SC1Opt); |
487 | EXPECT_FALSE(SC2Opt); |
488 | const char *args2[] = {"prog" , "sc1" , "-sc1" }; |
489 | EXPECT_TRUE( |
490 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
491 | EXPECT_FALSE(TopLevelOpt); |
492 | EXPECT_TRUE(SC1Opt); |
493 | EXPECT_FALSE(SC2Opt); |
494 | |
495 | SC1Opt = false; |
496 | |
497 | cl::ResetAllOptionOccurrences(); |
498 | EXPECT_FALSE(TopLevelOpt); |
499 | EXPECT_FALSE(SC1Opt); |
500 | EXPECT_FALSE(SC2Opt); |
501 | const char *args3[] = {"prog" , "sc2" , "-sc2" }; |
502 | EXPECT_TRUE( |
503 | cl::ParseCommandLineOptions(3, args3, StringRef(), &llvm::nulls())); |
504 | EXPECT_FALSE(TopLevelOpt); |
505 | EXPECT_FALSE(SC1Opt); |
506 | EXPECT_TRUE(SC2Opt); |
507 | } |
508 | |
509 | TEST(CommandLineTest, LookupFailsInWrongSubCommand) { |
510 | cl::ResetCommandLineParser(); |
511 | |
512 | StackSubCommand SC1("sc1" , "First subcommand" ); |
513 | StackSubCommand SC2("sc2" , "Second subcommand" ); |
514 | |
515 | StackOption<bool> SC1Opt("sc1" , cl::sub(SC1), cl::init(Val: false)); |
516 | StackOption<bool> SC2Opt("sc2" , cl::sub(SC2), cl::init(Val: false)); |
517 | |
518 | std::string Errs; |
519 | raw_string_ostream OS(Errs); |
520 | |
521 | const char *args[] = {"prog" , "sc1" , "-sc2" }; |
522 | EXPECT_FALSE(cl::ParseCommandLineOptions(3, args, StringRef(), &OS)); |
523 | OS.flush(); |
524 | EXPECT_FALSE(Errs.empty()); |
525 | } |
526 | |
527 | TEST(CommandLineTest, TopLevelOptInSubcommand) { |
528 | enum LiteralOptionEnum { |
529 | foo, |
530 | bar, |
531 | baz, |
532 | }; |
533 | |
534 | cl::ResetCommandLineParser(); |
535 | |
536 | // This is a top-level option and not associated with a subcommand. |
537 | // A command line using subcommand should parse both subcommand options and |
538 | // top-level options. A valid use case is that users of llvm command line |
539 | // tools should be able to specify top-level options defined in any library. |
540 | StackOption<std::string> TopLevelOpt("str" , cl::init(Val: "txt" ), |
541 | cl::desc("A top-level option." )); |
542 | |
543 | StackSubCommand SC("sc" , "Subcommand" ); |
544 | StackOption<std::string> PositionalOpt( |
545 | cl::Positional, cl::desc("positional argument test coverage" ), |
546 | cl::sub(SC)); |
547 | StackOption<LiteralOptionEnum> LiteralOpt( |
548 | cl::desc("literal argument test coverage" ), cl::sub(SC), cl::init(Val: bar), |
549 | cl::values(clEnumVal(foo, "foo" ), clEnumVal(bar, "bar" ), |
550 | clEnumVal(baz, "baz" ))); |
551 | StackOption<bool> EnableOpt("enable" , cl::sub(SC), cl::init(Val: false)); |
552 | StackOption<int> ThresholdOpt("threshold" , cl::sub(SC), cl::init(Val: 1)); |
553 | |
554 | const char *PositionalOptVal = "input-file" ; |
555 | const char *args[] = {"prog" , "sc" , PositionalOptVal, |
556 | "-enable" , "--str=csv" , "--threshold=2" }; |
557 | |
558 | // cl::ParseCommandLineOptions returns true on success. Otherwise, it will |
559 | // print the error message to stderr and exit in this setting (`Errs` ostream |
560 | // is not set). |
561 | ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]), args, |
562 | StringRef())); |
563 | EXPECT_STREQ(PositionalOpt.getValue().c_str(), PositionalOptVal); |
564 | EXPECT_TRUE(EnableOpt); |
565 | // Tests that the value of `str` option is `csv` as specified. |
566 | EXPECT_STREQ(TopLevelOpt.getValue().c_str(), "csv" ); |
567 | EXPECT_EQ(ThresholdOpt, 2); |
568 | |
569 | for (auto &[LiteralOptVal, WantLiteralOpt] : |
570 | {std::pair{"--bar" , bar}, {"--foo" , foo}, {"--baz" , baz}}) { |
571 | const char *args[] = {"prog" , "sc" , LiteralOptVal}; |
572 | ASSERT_TRUE(cl::ParseCommandLineOptions(sizeof(args) / sizeof(args[0]), |
573 | args, StringRef())); |
574 | |
575 | // Tests that literal options are parsed correctly. |
576 | EXPECT_EQ(LiteralOpt, WantLiteralOpt); |
577 | } |
578 | } |
579 | |
580 | TEST(CommandLineTest, AddToAllSubCommands) { |
581 | cl::ResetCommandLineParser(); |
582 | |
583 | StackSubCommand SC1("sc1" , "First subcommand" ); |
584 | StackOption<bool> AllOpt("everywhere" , cl::sub(cl::SubCommand::getAll()), |
585 | cl::init(Val: false)); |
586 | StackSubCommand SC2("sc2" , "Second subcommand" ); |
587 | |
588 | EXPECT_TRUE(cl::SubCommand::getTopLevel().OptionsMap.contains("everywhere" )); |
589 | EXPECT_TRUE(cl::SubCommand::getAll().OptionsMap.contains("everywhere" )); |
590 | EXPECT_TRUE(SC1.OptionsMap.contains("everywhere" )); |
591 | EXPECT_TRUE(SC2.OptionsMap.contains("everywhere" )); |
592 | |
593 | const char *args[] = {"prog" , "-everywhere" }; |
594 | const char *args2[] = {"prog" , "sc1" , "-everywhere" }; |
595 | const char *args3[] = {"prog" , "sc2" , "-everywhere" }; |
596 | |
597 | std::string Errs; |
598 | raw_string_ostream OS(Errs); |
599 | |
600 | EXPECT_FALSE(AllOpt); |
601 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args, StringRef(), &OS)); |
602 | EXPECT_TRUE(AllOpt); |
603 | |
604 | AllOpt = false; |
605 | |
606 | cl::ResetAllOptionOccurrences(); |
607 | EXPECT_FALSE(AllOpt); |
608 | EXPECT_TRUE(cl::ParseCommandLineOptions(3, args2, StringRef(), &OS)); |
609 | EXPECT_TRUE(AllOpt); |
610 | |
611 | AllOpt = false; |
612 | |
613 | cl::ResetAllOptionOccurrences(); |
614 | EXPECT_FALSE(AllOpt); |
615 | EXPECT_TRUE(cl::ParseCommandLineOptions(3, args3, StringRef(), &OS)); |
616 | EXPECT_TRUE(AllOpt); |
617 | |
618 | // Since all parsing succeeded, the error message should be empty. |
619 | OS.flush(); |
620 | EXPECT_TRUE(Errs.empty()); |
621 | } |
622 | |
623 | TEST(CommandLineTest, ReparseCommandLineOptions) { |
624 | cl::ResetCommandLineParser(); |
625 | |
626 | StackOption<bool> TopLevelOpt( |
627 | "top-level" , cl::sub(cl::SubCommand::getTopLevel()), cl::init(Val: false)); |
628 | |
629 | const char *args[] = {"prog" , "-top-level" }; |
630 | |
631 | EXPECT_FALSE(TopLevelOpt); |
632 | EXPECT_TRUE( |
633 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
634 | EXPECT_TRUE(TopLevelOpt); |
635 | |
636 | TopLevelOpt = false; |
637 | |
638 | cl::ResetAllOptionOccurrences(); |
639 | EXPECT_FALSE(TopLevelOpt); |
640 | EXPECT_TRUE( |
641 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
642 | EXPECT_TRUE(TopLevelOpt); |
643 | } |
644 | |
645 | TEST(CommandLineTest, RemoveFromRegularSubCommand) { |
646 | cl::ResetCommandLineParser(); |
647 | |
648 | StackSubCommand SC("sc" , "Subcommand" ); |
649 | StackOption<bool> RemoveOption("remove-option" , cl::sub(SC), cl::init(Val: false)); |
650 | StackOption<bool> KeepOption("keep-option" , cl::sub(SC), cl::init(Val: false)); |
651 | |
652 | const char *args[] = {"prog" , "sc" , "-remove-option" }; |
653 | |
654 | std::string Errs; |
655 | raw_string_ostream OS(Errs); |
656 | |
657 | EXPECT_FALSE(RemoveOption); |
658 | EXPECT_TRUE(cl::ParseCommandLineOptions(3, args, StringRef(), &OS)); |
659 | EXPECT_TRUE(RemoveOption); |
660 | OS.flush(); |
661 | EXPECT_TRUE(Errs.empty()); |
662 | |
663 | RemoveOption.removeArgument(); |
664 | |
665 | cl::ResetAllOptionOccurrences(); |
666 | EXPECT_FALSE(cl::ParseCommandLineOptions(3, args, StringRef(), &OS)); |
667 | OS.flush(); |
668 | EXPECT_FALSE(Errs.empty()); |
669 | } |
670 | |
671 | TEST(CommandLineTest, RemoveFromTopLevelSubCommand) { |
672 | cl::ResetCommandLineParser(); |
673 | |
674 | StackOption<bool> TopLevelRemove("top-level-remove" , |
675 | cl::sub(cl::SubCommand::getTopLevel()), |
676 | cl::init(Val: false)); |
677 | StackOption<bool> TopLevelKeep("top-level-keep" , |
678 | cl::sub(cl::SubCommand::getTopLevel()), |
679 | cl::init(Val: false)); |
680 | |
681 | const char *args[] = {"prog" , "-top-level-remove" }; |
682 | |
683 | EXPECT_FALSE(TopLevelRemove); |
684 | EXPECT_TRUE( |
685 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
686 | EXPECT_TRUE(TopLevelRemove); |
687 | |
688 | TopLevelRemove.removeArgument(); |
689 | |
690 | cl::ResetAllOptionOccurrences(); |
691 | EXPECT_FALSE( |
692 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
693 | } |
694 | |
695 | TEST(CommandLineTest, RemoveFromAllSubCommands) { |
696 | cl::ResetCommandLineParser(); |
697 | |
698 | StackSubCommand SC1("sc1" , "First Subcommand" ); |
699 | StackSubCommand SC2("sc2" , "Second Subcommand" ); |
700 | StackOption<bool> RemoveOption( |
701 | "remove-option" , cl::sub(cl::SubCommand::getAll()), cl::init(Val: false)); |
702 | StackOption<bool> KeepOption("keep-option" , cl::sub(cl::SubCommand::getAll()), |
703 | cl::init(Val: false)); |
704 | |
705 | const char *args0[] = {"prog" , "-remove-option" }; |
706 | const char *args1[] = {"prog" , "sc1" , "-remove-option" }; |
707 | const char *args2[] = {"prog" , "sc2" , "-remove-option" }; |
708 | |
709 | // It should work for all subcommands including the top-level. |
710 | EXPECT_FALSE(RemoveOption); |
711 | EXPECT_TRUE( |
712 | cl::ParseCommandLineOptions(2, args0, StringRef(), &llvm::nulls())); |
713 | EXPECT_TRUE(RemoveOption); |
714 | |
715 | RemoveOption = false; |
716 | |
717 | cl::ResetAllOptionOccurrences(); |
718 | EXPECT_FALSE(RemoveOption); |
719 | EXPECT_TRUE( |
720 | cl::ParseCommandLineOptions(3, args1, StringRef(), &llvm::nulls())); |
721 | EXPECT_TRUE(RemoveOption); |
722 | |
723 | RemoveOption = false; |
724 | |
725 | cl::ResetAllOptionOccurrences(); |
726 | EXPECT_FALSE(RemoveOption); |
727 | EXPECT_TRUE( |
728 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
729 | EXPECT_TRUE(RemoveOption); |
730 | |
731 | RemoveOption.removeArgument(); |
732 | |
733 | // It should not work for any subcommands including the top-level. |
734 | cl::ResetAllOptionOccurrences(); |
735 | EXPECT_FALSE( |
736 | cl::ParseCommandLineOptions(2, args0, StringRef(), &llvm::nulls())); |
737 | cl::ResetAllOptionOccurrences(); |
738 | EXPECT_FALSE( |
739 | cl::ParseCommandLineOptions(3, args1, StringRef(), &llvm::nulls())); |
740 | cl::ResetAllOptionOccurrences(); |
741 | EXPECT_FALSE( |
742 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
743 | } |
744 | |
745 | TEST(CommandLineTest, GetRegisteredSubcommands) { |
746 | cl::ResetCommandLineParser(); |
747 | |
748 | StackSubCommand SC1("sc1" , "First Subcommand" ); |
749 | StackOption<bool> Opt1("opt1" , cl::sub(SC1), cl::init(Val: false)); |
750 | StackSubCommand SC2("sc2" , "Second subcommand" ); |
751 | StackOption<bool> Opt2("opt2" , cl::sub(SC2), cl::init(Val: false)); |
752 | |
753 | const char *args0[] = {"prog" , "sc1" }; |
754 | const char *args1[] = {"prog" , "sc2" }; |
755 | |
756 | EXPECT_TRUE( |
757 | cl::ParseCommandLineOptions(2, args0, StringRef(), &llvm::nulls())); |
758 | EXPECT_FALSE(Opt1); |
759 | EXPECT_FALSE(Opt2); |
760 | for (auto *S : cl::getRegisteredSubcommands()) { |
761 | if (*S) { |
762 | EXPECT_EQ("sc1" , S->getName()); |
763 | } |
764 | } |
765 | |
766 | cl::ResetAllOptionOccurrences(); |
767 | EXPECT_TRUE( |
768 | cl::ParseCommandLineOptions(2, args1, StringRef(), &llvm::nulls())); |
769 | EXPECT_FALSE(Opt1); |
770 | EXPECT_FALSE(Opt2); |
771 | for (auto *S : cl::getRegisteredSubcommands()) { |
772 | if (*S) { |
773 | EXPECT_EQ("sc2" , S->getName()); |
774 | } |
775 | } |
776 | } |
777 | |
778 | TEST(CommandLineTest, DefaultOptions) { |
779 | cl::ResetCommandLineParser(); |
780 | |
781 | StackOption<std::string> Bar("bar" , cl::sub(cl::SubCommand::getAll()), |
782 | cl::DefaultOption); |
783 | StackOption<std::string, cl::alias> Bar_Alias( |
784 | "b" , cl::desc("Alias for -bar" ), cl::aliasopt(Bar), cl::DefaultOption); |
785 | |
786 | StackOption<bool> Foo("foo" , cl::init(Val: false), |
787 | cl::sub(cl::SubCommand::getAll()), cl::DefaultOption); |
788 | StackOption<bool, cl::alias> Foo_Alias("f" , cl::desc("Alias for -foo" ), |
789 | cl::aliasopt(Foo), cl::DefaultOption); |
790 | |
791 | StackSubCommand SC1("sc1" , "First Subcommand" ); |
792 | // Override "-b" and change type in sc1 SubCommand. |
793 | StackOption<bool> SC1_B("b" , cl::sub(SC1), cl::init(Val: false)); |
794 | StackSubCommand SC2("sc2" , "Second subcommand" ); |
795 | // Override "-foo" and change type in sc2 SubCommand. Note that this does not |
796 | // affect "-f" alias, which continues to work correctly. |
797 | StackOption<std::string> SC2_Foo("foo" , cl::sub(SC2)); |
798 | |
799 | const char *args0[] = {"prog" , "-b" , "args0 bar string" , "-f" }; |
800 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args0), args0, |
801 | StringRef(), &llvm::nulls())); |
802 | EXPECT_EQ(Bar, "args0 bar string" ); |
803 | EXPECT_TRUE(Foo); |
804 | EXPECT_FALSE(SC1_B); |
805 | EXPECT_TRUE(SC2_Foo.empty()); |
806 | |
807 | cl::ResetAllOptionOccurrences(); |
808 | |
809 | const char *args1[] = {"prog" , "sc1" , "-b" , "-bar" , "args1 bar string" , "-f" }; |
810 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args1), args1, |
811 | StringRef(), &llvm::nulls())); |
812 | EXPECT_EQ(Bar, "args1 bar string" ); |
813 | EXPECT_TRUE(Foo); |
814 | EXPECT_TRUE(SC1_B); |
815 | EXPECT_TRUE(SC2_Foo.empty()); |
816 | for (auto *S : cl::getRegisteredSubcommands()) { |
817 | if (*S) { |
818 | EXPECT_EQ("sc1" , S->getName()); |
819 | } |
820 | } |
821 | |
822 | cl::ResetAllOptionOccurrences(); |
823 | |
824 | const char *args2[] = {"prog" , "sc2" , "-b" , "args2 bar string" , |
825 | "-f" , "-foo" , "foo string" }; |
826 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args2), args2, |
827 | StringRef(), &llvm::nulls())); |
828 | EXPECT_EQ(Bar, "args2 bar string" ); |
829 | EXPECT_TRUE(Foo); |
830 | EXPECT_FALSE(SC1_B); |
831 | EXPECT_EQ(SC2_Foo, "foo string" ); |
832 | for (auto *S : cl::getRegisteredSubcommands()) { |
833 | if (*S) { |
834 | EXPECT_EQ("sc2" , S->getName()); |
835 | } |
836 | } |
837 | cl::ResetCommandLineParser(); |
838 | } |
839 | |
840 | TEST(CommandLineTest, ArgumentLimit) { |
841 | std::string args(32 * 4096, 'a'); |
842 | EXPECT_FALSE(llvm::sys::commandLineFitsWithinSystemLimits("cl" , args.data())); |
843 | std::string args2(256, 'a'); |
844 | EXPECT_TRUE(llvm::sys::commandLineFitsWithinSystemLimits("cl" , args2.data())); |
845 | } |
846 | |
847 | TEST(CommandLineTest, ArgumentLimitWindows) { |
848 | if (!Triple(sys::getProcessTriple()).isOSWindows()) |
849 | GTEST_SKIP(); |
850 | // We use 32000 as a limit for command line length. Program name ('cl'), |
851 | // separating spaces and termination null character occupy 5 symbols. |
852 | std::string long_arg(32000 - 5, 'b'); |
853 | EXPECT_TRUE( |
854 | llvm::sys::commandLineFitsWithinSystemLimits("cl" , long_arg.data())); |
855 | long_arg += 'b'; |
856 | EXPECT_FALSE( |
857 | llvm::sys::commandLineFitsWithinSystemLimits("cl" , long_arg.data())); |
858 | } |
859 | |
860 | TEST(CommandLineTest, ResponseFileWindows) { |
861 | if (!Triple(sys::getProcessTriple()).isOSWindows()) |
862 | GTEST_SKIP(); |
863 | |
864 | StackOption<std::string, cl::list<std::string>> InputFilenames( |
865 | cl::Positional, cl::desc("<input files>" )); |
866 | StackOption<bool> TopLevelOpt("top-level" , cl::init(Val: false)); |
867 | |
868 | // Create response file. |
869 | TempFile ResponseFile("resp-" , ".txt" , |
870 | "-top-level\npath\\dir\\file1\npath/dir/file2" , |
871 | /*Unique*/ true); |
872 | |
873 | llvm::SmallString<128> RspOpt; |
874 | RspOpt.append(NumInputs: 1, Elt: '@'); |
875 | RspOpt.append(RHS: ResponseFile.path()); |
876 | const char *args[] = {"prog" , RspOpt.c_str()}; |
877 | EXPECT_FALSE(TopLevelOpt); |
878 | EXPECT_TRUE( |
879 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
880 | EXPECT_TRUE(TopLevelOpt); |
881 | EXPECT_EQ(InputFilenames[0], "path\\dir\\file1" ); |
882 | EXPECT_EQ(InputFilenames[1], "path/dir/file2" ); |
883 | } |
884 | |
885 | TEST(CommandLineTest, ResponseFiles) { |
886 | vfs::InMemoryFileSystem FS; |
887 | #ifdef _WIN32 |
888 | const char *TestRoot = "C:\\" ; |
889 | #else |
890 | const char *TestRoot = "/" ; |
891 | #endif |
892 | FS.setCurrentWorkingDirectory(TestRoot); |
893 | |
894 | // Create included response file of first level. |
895 | llvm::StringRef IncludedFileName = "resp1" ; |
896 | FS.addFile(Path: IncludedFileName, ModificationTime: 0, |
897 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "-option_1 -option_2\n" |
898 | "@incdir/resp2\n" |
899 | "-option_3=abcd\n" |
900 | "@incdir/resp3\n" |
901 | "-option_4=efjk\n" )); |
902 | |
903 | // Directory for included file. |
904 | llvm::StringRef IncDir = "incdir" ; |
905 | |
906 | // Create included response file of second level. |
907 | llvm::SmallString<128> IncludedFileName2; |
908 | llvm::sys::path::append(path&: IncludedFileName2, a: IncDir, b: "resp2" ); |
909 | FS.addFile(Path: IncludedFileName2, ModificationTime: 0, |
910 | Buffer: MemoryBuffer::getMemBuffer(InputData: "-option_21 -option_22\n" |
911 | "-option_23=abcd\n" )); |
912 | |
913 | // Create second included response file of second level. |
914 | llvm::SmallString<128> IncludedFileName3; |
915 | llvm::sys::path::append(path&: IncludedFileName3, a: IncDir, b: "resp3" ); |
916 | FS.addFile(Path: IncludedFileName3, ModificationTime: 0, |
917 | Buffer: MemoryBuffer::getMemBuffer(InputData: "-option_31 -option_32\n" |
918 | "-option_33=abcd\n" )); |
919 | |
920 | // Prepare 'file' with reference to response file. |
921 | SmallString<128> IncRef; |
922 | IncRef.append(NumInputs: 1, Elt: '@'); |
923 | IncRef.append(RHS: IncludedFileName); |
924 | llvm::SmallVector<const char *, 4> Argv = {"test/test" , "-flag_1" , |
925 | IncRef.c_str(), "-flag_2" }; |
926 | |
927 | // Expand response files. |
928 | llvm::BumpPtrAllocator A; |
929 | llvm::cl::ExpansionContext ECtx(A, llvm::cl::TokenizeGNUCommandLine); |
930 | ECtx.setVFS(&FS).setCurrentDir(TestRoot).setRelativeNames(true); |
931 | ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv)); |
932 | EXPECT_THAT(Argv, testing::Pointwise( |
933 | StringEquality(), |
934 | {"test/test" , "-flag_1" , "-option_1" , "-option_2" , |
935 | "-option_21" , "-option_22" , "-option_23=abcd" , |
936 | "-option_3=abcd" , "-option_31" , "-option_32" , |
937 | "-option_33=abcd" , "-option_4=efjk" , "-flag_2" })); |
938 | } |
939 | |
940 | TEST(CommandLineTest, RecursiveResponseFiles) { |
941 | vfs::InMemoryFileSystem FS; |
942 | #ifdef _WIN32 |
943 | const char *TestRoot = "C:\\" ; |
944 | #else |
945 | const char *TestRoot = "/" ; |
946 | #endif |
947 | FS.setCurrentWorkingDirectory(TestRoot); |
948 | |
949 | StringRef SelfFilePath = "self.rsp" ; |
950 | std::string SelfFileRef = ("@" + SelfFilePath).str(); |
951 | |
952 | StringRef NestedFilePath = "nested.rsp" ; |
953 | std::string NestedFileRef = ("@" + NestedFilePath).str(); |
954 | |
955 | StringRef FlagFilePath = "flag.rsp" ; |
956 | std::string FlagFileRef = ("@" + FlagFilePath).str(); |
957 | |
958 | std::string SelfFileContents; |
959 | raw_string_ostream SelfFile(SelfFileContents); |
960 | SelfFile << "-option_1\n" ; |
961 | SelfFile << FlagFileRef << "\n" ; |
962 | SelfFile << NestedFileRef << "\n" ; |
963 | SelfFile << SelfFileRef << "\n" ; |
964 | FS.addFile(Path: SelfFilePath, ModificationTime: 0, Buffer: MemoryBuffer::getMemBuffer(InputData: SelfFile.str())); |
965 | |
966 | std::string NestedFileContents; |
967 | raw_string_ostream NestedFile(NestedFileContents); |
968 | NestedFile << "-option_2\n" ; |
969 | NestedFile << FlagFileRef << "\n" ; |
970 | NestedFile << SelfFileRef << "\n" ; |
971 | NestedFile << NestedFileRef << "\n" ; |
972 | FS.addFile(Path: NestedFilePath, ModificationTime: 0, Buffer: MemoryBuffer::getMemBuffer(InputData: NestedFile.str())); |
973 | |
974 | std::string FlagFileContents; |
975 | raw_string_ostream FlagFile(FlagFileContents); |
976 | FlagFile << "-option_x\n" ; |
977 | FS.addFile(Path: FlagFilePath, ModificationTime: 0, Buffer: MemoryBuffer::getMemBuffer(InputData: FlagFile.str())); |
978 | |
979 | // Ensure: |
980 | // Recursive expansion terminates |
981 | // Recursive files never expand |
982 | // Non-recursive repeats are allowed |
983 | SmallVector<const char *, 4> Argv = {"test/test" , SelfFileRef.c_str(), |
984 | "-option_3" }; |
985 | BumpPtrAllocator A; |
986 | #ifdef _WIN32 |
987 | cl::TokenizerCallback Tokenizer = cl::TokenizeWindowsCommandLine; |
988 | #else |
989 | cl::TokenizerCallback Tokenizer = cl::TokenizeGNUCommandLine; |
990 | #endif |
991 | llvm::cl::ExpansionContext ECtx(A, Tokenizer); |
992 | ECtx.setVFS(&FS).setCurrentDir(TestRoot); |
993 | llvm::Error Err = ECtx.expandResponseFiles(Argv); |
994 | ASSERT_TRUE((bool)Err); |
995 | SmallString<128> FilePath = SelfFilePath; |
996 | std::error_code EC = FS.makeAbsolute(Path&: FilePath); |
997 | ASSERT_FALSE((bool)EC); |
998 | std::string ExpectedMessage = |
999 | std::string("recursive expansion of: '" ) + std::string(FilePath) + "'" ; |
1000 | ASSERT_TRUE(toString(std::move(Err)) == ExpectedMessage); |
1001 | |
1002 | EXPECT_THAT(Argv, |
1003 | testing::Pointwise(StringEquality(), |
1004 | {"test/test" , "-option_1" , "-option_x" , |
1005 | "-option_2" , "-option_x" , SelfFileRef.c_str(), |
1006 | NestedFileRef.c_str(), SelfFileRef.c_str(), |
1007 | "-option_3" })); |
1008 | } |
1009 | |
1010 | TEST(CommandLineTest, ResponseFilesAtArguments) { |
1011 | vfs::InMemoryFileSystem FS; |
1012 | #ifdef _WIN32 |
1013 | const char *TestRoot = "C:\\" ; |
1014 | #else |
1015 | const char *TestRoot = "/" ; |
1016 | #endif |
1017 | FS.setCurrentWorkingDirectory(TestRoot); |
1018 | |
1019 | StringRef ResponseFilePath = "test.rsp" ; |
1020 | |
1021 | std::string ResponseFileContents; |
1022 | raw_string_ostream ResponseFile(ResponseFileContents); |
1023 | ResponseFile << "-foo" << "\n" ; |
1024 | ResponseFile << "-bar" << "\n" ; |
1025 | FS.addFile(Path: ResponseFilePath, ModificationTime: 0, |
1026 | Buffer: MemoryBuffer::getMemBuffer(InputData: ResponseFile.str())); |
1027 | |
1028 | // Ensure we expand rsp files after lots of non-rsp arguments starting with @. |
1029 | constexpr size_t NON_RSP_AT_ARGS = 64; |
1030 | SmallVector<const char *, 4> Argv = {"test/test" }; |
1031 | Argv.append(NumInputs: NON_RSP_AT_ARGS, Elt: "@non_rsp_at_arg" ); |
1032 | std::string ResponseFileRef = ("@" + ResponseFilePath).str(); |
1033 | Argv.push_back(Elt: ResponseFileRef.c_str()); |
1034 | |
1035 | BumpPtrAllocator A; |
1036 | llvm::cl::ExpansionContext ECtx(A, cl::TokenizeGNUCommandLine); |
1037 | ECtx.setVFS(&FS).setCurrentDir(TestRoot); |
1038 | ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv)); |
1039 | |
1040 | // ASSERT instead of EXPECT to prevent potential out-of-bounds access. |
1041 | ASSERT_EQ(Argv.size(), 1 + NON_RSP_AT_ARGS + 2); |
1042 | size_t i = 0; |
1043 | EXPECT_STREQ(Argv[i++], "test/test" ); |
1044 | for (; i < 1 + NON_RSP_AT_ARGS; ++i) |
1045 | EXPECT_STREQ(Argv[i], "@non_rsp_at_arg" ); |
1046 | EXPECT_STREQ(Argv[i++], "-foo" ); |
1047 | EXPECT_STREQ(Argv[i++], "-bar" ); |
1048 | } |
1049 | |
1050 | TEST(CommandLineTest, ResponseFileRelativePath) { |
1051 | vfs::InMemoryFileSystem FS; |
1052 | #ifdef _WIN32 |
1053 | const char *TestRoot = "C:\\" ; |
1054 | #else |
1055 | const char *TestRoot = "//net" ; |
1056 | #endif |
1057 | FS.setCurrentWorkingDirectory(TestRoot); |
1058 | |
1059 | StringRef OuterFile = "dir/outer.rsp" ; |
1060 | StringRef OuterFileContents = "@inner.rsp" ; |
1061 | FS.addFile(Path: OuterFile, ModificationTime: 0, Buffer: MemoryBuffer::getMemBuffer(InputData: OuterFileContents)); |
1062 | |
1063 | StringRef InnerFile = "dir/inner.rsp" ; |
1064 | StringRef InnerFileContents = "-flag" ; |
1065 | FS.addFile(Path: InnerFile, ModificationTime: 0, Buffer: MemoryBuffer::getMemBuffer(InputData: InnerFileContents)); |
1066 | |
1067 | SmallVector<const char *, 2> Argv = {"test/test" , "@dir/outer.rsp" }; |
1068 | |
1069 | BumpPtrAllocator A; |
1070 | llvm::cl::ExpansionContext ECtx(A, cl::TokenizeGNUCommandLine); |
1071 | ECtx.setVFS(&FS).setCurrentDir(TestRoot).setRelativeNames(true); |
1072 | ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv)); |
1073 | EXPECT_THAT(Argv, |
1074 | testing::Pointwise(StringEquality(), {"test/test" , "-flag" })); |
1075 | } |
1076 | |
1077 | TEST(CommandLineTest, ResponseFileEOLs) { |
1078 | vfs::InMemoryFileSystem FS; |
1079 | #ifdef _WIN32 |
1080 | const char *TestRoot = "C:\\" ; |
1081 | #else |
1082 | const char *TestRoot = "//net" ; |
1083 | #endif |
1084 | FS.setCurrentWorkingDirectory(TestRoot); |
1085 | FS.addFile(Path: "eols.rsp" , ModificationTime: 0, |
1086 | Buffer: MemoryBuffer::getMemBuffer(InputData: "-Xclang -Wno-whatever\n input.cpp" )); |
1087 | SmallVector<const char *, 2> Argv = {"clang" , "@eols.rsp" }; |
1088 | BumpPtrAllocator A; |
1089 | llvm::cl::ExpansionContext ECtx(A, cl::TokenizeWindowsCommandLine); |
1090 | ECtx.setVFS(&FS).setCurrentDir(TestRoot).setMarkEOLs(true).setRelativeNames( |
1091 | true); |
1092 | ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv)); |
1093 | const char *Expected[] = {"clang" , "-Xclang" , "-Wno-whatever" , nullptr, |
1094 | "input.cpp" }; |
1095 | ASSERT_EQ(std::size(Expected), Argv.size()); |
1096 | for (size_t I = 0, E = std::size(Expected); I < E; ++I) { |
1097 | if (Expected[I] == nullptr) { |
1098 | ASSERT_EQ(Argv[I], nullptr); |
1099 | } else { |
1100 | ASSERT_STREQ(Expected[I], Argv[I]); |
1101 | } |
1102 | } |
1103 | } |
1104 | |
1105 | TEST(CommandLineTest, BadResponseFile) { |
1106 | BumpPtrAllocator A; |
1107 | StringSaver Saver(A); |
1108 | TempDir ADir("dir" , /*Unique*/ true); |
1109 | SmallString<128> AFilePath = ADir.path(); |
1110 | llvm::sys::path::append(path&: AFilePath, a: "file.rsp" ); |
1111 | std::string AFileExp = std::string("@" ) + std::string(AFilePath.str()); |
1112 | SmallVector<const char *, 2> Argv = {"clang" , AFileExp.c_str()}; |
1113 | |
1114 | bool Res = cl::ExpandResponseFiles(Saver, Tokenizer: cl::TokenizeGNUCommandLine, Argv); |
1115 | ASSERT_TRUE(Res); |
1116 | ASSERT_EQ(2U, Argv.size()); |
1117 | ASSERT_STREQ(Argv[0], "clang" ); |
1118 | ASSERT_STREQ(Argv[1], AFileExp.c_str()); |
1119 | |
1120 | std::string ADirExp = std::string("@" ) + std::string(ADir.path()); |
1121 | Argv = {"clang" , ADirExp.c_str()}; |
1122 | Res = cl::ExpandResponseFiles(Saver, Tokenizer: cl::TokenizeGNUCommandLine, Argv); |
1123 | ASSERT_FALSE(Res); |
1124 | ASSERT_EQ(2U, Argv.size()); |
1125 | ASSERT_STREQ(Argv[0], "clang" ); |
1126 | ASSERT_STREQ(Argv[1], ADirExp.c_str()); |
1127 | } |
1128 | |
1129 | TEST(CommandLineTest, SetDefaultValue) { |
1130 | cl::ResetCommandLineParser(); |
1131 | |
1132 | StackOption<std::string> Opt1("opt1" , cl::init(Val: "true" )); |
1133 | StackOption<bool> Opt2("opt2" , cl::init(Val: true)); |
1134 | cl::alias Alias("alias" , llvm::cl::aliasopt(Opt2)); |
1135 | StackOption<int> Opt3("opt3" , cl::init(Val: 3)); |
1136 | |
1137 | llvm::SmallVector<int, 3> IntVals = {1, 2, 3}; |
1138 | llvm::SmallVector<std::string, 3> StrVals = {"foo" , "bar" , "baz" }; |
1139 | |
1140 | StackOption<int, cl::list<int>> List1( |
1141 | "list1" , cl::list_init<int>(Vals: llvm::ArrayRef<int>(IntVals)), |
1142 | cl::CommaSeparated); |
1143 | StackOption<std::string, cl::list<std::string>> List2( |
1144 | "list2" , cl::list_init<std::string>(Vals: llvm::ArrayRef<std::string>(StrVals)), |
1145 | cl::CommaSeparated); |
1146 | cl::alias ListAlias("list-alias" , llvm::cl::aliasopt(List2)); |
1147 | |
1148 | const char *args[] = {"prog" , "-opt1=false" , "-list1" , "4" , |
1149 | "-list1" , "5,6" , "-opt2" , "-opt3" }; |
1150 | |
1151 | EXPECT_TRUE( |
1152 | cl::ParseCommandLineOptions(7, args, StringRef(), &llvm::nulls())); |
1153 | |
1154 | EXPECT_EQ(Opt1, "false" ); |
1155 | EXPECT_TRUE(Opt2); |
1156 | EXPECT_EQ(Opt3, 3); |
1157 | |
1158 | for (size_t I = 0, E = IntVals.size(); I < E; ++I) { |
1159 | EXPECT_EQ(IntVals[I] + 3, List1[I]); |
1160 | EXPECT_EQ(StrVals[I], List2[I]); |
1161 | } |
1162 | |
1163 | Opt2 = false; |
1164 | Opt3 = 1; |
1165 | |
1166 | cl::ResetAllOptionOccurrences(); |
1167 | |
1168 | for (auto &OM : cl::getRegisteredOptions(Sub&: cl::SubCommand::getTopLevel())) { |
1169 | cl::Option *O = OM.second; |
1170 | if (O->ArgStr == "opt2" ) { |
1171 | continue; |
1172 | } |
1173 | O->setDefault(); |
1174 | } |
1175 | |
1176 | EXPECT_EQ(Opt1, "true" ); |
1177 | EXPECT_TRUE(Opt2); |
1178 | EXPECT_EQ(Opt3, 3); |
1179 | for (size_t I = 0, E = IntVals.size(); I < E; ++I) { |
1180 | EXPECT_EQ(IntVals[I], List1[I]); |
1181 | EXPECT_EQ(StrVals[I], List2[I]); |
1182 | } |
1183 | |
1184 | Alias.removeArgument(); |
1185 | ListAlias.removeArgument(); |
1186 | } |
1187 | |
1188 | TEST(CommandLineTest, ReadConfigFile) { |
1189 | llvm::SmallVector<const char *, 1> Argv; |
1190 | |
1191 | TempDir TestDir("unittest" , /*Unique*/ true); |
1192 | TempDir TestSubDir(TestDir.path(component: "subdir" ), /*Unique*/ false); |
1193 | |
1194 | llvm::SmallString<128> TestCfg = TestDir.path(component: "foo" ); |
1195 | TempFile ConfigFile(TestCfg, "" , |
1196 | "# Comment\n" |
1197 | "-option_1\n" |
1198 | "-option_2=<CFGDIR>/dir1\n" |
1199 | "-option_3=<CFGDIR>\n" |
1200 | "-option_4 <CFGDIR>\n" |
1201 | "-option_5=<CFG\\\n" |
1202 | "DIR>\n" |
1203 | "-option_6=<CFGDIR>/dir1,<CFGDIR>/dir2\n" |
1204 | "@subconfig\n" |
1205 | "-option_11=abcd\n" |
1206 | "-option_12=\\\n" |
1207 | "cdef\n" ); |
1208 | |
1209 | llvm::SmallString<128> TestCfg2 = TestDir.path(component: "subconfig" ); |
1210 | TempFile ConfigFile2(TestCfg2, "" , |
1211 | "-option_7\n" |
1212 | "-option_8=<CFGDIR>/dir2\n" |
1213 | "@subdir/subfoo\n" |
1214 | "\n" |
1215 | " # comment\n" ); |
1216 | |
1217 | llvm::SmallString<128> TestCfg3 = TestSubDir.path(component: "subfoo" ); |
1218 | TempFile ConfigFile3(TestCfg3, "" , |
1219 | "-option_9=<CFGDIR>/dir3\n" |
1220 | "@<CFGDIR>/subfoo2\n" ); |
1221 | |
1222 | llvm::SmallString<128> TestCfg4 = TestSubDir.path(component: "subfoo2" ); |
1223 | TempFile ConfigFile4(TestCfg4, "" , "-option_10\n" ); |
1224 | |
1225 | // Make sure the current directory is not the directory where config files |
1226 | // resides. In this case the code that expands response files will not find |
1227 | // 'subconfig' unless it resolves nested inclusions relative to the including |
1228 | // file. |
1229 | llvm::SmallString<128> CurrDir; |
1230 | std::error_code EC = llvm::sys::fs::current_path(result&: CurrDir); |
1231 | EXPECT_TRUE(!EC); |
1232 | EXPECT_NE(CurrDir.str(), TestDir.path()); |
1233 | |
1234 | llvm::BumpPtrAllocator A; |
1235 | llvm::cl::ExpansionContext ECtx(A, cl::tokenizeConfigFile); |
1236 | llvm::Error Result = ECtx.readConfigFile(CfgFile: ConfigFile.path(), Argv); |
1237 | |
1238 | EXPECT_FALSE((bool)Result); |
1239 | EXPECT_EQ(Argv.size(), 13U); |
1240 | EXPECT_STREQ(Argv[0], "-option_1" ); |
1241 | EXPECT_STREQ(Argv[1], |
1242 | ("-option_2=" + TestDir.path() + "/dir1" ).str().c_str()); |
1243 | EXPECT_STREQ(Argv[2], ("-option_3=" + TestDir.path()).str().c_str()); |
1244 | EXPECT_STREQ(Argv[3], "-option_4" ); |
1245 | EXPECT_STREQ(Argv[4], TestDir.path().str().c_str()); |
1246 | EXPECT_STREQ(Argv[5], ("-option_5=" + TestDir.path()).str().c_str()); |
1247 | EXPECT_STREQ(Argv[6], ("-option_6=" + TestDir.path() + "/dir1," + |
1248 | TestDir.path() + "/dir2" ) |
1249 | .str() |
1250 | .c_str()); |
1251 | EXPECT_STREQ(Argv[7], "-option_7" ); |
1252 | EXPECT_STREQ(Argv[8], |
1253 | ("-option_8=" + TestDir.path() + "/dir2" ).str().c_str()); |
1254 | EXPECT_STREQ(Argv[9], |
1255 | ("-option_9=" + TestSubDir.path() + "/dir3" ).str().c_str()); |
1256 | EXPECT_STREQ(Argv[10], "-option_10" ); |
1257 | EXPECT_STREQ(Argv[11], "-option_11=abcd" ); |
1258 | EXPECT_STREQ(Argv[12], "-option_12=cdef" ); |
1259 | } |
1260 | |
1261 | TEST(CommandLineTest, PositionalEatArgsError) { |
1262 | cl::ResetCommandLineParser(); |
1263 | |
1264 | StackOption<std::string, cl::list<std::string>> PosEatArgs( |
1265 | "positional-eat-args" , cl::Positional, cl::desc("<arguments>..." ), |
1266 | cl::PositionalEatsArgs); |
1267 | StackOption<std::string, cl::list<std::string>> PosEatArgs2( |
1268 | "positional-eat-args2" , cl::Positional, cl::desc("Some strings" ), |
1269 | cl::PositionalEatsArgs); |
1270 | |
1271 | const char *args[] = {"prog" , "-positional-eat-args=XXXX" }; |
1272 | const char *args2[] = {"prog" , "-positional-eat-args=XXXX" , "-foo" }; |
1273 | const char *args3[] = {"prog" , "-positional-eat-args" , "-foo" }; |
1274 | const char *args4[] = {"prog" , "-positional-eat-args" , |
1275 | "-foo" , "-positional-eat-args2" , |
1276 | "-bar" , "foo" }; |
1277 | |
1278 | std::string Errs; |
1279 | raw_string_ostream OS(Errs); |
1280 | EXPECT_FALSE(cl::ParseCommandLineOptions(2, args, StringRef(), &OS)); OS.flush(); |
1281 | EXPECT_FALSE(Errs.empty()); Errs.clear(); |
1282 | EXPECT_FALSE(cl::ParseCommandLineOptions(3, args2, StringRef(), &OS)); OS.flush(); |
1283 | EXPECT_FALSE(Errs.empty()); Errs.clear(); |
1284 | EXPECT_TRUE(cl::ParseCommandLineOptions(3, args3, StringRef(), &OS)); OS.flush(); |
1285 | EXPECT_TRUE(Errs.empty()); Errs.clear(); |
1286 | |
1287 | cl::ResetAllOptionOccurrences(); |
1288 | EXPECT_TRUE(cl::ParseCommandLineOptions(6, args4, StringRef(), &OS)); OS.flush(); |
1289 | EXPECT_EQ(PosEatArgs.size(), 1u); |
1290 | EXPECT_EQ(PosEatArgs2.size(), 2u); |
1291 | EXPECT_TRUE(Errs.empty()); |
1292 | } |
1293 | |
1294 | #ifdef _WIN32 |
1295 | void checkSeparators(StringRef Path) { |
1296 | char UndesiredSeparator = sys::path::get_separator()[0] == '/' ? '\\' : '/'; |
1297 | ASSERT_EQ(Path.find(UndesiredSeparator), StringRef::npos); |
1298 | } |
1299 | |
1300 | TEST(CommandLineTest, GetCommandLineArguments) { |
1301 | int argc = __argc; |
1302 | char **argv = __argv; |
1303 | |
1304 | // GetCommandLineArguments is called in InitLLVM. |
1305 | llvm::InitLLVM X(argc, argv); |
1306 | |
1307 | EXPECT_EQ(llvm::sys::path::is_absolute(argv[0]), |
1308 | llvm::sys::path::is_absolute(__argv[0])); |
1309 | checkSeparators(argv[0]); |
1310 | |
1311 | EXPECT_TRUE( |
1312 | llvm::sys::path::filename(argv[0]).equals_insensitive("supporttests.exe" )) |
1313 | << "Filename of test executable is " |
1314 | << llvm::sys::path::filename(argv[0]); |
1315 | } |
1316 | #endif |
1317 | |
1318 | class OutputRedirector { |
1319 | public: |
1320 | OutputRedirector(int RedirectFD) |
1321 | : RedirectFD(RedirectFD), OldFD(dup(fd: RedirectFD)) { |
1322 | if (OldFD == -1 || |
1323 | sys::fs::createTemporaryFile(Prefix: "unittest-redirect" , Suffix: "" , ResultFD&: NewFD, |
1324 | ResultPath&: FilePath) || |
1325 | dup2(fd: NewFD, fd2: RedirectFD) == -1) |
1326 | Valid = false; |
1327 | } |
1328 | |
1329 | ~OutputRedirector() { |
1330 | dup2(fd: OldFD, fd2: RedirectFD); |
1331 | close(fd: OldFD); |
1332 | close(fd: NewFD); |
1333 | } |
1334 | |
1335 | SmallVector<char, 128> FilePath; |
1336 | bool Valid = true; |
1337 | |
1338 | private: |
1339 | int RedirectFD; |
1340 | int OldFD; |
1341 | int NewFD; |
1342 | }; |
1343 | |
1344 | struct AutoDeleteFile { |
1345 | SmallVector<char, 128> FilePath; |
1346 | ~AutoDeleteFile() { |
1347 | if (!FilePath.empty()) |
1348 | sys::fs::remove(path: std::string(FilePath.data(), FilePath.size())); |
1349 | } |
1350 | }; |
1351 | |
1352 | static std::string interceptStdout(std::function<void()> F) { |
1353 | outs().flush(); // flush any output from previous tests |
1354 | AutoDeleteFile File; |
1355 | { |
1356 | OutputRedirector Stdout(fileno(stdout)); |
1357 | if (!Stdout.Valid) |
1358 | return "" ; |
1359 | File.FilePath = Stdout.FilePath; |
1360 | F(); |
1361 | outs().flush(); |
1362 | } |
1363 | auto Buffer = MemoryBuffer::getFile(Filename: File.FilePath); |
1364 | if (!Buffer) |
1365 | return "" ; |
1366 | return Buffer->get()->getBuffer().str(); |
1367 | } |
1368 | |
1369 | template <void (*Func)(const cl::Option &)> |
1370 | class PrintOptionTestBase : public ::testing::Test { |
1371 | public: |
1372 | // Return std::string because the output of a failing EXPECT check is |
1373 | // unreadable for StringRef. It also avoids any lifetime issues. |
1374 | template <typename... Ts> std::string runTest(Ts... OptionAttributes) { |
1375 | StackOption<OptionValue> TestOption(Opt, cl::desc(HelpText), |
1376 | OptionAttributes...); |
1377 | return interceptStdout([&]() { Func(TestOption); }); |
1378 | } |
1379 | |
1380 | enum class OptionValue { Val }; |
1381 | const StringRef Opt = "some-option" ; |
1382 | const StringRef HelpText = "some help" ; |
1383 | }; |
1384 | |
1385 | // This is a workaround for cl::Option sub-classes having their |
1386 | // printOptionInfo functions private. |
1387 | void printOptionInfo(const cl::Option &O) { |
1388 | O.printOptionInfo(/*GlobalWidth=*/26); |
1389 | } |
1390 | |
1391 | using PrintOptionInfoTest = PrintOptionTestBase<printOptionInfo>; |
1392 | |
1393 | TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithoutSentinel) { |
1394 | std::string Output = |
1395 | runTest(OptionAttributes: cl::ValueOptional, |
1396 | OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ))); |
1397 | |
1398 | // clang-format off |
1399 | EXPECT_EQ(Output, (" --" + Opt + "=<value> - " + HelpText + "\n" |
1400 | " =v1 - desc1\n" ) |
1401 | .str()); |
1402 | // clang-format on |
1403 | } |
1404 | |
1405 | TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinel) { |
1406 | std::string Output = runTest( |
1407 | OptionAttributes: cl::ValueOptional, OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ), |
1408 | clEnumValN(OptionValue::Val, "" , "" ))); |
1409 | |
1410 | // clang-format off |
1411 | EXPECT_EQ(Output, |
1412 | (" --" + Opt + " - " + HelpText + "\n" |
1413 | " --" + Opt + "=<value> - " + HelpText + "\n" |
1414 | " =v1 - desc1\n" ) |
1415 | .str()); |
1416 | // clang-format on |
1417 | } |
1418 | |
1419 | TEST_F(PrintOptionInfoTest, PrintOptionInfoValueOptionalWithSentinelWithHelp) { |
1420 | std::string Output = runTest( |
1421 | OptionAttributes: cl::ValueOptional, OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ), |
1422 | clEnumValN(OptionValue::Val, "" , "desc2" ))); |
1423 | |
1424 | // clang-format off |
1425 | EXPECT_EQ(Output, (" --" + Opt + " - " + HelpText + "\n" |
1426 | " --" + Opt + "=<value> - " + HelpText + "\n" |
1427 | " =v1 - desc1\n" |
1428 | " =<empty> - desc2\n" ) |
1429 | .str()); |
1430 | // clang-format on |
1431 | } |
1432 | |
1433 | TEST_F(PrintOptionInfoTest, PrintOptionInfoValueRequiredWithEmptyValueName) { |
1434 | std::string Output = runTest( |
1435 | OptionAttributes: cl::ValueRequired, OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ), |
1436 | clEnumValN(OptionValue::Val, "" , "" ))); |
1437 | |
1438 | // clang-format off |
1439 | EXPECT_EQ(Output, (" --" + Opt + "=<value> - " + HelpText + "\n" |
1440 | " =v1 - desc1\n" |
1441 | " =<empty>\n" ) |
1442 | .str()); |
1443 | // clang-format on |
1444 | } |
1445 | |
1446 | TEST_F(PrintOptionInfoTest, PrintOptionInfoEmptyValueDescription) { |
1447 | std::string Output = runTest( |
1448 | OptionAttributes: cl::ValueRequired, OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "" ))); |
1449 | |
1450 | // clang-format off |
1451 | EXPECT_EQ(Output, |
1452 | (" --" + Opt + "=<value> - " + HelpText + "\n" |
1453 | " =v1\n" ).str()); |
1454 | // clang-format on |
1455 | } |
1456 | |
1457 | TEST_F(PrintOptionInfoTest, PrintOptionInfoMultilineValueDescription) { |
1458 | std::string Output = |
1459 | runTest(OptionAttributes: cl::ValueRequired, |
1460 | OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , |
1461 | "This is the first enum value\n" |
1462 | "which has a really long description\n" |
1463 | "thus it is multi-line." ), |
1464 | clEnumValN(OptionValue::Val, "" , |
1465 | "This is an unnamed enum value\n" |
1466 | "Should be indented as well" ))); |
1467 | |
1468 | // clang-format off |
1469 | EXPECT_EQ(Output, |
1470 | (" --" + Opt + "=<value> - " + HelpText + "\n" |
1471 | " =v1 - This is the first enum value\n" |
1472 | " which has a really long description\n" |
1473 | " thus it is multi-line.\n" |
1474 | " =<empty> - This is an unnamed enum value\n" |
1475 | " Should be indented as well\n" ).str()); |
1476 | // clang-format on |
1477 | } |
1478 | |
1479 | void printOptionValue(const cl::Option &O) { |
1480 | O.printOptionValue(/*GlobalWidth=*/12, /*Force=*/true); |
1481 | } |
1482 | |
1483 | using PrintOptionValueTest = PrintOptionTestBase<printOptionValue>; |
1484 | |
1485 | TEST_F(PrintOptionValueTest, PrintOptionDefaultValue) { |
1486 | std::string Output = |
1487 | runTest(OptionAttributes: cl::init(Val: OptionValue::Val), |
1488 | OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ))); |
1489 | |
1490 | EXPECT_EQ(Output, (" --" + Opt + " = v1 (default: v1)\n" ).str()); |
1491 | } |
1492 | |
1493 | TEST_F(PrintOptionValueTest, PrintOptionNoDefaultValue) { |
1494 | std::string Output = |
1495 | runTest(OptionAttributes: cl::values(clEnumValN(OptionValue::Val, "v1" , "desc1" ))); |
1496 | |
1497 | // Note: the option still has a (zero-initialized) value, but the default |
1498 | // is invalid and doesn't match any value. |
1499 | EXPECT_EQ(Output, (" --" + Opt + " = v1 (default: )\n" ).str()); |
1500 | } |
1501 | |
1502 | TEST_F(PrintOptionValueTest, PrintOptionUnknownValue) { |
1503 | std::string Output = runTest(OptionAttributes: cl::init(Val: OptionValue::Val)); |
1504 | |
1505 | EXPECT_EQ(Output, (" --" + Opt + " = *unknown option value*\n" ).str()); |
1506 | } |
1507 | |
1508 | class GetOptionWidthTest : public ::testing::Test { |
1509 | public: |
1510 | enum class OptionValue { Val }; |
1511 | |
1512 | template <typename... Ts> |
1513 | size_t runTest(StringRef ArgName, Ts... OptionAttributes) { |
1514 | StackOption<OptionValue> TestOption(ArgName, cl::desc("some help" ), |
1515 | OptionAttributes...); |
1516 | return getOptionWidth(O: TestOption); |
1517 | } |
1518 | |
1519 | private: |
1520 | // This is a workaround for cl::Option sub-classes having their |
1521 | // printOptionInfo |
1522 | // functions private. |
1523 | size_t getOptionWidth(const cl::Option &O) { return O.getOptionWidth(); } |
1524 | }; |
1525 | |
1526 | TEST_F(GetOptionWidthTest, GetOptionWidthArgNameLonger) { |
1527 | StringRef ArgName("a-long-argument-name" ); |
1528 | size_t ExpectedStrSize = (" --" + ArgName + "=<value> - " ).str().size(); |
1529 | EXPECT_EQ( |
1530 | runTest(ArgName, cl::values(clEnumValN(OptionValue::Val, "v" , "help" ))), |
1531 | ExpectedStrSize); |
1532 | } |
1533 | |
1534 | TEST_F(GetOptionWidthTest, GetOptionWidthFirstOptionNameLonger) { |
1535 | StringRef OptName("a-long-option-name" ); |
1536 | size_t ExpectedStrSize = (" =" + OptName + " - " ).str().size(); |
1537 | EXPECT_EQ( |
1538 | runTest("a" , cl::values(clEnumValN(OptionValue::Val, OptName, "help" ), |
1539 | clEnumValN(OptionValue::Val, "b" , "help" ))), |
1540 | ExpectedStrSize); |
1541 | } |
1542 | |
1543 | TEST_F(GetOptionWidthTest, GetOptionWidthSecondOptionNameLonger) { |
1544 | StringRef OptName("a-long-option-name" ); |
1545 | size_t ExpectedStrSize = (" =" + OptName + " - " ).str().size(); |
1546 | EXPECT_EQ( |
1547 | runTest("a" , cl::values(clEnumValN(OptionValue::Val, "b" , "help" ), |
1548 | clEnumValN(OptionValue::Val, OptName, "help" ))), |
1549 | ExpectedStrSize); |
1550 | } |
1551 | |
1552 | TEST_F(GetOptionWidthTest, GetOptionWidthEmptyOptionNameLonger) { |
1553 | size_t ExpectedStrSize = StringRef(" =<empty> - " ).size(); |
1554 | // The length of a=<value> (including indentation) is actually the same as the |
1555 | // =<empty> string, so it is impossible to distinguish via testing the case |
1556 | // where the empty string is picked from where the option name is picked. |
1557 | EXPECT_EQ(runTest("a" , cl::values(clEnumValN(OptionValue::Val, "b" , "help" ), |
1558 | clEnumValN(OptionValue::Val, "" , "help" ))), |
1559 | ExpectedStrSize); |
1560 | } |
1561 | |
1562 | TEST_F(GetOptionWidthTest, |
1563 | GetOptionWidthValueOptionalEmptyOptionWithNoDescription) { |
1564 | StringRef ArgName("a" ); |
1565 | // The length of a=<value> (including indentation) is actually the same as the |
1566 | // =<empty> string, so it is impossible to distinguish via testing the case |
1567 | // where the empty string is ignored from where it is not ignored. |
1568 | // The dash will not actually be printed, but the space it would take up is |
1569 | // included to ensure a consistent column width. |
1570 | size_t ExpectedStrSize = (" -" + ArgName + "=<value> - " ).str().size(); |
1571 | EXPECT_EQ(runTest(ArgName, cl::ValueOptional, |
1572 | cl::values(clEnumValN(OptionValue::Val, "value" , "help" ), |
1573 | clEnumValN(OptionValue::Val, "" , "" ))), |
1574 | ExpectedStrSize); |
1575 | } |
1576 | |
1577 | TEST_F(GetOptionWidthTest, |
1578 | GetOptionWidthValueRequiredEmptyOptionWithNoDescription) { |
1579 | // The length of a=<value> (including indentation) is actually the same as the |
1580 | // =<empty> string, so it is impossible to distinguish via testing the case |
1581 | // where the empty string is picked from where the option name is picked |
1582 | size_t ExpectedStrSize = StringRef(" =<empty> - " ).size(); |
1583 | EXPECT_EQ(runTest("a" , cl::ValueRequired, |
1584 | cl::values(clEnumValN(OptionValue::Val, "value" , "help" ), |
1585 | clEnumValN(OptionValue::Val, "" , "" ))), |
1586 | ExpectedStrSize); |
1587 | } |
1588 | |
1589 | TEST(CommandLineTest, PrefixOptions) { |
1590 | cl::ResetCommandLineParser(); |
1591 | |
1592 | StackOption<std::string, cl::list<std::string>> IncludeDirs( |
1593 | "I" , cl::Prefix, cl::desc("Declare an include directory" )); |
1594 | |
1595 | // Test non-prefixed variant works with cl::Prefix options. |
1596 | EXPECT_TRUE(IncludeDirs.empty()); |
1597 | const char *args[] = {"prog" , "-I=/usr/include" }; |
1598 | EXPECT_TRUE( |
1599 | cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); |
1600 | EXPECT_EQ(IncludeDirs.size(), 1u); |
1601 | EXPECT_EQ(IncludeDirs.front().compare("/usr/include" ), 0); |
1602 | |
1603 | IncludeDirs.erase(pos: IncludeDirs.begin()); |
1604 | cl::ResetAllOptionOccurrences(); |
1605 | |
1606 | // Test non-prefixed variant works with cl::Prefix options when value is |
1607 | // passed in following argument. |
1608 | EXPECT_TRUE(IncludeDirs.empty()); |
1609 | const char *args2[] = {"prog" , "-I" , "/usr/include" }; |
1610 | EXPECT_TRUE( |
1611 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
1612 | EXPECT_EQ(IncludeDirs.size(), 1u); |
1613 | EXPECT_EQ(IncludeDirs.front().compare("/usr/include" ), 0); |
1614 | |
1615 | IncludeDirs.erase(pos: IncludeDirs.begin()); |
1616 | cl::ResetAllOptionOccurrences(); |
1617 | |
1618 | // Test prefixed variant works with cl::Prefix options. |
1619 | EXPECT_TRUE(IncludeDirs.empty()); |
1620 | const char *args3[] = {"prog" , "-I/usr/include" }; |
1621 | EXPECT_TRUE( |
1622 | cl::ParseCommandLineOptions(2, args3, StringRef(), &llvm::nulls())); |
1623 | EXPECT_EQ(IncludeDirs.size(), 1u); |
1624 | EXPECT_EQ(IncludeDirs.front().compare("/usr/include" ), 0); |
1625 | |
1626 | StackOption<std::string, cl::list<std::string>> MacroDefs( |
1627 | "D" , cl::AlwaysPrefix, cl::desc("Define a macro" ), |
1628 | cl::value_desc("MACRO[=VALUE]" )); |
1629 | |
1630 | cl::ResetAllOptionOccurrences(); |
1631 | |
1632 | // Test non-prefixed variant does not work with cl::AlwaysPrefix options: |
1633 | // equal sign is part of the value. |
1634 | EXPECT_TRUE(MacroDefs.empty()); |
1635 | const char *args4[] = {"prog" , "-D=HAVE_FOO" }; |
1636 | EXPECT_TRUE( |
1637 | cl::ParseCommandLineOptions(2, args4, StringRef(), &llvm::nulls())); |
1638 | EXPECT_EQ(MacroDefs.size(), 1u); |
1639 | EXPECT_EQ(MacroDefs.front().compare("=HAVE_FOO" ), 0); |
1640 | |
1641 | MacroDefs.erase(pos: MacroDefs.begin()); |
1642 | cl::ResetAllOptionOccurrences(); |
1643 | |
1644 | // Test non-prefixed variant does not allow value to be passed in following |
1645 | // argument with cl::AlwaysPrefix options. |
1646 | EXPECT_TRUE(MacroDefs.empty()); |
1647 | const char *args5[] = {"prog" , "-D" , "HAVE_FOO" }; |
1648 | EXPECT_FALSE( |
1649 | cl::ParseCommandLineOptions(3, args5, StringRef(), &llvm::nulls())); |
1650 | EXPECT_TRUE(MacroDefs.empty()); |
1651 | |
1652 | cl::ResetAllOptionOccurrences(); |
1653 | |
1654 | // Test prefixed variant works with cl::AlwaysPrefix options. |
1655 | EXPECT_TRUE(MacroDefs.empty()); |
1656 | const char *args6[] = {"prog" , "-DHAVE_FOO" }; |
1657 | EXPECT_TRUE( |
1658 | cl::ParseCommandLineOptions(2, args6, StringRef(), &llvm::nulls())); |
1659 | EXPECT_EQ(MacroDefs.size(), 1u); |
1660 | EXPECT_EQ(MacroDefs.front().compare("HAVE_FOO" ), 0); |
1661 | } |
1662 | |
1663 | TEST(CommandLineTest, GroupingWithValue) { |
1664 | cl::ResetCommandLineParser(); |
1665 | |
1666 | StackOption<bool> OptF("f" , cl::Grouping, cl::desc("Some flag" )); |
1667 | StackOption<bool> OptB("b" , cl::Grouping, cl::desc("Another flag" )); |
1668 | StackOption<bool> OptD("d" , cl::Grouping, cl::ValueDisallowed, |
1669 | cl::desc("ValueDisallowed option" )); |
1670 | StackOption<std::string> OptV("v" , cl::Grouping, |
1671 | cl::desc("ValueRequired option" )); |
1672 | StackOption<std::string> OptO("o" , cl::Grouping, cl::ValueOptional, |
1673 | cl::desc("ValueOptional option" )); |
1674 | |
1675 | // Should be possible to use an option which requires a value |
1676 | // at the end of a group. |
1677 | const char *args1[] = {"prog" , "-fv" , "val1" }; |
1678 | EXPECT_TRUE( |
1679 | cl::ParseCommandLineOptions(3, args1, StringRef(), &llvm::nulls())); |
1680 | EXPECT_TRUE(OptF); |
1681 | EXPECT_STREQ("val1" , OptV.c_str()); |
1682 | OptV.clear(); |
1683 | cl::ResetAllOptionOccurrences(); |
1684 | |
1685 | // Should not crash if it is accidentally used elsewhere in the group. |
1686 | const char *args2[] = {"prog" , "-vf" , "val2" }; |
1687 | EXPECT_FALSE( |
1688 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
1689 | OptV.clear(); |
1690 | cl::ResetAllOptionOccurrences(); |
1691 | |
1692 | // Should allow the "opt=value" form at the end of the group |
1693 | const char *args3[] = {"prog" , "-fv=val3" }; |
1694 | EXPECT_TRUE( |
1695 | cl::ParseCommandLineOptions(2, args3, StringRef(), &llvm::nulls())); |
1696 | EXPECT_TRUE(OptF); |
1697 | EXPECT_STREQ("val3" , OptV.c_str()); |
1698 | OptV.clear(); |
1699 | cl::ResetAllOptionOccurrences(); |
1700 | |
1701 | // Should allow assigning a value for a ValueOptional option |
1702 | // at the end of the group |
1703 | const char *args4[] = {"prog" , "-fo=val4" }; |
1704 | EXPECT_TRUE( |
1705 | cl::ParseCommandLineOptions(2, args4, StringRef(), &llvm::nulls())); |
1706 | EXPECT_TRUE(OptF); |
1707 | EXPECT_STREQ("val4" , OptO.c_str()); |
1708 | OptO.clear(); |
1709 | cl::ResetAllOptionOccurrences(); |
1710 | |
1711 | // Should assign an empty value if a ValueOptional option is used elsewhere |
1712 | // in the group. |
1713 | const char *args5[] = {"prog" , "-fob" }; |
1714 | EXPECT_TRUE( |
1715 | cl::ParseCommandLineOptions(2, args5, StringRef(), &llvm::nulls())); |
1716 | EXPECT_TRUE(OptF); |
1717 | EXPECT_EQ(1, OptO.getNumOccurrences()); |
1718 | EXPECT_EQ(1, OptB.getNumOccurrences()); |
1719 | EXPECT_TRUE(OptO.empty()); |
1720 | cl::ResetAllOptionOccurrences(); |
1721 | |
1722 | // Should not allow an assignment for a ValueDisallowed option. |
1723 | const char *args6[] = {"prog" , "-fd=false" }; |
1724 | EXPECT_FALSE( |
1725 | cl::ParseCommandLineOptions(2, args6, StringRef(), &llvm::nulls())); |
1726 | } |
1727 | |
1728 | TEST(CommandLineTest, GroupingAndPrefix) { |
1729 | cl::ResetCommandLineParser(); |
1730 | |
1731 | StackOption<bool> OptF("f" , cl::Grouping, cl::desc("Some flag" )); |
1732 | StackOption<bool> OptB("b" , cl::Grouping, cl::desc("Another flag" )); |
1733 | StackOption<std::string> OptP("p" , cl::Prefix, cl::Grouping, |
1734 | cl::desc("Prefix and Grouping" )); |
1735 | StackOption<std::string> OptA("a" , cl::AlwaysPrefix, cl::Grouping, |
1736 | cl::desc("AlwaysPrefix and Grouping" )); |
1737 | |
1738 | // Should be possible to use a cl::Prefix option without grouping. |
1739 | const char *args1[] = {"prog" , "-pval1" }; |
1740 | EXPECT_TRUE( |
1741 | cl::ParseCommandLineOptions(2, args1, StringRef(), &llvm::nulls())); |
1742 | EXPECT_STREQ("val1" , OptP.c_str()); |
1743 | OptP.clear(); |
1744 | cl::ResetAllOptionOccurrences(); |
1745 | |
1746 | // Should be possible to pass a value in a separate argument. |
1747 | const char *args2[] = {"prog" , "-p" , "val2" }; |
1748 | EXPECT_TRUE( |
1749 | cl::ParseCommandLineOptions(3, args2, StringRef(), &llvm::nulls())); |
1750 | EXPECT_STREQ("val2" , OptP.c_str()); |
1751 | OptP.clear(); |
1752 | cl::ResetAllOptionOccurrences(); |
1753 | |
1754 | // The "-opt=value" form should work, too. |
1755 | const char *args3[] = {"prog" , "-p=val3" }; |
1756 | EXPECT_TRUE( |
1757 | cl::ParseCommandLineOptions(2, args3, StringRef(), &llvm::nulls())); |
1758 | EXPECT_STREQ("val3" , OptP.c_str()); |
1759 | OptP.clear(); |
1760 | cl::ResetAllOptionOccurrences(); |
1761 | |
1762 | // All three previous cases should work the same way if an option with both |
1763 | // cl::Prefix and cl::Grouping modifiers is used at the end of a group. |
1764 | const char *args4[] = {"prog" , "-fpval4" }; |
1765 | EXPECT_TRUE( |
1766 | cl::ParseCommandLineOptions(2, args4, StringRef(), &llvm::nulls())); |
1767 | EXPECT_TRUE(OptF); |
1768 | EXPECT_STREQ("val4" , OptP.c_str()); |
1769 | OptP.clear(); |
1770 | cl::ResetAllOptionOccurrences(); |
1771 | |
1772 | const char *args5[] = {"prog" , "-fp" , "val5" }; |
1773 | EXPECT_TRUE( |
1774 | cl::ParseCommandLineOptions(3, args5, StringRef(), &llvm::nulls())); |
1775 | EXPECT_TRUE(OptF); |
1776 | EXPECT_STREQ("val5" , OptP.c_str()); |
1777 | OptP.clear(); |
1778 | cl::ResetAllOptionOccurrences(); |
1779 | |
1780 | const char *args6[] = {"prog" , "-fp=val6" }; |
1781 | EXPECT_TRUE( |
1782 | cl::ParseCommandLineOptions(2, args6, StringRef(), &llvm::nulls())); |
1783 | EXPECT_TRUE(OptF); |
1784 | EXPECT_STREQ("val6" , OptP.c_str()); |
1785 | OptP.clear(); |
1786 | cl::ResetAllOptionOccurrences(); |
1787 | |
1788 | // Should assign a value even if the part after a cl::Prefix option is equal |
1789 | // to the name of another option. |
1790 | const char *args7[] = {"prog" , "-fpb" }; |
1791 | EXPECT_TRUE( |
1792 | cl::ParseCommandLineOptions(2, args7, StringRef(), &llvm::nulls())); |
1793 | EXPECT_TRUE(OptF); |
1794 | EXPECT_STREQ("b" , OptP.c_str()); |
1795 | EXPECT_FALSE(OptB); |
1796 | OptP.clear(); |
1797 | cl::ResetAllOptionOccurrences(); |
1798 | |
1799 | // Should be possible to use a cl::AlwaysPrefix option without grouping. |
1800 | const char *args8[] = {"prog" , "-aval8" }; |
1801 | EXPECT_TRUE( |
1802 | cl::ParseCommandLineOptions(2, args8, StringRef(), &llvm::nulls())); |
1803 | EXPECT_STREQ("val8" , OptA.c_str()); |
1804 | OptA.clear(); |
1805 | cl::ResetAllOptionOccurrences(); |
1806 | |
1807 | // Should not be possible to pass a value in a separate argument. |
1808 | const char *args9[] = {"prog" , "-a" , "val9" }; |
1809 | EXPECT_FALSE( |
1810 | cl::ParseCommandLineOptions(3, args9, StringRef(), &llvm::nulls())); |
1811 | cl::ResetAllOptionOccurrences(); |
1812 | |
1813 | // With the "-opt=value" form, the "=" symbol should be preserved. |
1814 | const char *args10[] = {"prog" , "-a=val10" }; |
1815 | EXPECT_TRUE( |
1816 | cl::ParseCommandLineOptions(2, args10, StringRef(), &llvm::nulls())); |
1817 | EXPECT_STREQ("=val10" , OptA.c_str()); |
1818 | OptA.clear(); |
1819 | cl::ResetAllOptionOccurrences(); |
1820 | |
1821 | // All three previous cases should work the same way if an option with both |
1822 | // cl::AlwaysPrefix and cl::Grouping modifiers is used at the end of a group. |
1823 | const char *args11[] = {"prog" , "-faval11" }; |
1824 | EXPECT_TRUE( |
1825 | cl::ParseCommandLineOptions(2, args11, StringRef(), &llvm::nulls())); |
1826 | EXPECT_TRUE(OptF); |
1827 | EXPECT_STREQ("val11" , OptA.c_str()); |
1828 | OptA.clear(); |
1829 | cl::ResetAllOptionOccurrences(); |
1830 | |
1831 | const char *args12[] = {"prog" , "-fa" , "val12" }; |
1832 | EXPECT_FALSE( |
1833 | cl::ParseCommandLineOptions(3, args12, StringRef(), &llvm::nulls())); |
1834 | cl::ResetAllOptionOccurrences(); |
1835 | |
1836 | const char *args13[] = {"prog" , "-fa=val13" }; |
1837 | EXPECT_TRUE( |
1838 | cl::ParseCommandLineOptions(2, args13, StringRef(), &llvm::nulls())); |
1839 | EXPECT_TRUE(OptF); |
1840 | EXPECT_STREQ("=val13" , OptA.c_str()); |
1841 | OptA.clear(); |
1842 | cl::ResetAllOptionOccurrences(); |
1843 | |
1844 | // Should assign a value even if the part after a cl::AlwaysPrefix option |
1845 | // is equal to the name of another option. |
1846 | const char *args14[] = {"prog" , "-fab" }; |
1847 | EXPECT_TRUE( |
1848 | cl::ParseCommandLineOptions(2, args14, StringRef(), &llvm::nulls())); |
1849 | EXPECT_TRUE(OptF); |
1850 | EXPECT_STREQ("b" , OptA.c_str()); |
1851 | EXPECT_FALSE(OptB); |
1852 | OptA.clear(); |
1853 | cl::ResetAllOptionOccurrences(); |
1854 | } |
1855 | |
1856 | TEST(CommandLineTest, LongOptions) { |
1857 | cl::ResetCommandLineParser(); |
1858 | |
1859 | StackOption<bool> OptA("a" , cl::desc("Some flag" )); |
1860 | StackOption<bool> OptBLong("long-flag" , cl::desc("Some long flag" )); |
1861 | StackOption<bool, cl::alias> OptB("b" , cl::desc("Alias to --long-flag" ), |
1862 | cl::aliasopt(OptBLong)); |
1863 | StackOption<std::string> OptAB("ab" , cl::desc("Another long option" )); |
1864 | |
1865 | std::string Errs; |
1866 | raw_string_ostream OS(Errs); |
1867 | |
1868 | const char *args1[] = {"prog" , "-a" , "-ab" , "val1" }; |
1869 | const char *args2[] = {"prog" , "-a" , "--ab" , "val1" }; |
1870 | const char *args3[] = {"prog" , "-ab" , "--ab" , "val1" }; |
1871 | |
1872 | // |
1873 | // The following tests treat `-` and `--` the same, and always match the |
1874 | // longest string. |
1875 | // |
1876 | |
1877 | EXPECT_TRUE( |
1878 | cl::ParseCommandLineOptions(4, args1, StringRef(), &OS)); OS.flush(); |
1879 | EXPECT_TRUE(OptA); |
1880 | EXPECT_FALSE(OptBLong); |
1881 | EXPECT_STREQ("val1" , OptAB.c_str()); |
1882 | EXPECT_TRUE(Errs.empty()); Errs.clear(); |
1883 | cl::ResetAllOptionOccurrences(); |
1884 | |
1885 | EXPECT_TRUE( |
1886 | cl::ParseCommandLineOptions(4, args2, StringRef(), &OS)); OS.flush(); |
1887 | EXPECT_TRUE(OptA); |
1888 | EXPECT_FALSE(OptBLong); |
1889 | EXPECT_STREQ("val1" , OptAB.c_str()); |
1890 | EXPECT_TRUE(Errs.empty()); Errs.clear(); |
1891 | cl::ResetAllOptionOccurrences(); |
1892 | |
1893 | // Fails because `-ab` and `--ab` are treated the same and appear more than |
1894 | // once. Also, `val1` is unexpected. |
1895 | EXPECT_FALSE( |
1896 | cl::ParseCommandLineOptions(4, args3, StringRef(), &OS)); OS.flush(); |
1897 | outs()<< Errs << "\n" ; |
1898 | EXPECT_FALSE(Errs.empty()); Errs.clear(); |
1899 | cl::ResetAllOptionOccurrences(); |
1900 | |
1901 | // |
1902 | // The following tests treat `-` and `--` differently, with `-` for short, and |
1903 | // `--` for long options. |
1904 | // |
1905 | |
1906 | // Fails because `-ab` is treated as `-a -b`, so `-a` is seen twice, and |
1907 | // `val1` is unexpected. |
1908 | EXPECT_FALSE(cl::ParseCommandLineOptions(4, args1, StringRef(), |
1909 | &OS, nullptr, true)); OS.flush(); |
1910 | EXPECT_FALSE(Errs.empty()); Errs.clear(); |
1911 | cl::ResetAllOptionOccurrences(); |
1912 | |
1913 | // Works because `-a` is treated differently than `--ab`. |
1914 | EXPECT_TRUE(cl::ParseCommandLineOptions(4, args2, StringRef(), |
1915 | &OS, nullptr, true)); OS.flush(); |
1916 | EXPECT_TRUE(Errs.empty()); Errs.clear(); |
1917 | cl::ResetAllOptionOccurrences(); |
1918 | |
1919 | // Works because `-ab` is treated as `-a -b`, and `--ab` is a long option. |
1920 | EXPECT_TRUE(cl::ParseCommandLineOptions(4, args3, StringRef(), |
1921 | &OS, nullptr, true)); |
1922 | EXPECT_TRUE(OptA); |
1923 | EXPECT_TRUE(OptBLong); |
1924 | EXPECT_STREQ("val1" , OptAB.c_str()); |
1925 | OS.flush(); |
1926 | EXPECT_TRUE(Errs.empty()); Errs.clear(); |
1927 | cl::ResetAllOptionOccurrences(); |
1928 | } |
1929 | |
1930 | TEST(CommandLineTest, OptionErrorMessage) { |
1931 | // When there is an error, we expect some error message like: |
1932 | // prog: for the -a option: [...] |
1933 | // |
1934 | // Test whether the "for the -a option"-part is correctly formatted. |
1935 | cl::ResetCommandLineParser(); |
1936 | |
1937 | StackOption<bool> OptA("a" , cl::desc("Some option" )); |
1938 | StackOption<bool> OptLong("long" , cl::desc("Some long option" )); |
1939 | |
1940 | std::string Errs; |
1941 | raw_string_ostream OS(Errs); |
1942 | |
1943 | OptA.error(Message: "custom error" , Errs&: OS); |
1944 | OS.flush(); |
1945 | EXPECT_NE(Errs.find("for the -a option:" ), std::string::npos); |
1946 | Errs.clear(); |
1947 | |
1948 | OptLong.error(Message: "custom error" , Errs&: OS); |
1949 | OS.flush(); |
1950 | EXPECT_NE(Errs.find("for the --long option:" ), std::string::npos); |
1951 | Errs.clear(); |
1952 | |
1953 | cl::ResetAllOptionOccurrences(); |
1954 | } |
1955 | |
1956 | TEST(CommandLineTest, OptionErrorMessageSuggest) { |
1957 | // When there is an error, and the edit-distance is not very large, |
1958 | // we expect some error message like: |
1959 | // prog: did you mean '--option'? |
1960 | // |
1961 | // Test whether this message is well-formatted. |
1962 | cl::ResetCommandLineParser(); |
1963 | |
1964 | StackOption<bool> OptLong("aluminium" , cl::desc("Some long option" )); |
1965 | |
1966 | const char *args[] = {"prog" , "--aluminum" }; |
1967 | |
1968 | std::string Errs; |
1969 | raw_string_ostream OS(Errs); |
1970 | |
1971 | EXPECT_FALSE(cl::ParseCommandLineOptions(2, args, StringRef(), &OS)); |
1972 | OS.flush(); |
1973 | EXPECT_NE(Errs.find("prog: Did you mean '--aluminium'?\n" ), |
1974 | std::string::npos); |
1975 | Errs.clear(); |
1976 | |
1977 | cl::ResetAllOptionOccurrences(); |
1978 | } |
1979 | |
1980 | TEST(CommandLineTest, OptionErrorMessageSuggestNoHidden) { |
1981 | // We expect that 'really hidden' option do not show up in option |
1982 | // suggestions. |
1983 | cl::ResetCommandLineParser(); |
1984 | |
1985 | StackOption<bool> OptLong("aluminium" , cl::desc("Some long option" )); |
1986 | StackOption<bool> OptLong2("aluminum" , cl::desc("Bad option" ), |
1987 | cl::ReallyHidden); |
1988 | |
1989 | const char *args[] = {"prog" , "--alumnum" }; |
1990 | |
1991 | std::string Errs; |
1992 | raw_string_ostream OS(Errs); |
1993 | |
1994 | EXPECT_FALSE(cl::ParseCommandLineOptions(2, args, StringRef(), &OS)); |
1995 | OS.flush(); |
1996 | EXPECT_NE(Errs.find("prog: Did you mean '--aluminium'?\n" ), |
1997 | std::string::npos); |
1998 | Errs.clear(); |
1999 | |
2000 | cl::ResetAllOptionOccurrences(); |
2001 | } |
2002 | |
2003 | TEST(CommandLineTest, Callback) { |
2004 | cl::ResetCommandLineParser(); |
2005 | |
2006 | StackOption<bool> OptA("a" , cl::desc("option a" )); |
2007 | StackOption<bool> OptB( |
2008 | "b" , cl::desc("option b -- This option turns on option a" ), |
2009 | cl::callback(CB: [&](const bool &) { OptA = true; })); |
2010 | StackOption<bool> OptC( |
2011 | "c" , cl::desc("option c -- This option turns on options a and b" ), |
2012 | cl::callback(CB: [&](const bool &) { OptB = true; })); |
2013 | StackOption<std::string, cl::list<std::string>> List( |
2014 | "list" , |
2015 | cl::desc("option list -- This option turns on options a, b, and c when " |
2016 | "'foo' is included in list" ), |
2017 | cl::CommaSeparated, |
2018 | cl::callback(CB: [&](const std::string &Str) { |
2019 | if (Str == "foo" ) |
2020 | OptC = true; |
2021 | })); |
2022 | |
2023 | const char *args1[] = {"prog" , "-a" }; |
2024 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args1)); |
2025 | EXPECT_TRUE(OptA); |
2026 | EXPECT_FALSE(OptB); |
2027 | EXPECT_FALSE(OptC); |
2028 | EXPECT_EQ(List.size(), 0u); |
2029 | cl::ResetAllOptionOccurrences(); |
2030 | |
2031 | const char *args2[] = {"prog" , "-b" }; |
2032 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args2)); |
2033 | EXPECT_TRUE(OptA); |
2034 | EXPECT_TRUE(OptB); |
2035 | EXPECT_FALSE(OptC); |
2036 | EXPECT_EQ(List.size(), 0u); |
2037 | cl::ResetAllOptionOccurrences(); |
2038 | |
2039 | const char *args3[] = {"prog" , "-c" }; |
2040 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args3)); |
2041 | EXPECT_TRUE(OptA); |
2042 | EXPECT_TRUE(OptB); |
2043 | EXPECT_TRUE(OptC); |
2044 | EXPECT_EQ(List.size(), 0u); |
2045 | cl::ResetAllOptionOccurrences(); |
2046 | |
2047 | const char *args4[] = {"prog" , "--list=foo,bar" }; |
2048 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args4)); |
2049 | EXPECT_TRUE(OptA); |
2050 | EXPECT_TRUE(OptB); |
2051 | EXPECT_TRUE(OptC); |
2052 | EXPECT_EQ(List.size(), 2u); |
2053 | cl::ResetAllOptionOccurrences(); |
2054 | |
2055 | const char *args5[] = {"prog" , "--list=bar" }; |
2056 | EXPECT_TRUE(cl::ParseCommandLineOptions(2, args5)); |
2057 | EXPECT_FALSE(OptA); |
2058 | EXPECT_FALSE(OptB); |
2059 | EXPECT_FALSE(OptC); |
2060 | EXPECT_EQ(List.size(), 1u); |
2061 | |
2062 | cl::ResetAllOptionOccurrences(); |
2063 | } |
2064 | |
2065 | enum Enum { Val1, Val2 }; |
2066 | static cl::bits<Enum> ExampleBits( |
2067 | cl::desc("An example cl::bits to ensure it compiles" ), |
2068 | cl::values( |
2069 | clEnumValN(Val1, "bits-val1" , "The Val1 value" ), |
2070 | clEnumValN(Val1, "bits-val2" , "The Val2 value" ))); |
2071 | |
2072 | TEST(CommandLineTest, ConsumeAfterOnePositional) { |
2073 | cl::ResetCommandLineParser(); |
2074 | |
2075 | // input [args] |
2076 | StackOption<std::string, cl::opt<std::string>> Input(cl::Positional, |
2077 | cl::Required); |
2078 | StackOption<std::string, cl::list<std::string>> (cl::ConsumeAfter); |
2079 | |
2080 | const char *Args[] = {"prog" , "input" , "arg1" , "arg2" }; |
2081 | |
2082 | std::string Errs; |
2083 | raw_string_ostream OS(Errs); |
2084 | EXPECT_TRUE(cl::ParseCommandLineOptions(4, Args, StringRef(), &OS)); |
2085 | OS.flush(); |
2086 | EXPECT_EQ("input" , Input); |
2087 | EXPECT_EQ(ExtraArgs.size(), 2u); |
2088 | EXPECT_EQ(ExtraArgs[0], "arg1" ); |
2089 | EXPECT_EQ(ExtraArgs[1], "arg2" ); |
2090 | EXPECT_TRUE(Errs.empty()); |
2091 | } |
2092 | |
2093 | TEST(CommandLineTest, ConsumeAfterTwoPositionals) { |
2094 | cl::ResetCommandLineParser(); |
2095 | |
2096 | // input1 input2 [args] |
2097 | StackOption<std::string, cl::opt<std::string>> Input1(cl::Positional, |
2098 | cl::Required); |
2099 | StackOption<std::string, cl::opt<std::string>> Input2(cl::Positional, |
2100 | cl::Required); |
2101 | StackOption<std::string, cl::list<std::string>> (cl::ConsumeAfter); |
2102 | |
2103 | const char *Args[] = {"prog" , "input1" , "input2" , "arg1" , "arg2" }; |
2104 | |
2105 | std::string Errs; |
2106 | raw_string_ostream OS(Errs); |
2107 | EXPECT_TRUE(cl::ParseCommandLineOptions(5, Args, StringRef(), &OS)); |
2108 | OS.flush(); |
2109 | EXPECT_EQ("input1" , Input1); |
2110 | EXPECT_EQ("input2" , Input2); |
2111 | EXPECT_EQ(ExtraArgs.size(), 2u); |
2112 | EXPECT_EQ(ExtraArgs[0], "arg1" ); |
2113 | EXPECT_EQ(ExtraArgs[1], "arg2" ); |
2114 | EXPECT_TRUE(Errs.empty()); |
2115 | } |
2116 | |
2117 | TEST(CommandLineTest, ResetAllOptionOccurrences) { |
2118 | cl::ResetCommandLineParser(); |
2119 | |
2120 | // -option -str -enableA -enableC [sink] input [args] |
2121 | StackOption<bool> Option("option" ); |
2122 | StackOption<std::string> Str("str" ); |
2123 | enum Vals { ValA, ValB, ValC }; |
2124 | StackOption<Vals, cl::bits<Vals>> Bits( |
2125 | cl::values(clEnumValN(ValA, "enableA" , "Enable A" ), |
2126 | clEnumValN(ValB, "enableB" , "Enable B" ), |
2127 | clEnumValN(ValC, "enableC" , "Enable C" ))); |
2128 | StackOption<std::string, cl::list<std::string>> Sink(cl::Sink); |
2129 | StackOption<std::string> Input(cl::Positional); |
2130 | StackOption<std::string, cl::list<std::string>> (cl::ConsumeAfter); |
2131 | |
2132 | const char *Args[] = {"prog" , "-option" , "-str=STR" , "-enableA" , |
2133 | "-enableC" , "-unknown" , "input" , "-arg" }; |
2134 | |
2135 | std::string Errs; |
2136 | raw_string_ostream OS(Errs); |
2137 | EXPECT_TRUE(cl::ParseCommandLineOptions(8, Args, StringRef(), &OS)); |
2138 | EXPECT_TRUE(OS.str().empty()); |
2139 | |
2140 | EXPECT_TRUE(Option); |
2141 | EXPECT_EQ("STR" , Str); |
2142 | EXPECT_EQ((1u << ValA) | (1u << ValC), Bits.getBits()); |
2143 | EXPECT_EQ(1u, Sink.size()); |
2144 | EXPECT_EQ("-unknown" , Sink[0]); |
2145 | EXPECT_EQ("input" , Input); |
2146 | EXPECT_EQ(1u, ExtraArgs.size()); |
2147 | EXPECT_EQ("-arg" , ExtraArgs[0]); |
2148 | |
2149 | cl::ResetAllOptionOccurrences(); |
2150 | EXPECT_FALSE(Option); |
2151 | EXPECT_EQ("" , Str); |
2152 | EXPECT_EQ(0u, Bits.getBits()); |
2153 | EXPECT_EQ(0u, Sink.size()); |
2154 | EXPECT_EQ(0, Input.getNumOccurrences()); |
2155 | EXPECT_EQ(0u, ExtraArgs.size()); |
2156 | } |
2157 | |
2158 | TEST(CommandLineTest, DefaultValue) { |
2159 | cl::ResetCommandLineParser(); |
2160 | |
2161 | StackOption<bool> BoolOption("bool-option" ); |
2162 | StackOption<std::string> StrOption("str-option" ); |
2163 | StackOption<bool> BoolInitOption("bool-init-option" , cl::init(Val: true)); |
2164 | StackOption<std::string> StrInitOption("str-init-option" , |
2165 | cl::init(Val: "str-default-value" )); |
2166 | |
2167 | const char *Args[] = {"prog" }; // no options |
2168 | |
2169 | std::string Errs; |
2170 | raw_string_ostream OS(Errs); |
2171 | EXPECT_TRUE(cl::ParseCommandLineOptions(1, Args, StringRef(), &OS)); |
2172 | EXPECT_TRUE(OS.str().empty()); |
2173 | |
2174 | EXPECT_TRUE(!BoolOption); |
2175 | EXPECT_FALSE(BoolOption.Default.hasValue()); |
2176 | EXPECT_EQ(0, BoolOption.getNumOccurrences()); |
2177 | |
2178 | EXPECT_EQ("" , StrOption); |
2179 | EXPECT_FALSE(StrOption.Default.hasValue()); |
2180 | EXPECT_EQ(0, StrOption.getNumOccurrences()); |
2181 | |
2182 | EXPECT_TRUE(BoolInitOption); |
2183 | EXPECT_TRUE(BoolInitOption.Default.hasValue()); |
2184 | EXPECT_EQ(0, BoolInitOption.getNumOccurrences()); |
2185 | |
2186 | EXPECT_EQ("str-default-value" , StrInitOption); |
2187 | EXPECT_TRUE(StrInitOption.Default.hasValue()); |
2188 | EXPECT_EQ(0, StrInitOption.getNumOccurrences()); |
2189 | |
2190 | const char *Args2[] = {"prog" , "-bool-option" , "-str-option=str-value" , |
2191 | "-bool-init-option=0" , |
2192 | "-str-init-option=str-init-value" }; |
2193 | |
2194 | EXPECT_TRUE(cl::ParseCommandLineOptions(5, Args2, StringRef(), &OS)); |
2195 | EXPECT_TRUE(OS.str().empty()); |
2196 | |
2197 | EXPECT_TRUE(BoolOption); |
2198 | EXPECT_FALSE(BoolOption.Default.hasValue()); |
2199 | EXPECT_EQ(1, BoolOption.getNumOccurrences()); |
2200 | |
2201 | EXPECT_EQ("str-value" , StrOption); |
2202 | EXPECT_FALSE(StrOption.Default.hasValue()); |
2203 | EXPECT_EQ(1, StrOption.getNumOccurrences()); |
2204 | |
2205 | EXPECT_FALSE(BoolInitOption); |
2206 | EXPECT_TRUE(BoolInitOption.Default.hasValue()); |
2207 | EXPECT_EQ(1, BoolInitOption.getNumOccurrences()); |
2208 | |
2209 | EXPECT_EQ("str-init-value" , StrInitOption); |
2210 | EXPECT_TRUE(StrInitOption.Default.hasValue()); |
2211 | EXPECT_EQ(1, StrInitOption.getNumOccurrences()); |
2212 | } |
2213 | |
2214 | TEST(CommandLineTest, HelpWithoutSubcommands) { |
2215 | // Check that the help message does not contain the "[subcommand]" placeholder |
2216 | // and the "SUBCOMMANDS" section if there are no subcommands. |
2217 | cl::ResetCommandLineParser(); |
2218 | StackOption<bool> Opt("opt" , cl::init(Val: false)); |
2219 | const char *args[] = {"prog" }; |
2220 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), |
2221 | &llvm::nulls())); |
2222 | auto Output = interceptStdout(F: []() { cl::PrintHelpMessage(); }); |
2223 | EXPECT_NE(std::string::npos, Output.find("USAGE: prog [options]" )) << Output; |
2224 | EXPECT_EQ(std::string::npos, Output.find("SUBCOMMANDS:" )) << Output; |
2225 | cl::ResetCommandLineParser(); |
2226 | } |
2227 | |
2228 | TEST(CommandLineTest, HelpWithSubcommands) { |
2229 | // Check that the help message contains the "[subcommand]" placeholder in the |
2230 | // "USAGE" line and describes subcommands. |
2231 | cl::ResetCommandLineParser(); |
2232 | StackSubCommand SC1("sc1" , "First Subcommand" ); |
2233 | StackSubCommand SC2("sc2" , "Second Subcommand" ); |
2234 | StackOption<bool> SC1Opt("sc1" , cl::sub(SC1), cl::init(Val: false)); |
2235 | StackOption<bool> SC2Opt("sc2" , cl::sub(SC2), cl::init(Val: false)); |
2236 | const char *args[] = {"prog" }; |
2237 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), |
2238 | &llvm::nulls())); |
2239 | auto Output = interceptStdout(F: []() { cl::PrintHelpMessage(); }); |
2240 | EXPECT_NE(std::string::npos, |
2241 | Output.find("USAGE: prog [subcommand] [options]" )) |
2242 | << Output; |
2243 | EXPECT_NE(std::string::npos, Output.find("SUBCOMMANDS:" )) << Output; |
2244 | EXPECT_NE(std::string::npos, Output.find("sc1 - First Subcommand" )) << Output; |
2245 | EXPECT_NE(std::string::npos, Output.find("sc2 - Second Subcommand" )) |
2246 | << Output; |
2247 | cl::ResetCommandLineParser(); |
2248 | } |
2249 | |
2250 | TEST(CommandLineTest, UnknownCommands) { |
2251 | cl::ResetCommandLineParser(); |
2252 | |
2253 | StackSubCommand SC1("foo" , "Foo subcommand" ); |
2254 | StackSubCommand SC2("bar" , "Bar subcommand" ); |
2255 | StackOption<bool> SC1Opt("put" , cl::sub(SC1)); |
2256 | StackOption<bool> SC2Opt("get" , cl::sub(SC2)); |
2257 | StackOption<bool> TopOpt1("peek" ); |
2258 | StackOption<bool> TopOpt2("set" ); |
2259 | |
2260 | std::string Errs; |
2261 | raw_string_ostream OS(Errs); |
2262 | |
2263 | const char *Args1[] = {"prog" , "baz" , "--get" }; |
2264 | EXPECT_FALSE( |
2265 | cl::ParseCommandLineOptions(std::size(Args1), Args1, StringRef(), &OS)); |
2266 | EXPECT_EQ(Errs, |
2267 | "prog: Unknown subcommand 'baz'. Try: 'prog --help'\n" |
2268 | "prog: Did you mean 'bar'?\n" |
2269 | "prog: Unknown command line argument '--get'. Try: 'prog --help'\n" |
2270 | "prog: Did you mean '--set'?\n" ); |
2271 | |
2272 | // Do not show a suggestion if the subcommand is not similar to any known. |
2273 | Errs.clear(); |
2274 | const char *Args2[] = {"prog" , "faz" }; |
2275 | EXPECT_FALSE( |
2276 | cl::ParseCommandLineOptions(std::size(Args2), Args2, StringRef(), &OS)); |
2277 | EXPECT_EQ(Errs, "prog: Unknown subcommand 'faz'. Try: 'prog --help'\n" ); |
2278 | } |
2279 | |
2280 | TEST(CommandLineTest, SubCommandGroups) { |
2281 | // Check that options in subcommand groups are associated with expected |
2282 | // subcommands. |
2283 | |
2284 | cl::ResetCommandLineParser(); |
2285 | |
2286 | StackSubCommand SC1("sc1" , "SC1 subcommand" ); |
2287 | StackSubCommand SC2("sc2" , "SC2 subcommand" ); |
2288 | StackSubCommand SC3("sc3" , "SC3 subcommand" ); |
2289 | cl::SubCommandGroup Group12 = {&SC1, &SC2}; |
2290 | |
2291 | StackOption<bool> Opt12("opt12" , cl::sub(Group12), cl::init(Val: false)); |
2292 | StackOption<bool> Opt3("opt3" , cl::sub(SC3), cl::init(Val: false)); |
2293 | |
2294 | // The "--opt12" option is expected to be added to both subcommands in the |
2295 | // group, but not to the top-level "no subcommand" pseudo-subcommand or the |
2296 | // "sc3" subcommand. |
2297 | EXPECT_EQ(1U, SC1.OptionsMap.size()); |
2298 | EXPECT_TRUE(SC1.OptionsMap.contains("opt12" )); |
2299 | |
2300 | EXPECT_EQ(1U, SC2.OptionsMap.size()); |
2301 | EXPECT_TRUE(SC2.OptionsMap.contains("opt12" )); |
2302 | |
2303 | EXPECT_FALSE(cl::SubCommand::getTopLevel().OptionsMap.contains("opt12" )); |
2304 | EXPECT_FALSE(SC3.OptionsMap.contains("opt12" )); |
2305 | } |
2306 | |
2307 | TEST(CommandLineTest, HelpWithEmptyCategory) { |
2308 | cl::ResetCommandLineParser(); |
2309 | |
2310 | cl::OptionCategory Category1("First Category" ); |
2311 | cl::OptionCategory Category2("Second Category" ); |
2312 | StackOption<int> Opt1("opt1" , cl::cat(Category1)); |
2313 | StackOption<int> Opt2("opt2" , cl::cat(Category2)); |
2314 | cl::HideUnrelatedOptions(Category&: Category2); |
2315 | |
2316 | const char *args[] = {"prog" }; |
2317 | EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(args), args, StringRef(), |
2318 | &llvm::nulls())); |
2319 | auto Output = interceptStdout( |
2320 | F: []() { cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); }); |
2321 | EXPECT_EQ(std::string::npos, Output.find("First Category" )) |
2322 | << "An empty category should not be printed" ; |
2323 | |
2324 | Output = interceptStdout( |
2325 | F: []() { cl::PrintHelpMessage(/*Hidden=*/true, /*Categorized=*/true); }); |
2326 | EXPECT_EQ(std::string::npos, Output.find("First Category" )) |
2327 | << "An empty category should not be printed" ; |
2328 | |
2329 | cl::ResetCommandLineParser(); |
2330 | } |
2331 | |
2332 | } // anonymous namespace |
2333 | |