1 //
2 // Copyright 2019 The Abseil Authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // https://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 #include "absl/flags/parse.h"
17
18 #include <stdlib.h>
19
20 #include <fstream>
21 #include <string>
22 #include <vector>
23
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 #include "absl/base/internal/raw_logging.h"
27 #include "absl/base/internal/scoped_set_env.h"
28 #include "absl/flags/declare.h"
29 #include "absl/flags/flag.h"
30 #include "absl/flags/internal/parse.h"
31 #include "absl/flags/internal/usage.h"
32 #include "absl/flags/reflection.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/strings/substitute.h"
36 #include "absl/types/span.h"
37
38 #ifdef _WIN32
39 #include <windows.h>
40 #endif
41
42 namespace {
43
44 using absl::base_internal::ScopedSetEnv;
45
46 struct UDT {
47 UDT() = default;
48 UDT(const UDT&) = default;
49 UDT& operator=(const UDT&) = default;
UDT__anon502ad7420111::UDT50 UDT(int v) : value(v) {} // NOLINT
51
52 int value;
53 };
54
AbslParseFlag(absl::string_view in,UDT * udt,std::string * err)55 bool AbslParseFlag(absl::string_view in, UDT* udt, std::string* err) {
56 if (in == "A") {
57 udt->value = 1;
58 return true;
59 }
60 if (in == "AAA") {
61 udt->value = 10;
62 return true;
63 }
64
65 *err = "Use values A, AAA instead";
66 return false;
67 }
AbslUnparseFlag(const UDT & udt)68 std::string AbslUnparseFlag(const UDT& udt) {
69 return udt.value == 1 ? "A" : "AAA";
70 }
71
GetTestTmpDirEnvVar(const char * const env_var_name)72 std::string GetTestTmpDirEnvVar(const char* const env_var_name) {
73 #ifdef _WIN32
74 char buf[MAX_PATH];
75 auto get_res = GetEnvironmentVariableA(env_var_name, buf, sizeof(buf));
76 if (get_res >= sizeof(buf) || get_res == 0) {
77 return "";
78 }
79
80 return std::string(buf, get_res);
81 #else
82 const char* val = ::getenv(env_var_name);
83 if (val == nullptr) {
84 return "";
85 }
86
87 return val;
88 #endif
89 }
90
GetTestTempDir()91 const std::string& GetTestTempDir() {
92 static std::string* temp_dir_name = []() -> std::string* {
93 std::string* res = new std::string(GetTestTmpDirEnvVar("TEST_TMPDIR"));
94
95 if (res->empty()) {
96 *res = GetTestTmpDirEnvVar("TMPDIR");
97 }
98
99 if (res->empty()) {
100 #ifdef _WIN32
101 char temp_path_buffer[MAX_PATH];
102
103 auto len = GetTempPathA(MAX_PATH, temp_path_buffer);
104 if (len < MAX_PATH && len != 0) {
105 std::string temp_dir_name = temp_path_buffer;
106 if (!absl::EndsWith(temp_dir_name, "\\")) {
107 temp_dir_name.push_back('\\');
108 }
109 absl::StrAppend(&temp_dir_name, "parse_test.", GetCurrentProcessId());
110 if (CreateDirectoryA(temp_dir_name.c_str(), nullptr)) {
111 *res = temp_dir_name;
112 }
113 }
114 #else
115 char temp_dir_template[] = "/tmp/parse_test.XXXXXX";
116 if (auto* unique_name = ::mkdtemp(temp_dir_template)) {
117 *res = unique_name;
118 }
119 #endif
120 }
121
122 if (res->empty()) {
123 ABSL_INTERNAL_LOG(FATAL,
124 "Failed to make temporary directory for data files");
125 }
126
127 #ifdef _WIN32
128 *res += "\\";
129 #else
130 *res += "/";
131 #endif
132
133 return res;
134 }();
135
136 return *temp_dir_name;
137 }
138
139 struct FlagfileData {
140 const absl::string_view file_name;
141 const absl::Span<const char* const> file_lines;
142 };
143
144 // clang-format off
145 constexpr const char* const ff1_data[] = {
146 "# comment ",
147 " # comment ",
148 "",
149 " ",
150 "--int_flag=-1",
151 " --string_flag=q2w2 ",
152 " ## ",
153 " --double_flag=0.1",
154 "--bool_flag=Y "
155 };
156
157 constexpr const char* const ff2_data[] = {
158 "# Setting legacy flag",
159 "--legacy_int=1111",
160 "--legacy_bool",
161 "--nobool_flag",
162 "--legacy_str=aqsw",
163 "--int_flag=100",
164 " ## ============="
165 };
166 // clang-format on
167
168 // Builds flagfile flag in the flagfile_flag buffer and returns it. This
169 // function also creates a temporary flagfile based on FlagfileData input.
170 // We create a flagfile in a temporary directory with the name specified in
171 // FlagfileData and populate it with lines specifed in FlagfileData. If $0 is
172 // referenced in any of the lines in FlagfileData they are replaced with
173 // temporary directory location. This way we can test inclusion of one flagfile
174 // from another flagfile.
GetFlagfileFlag(const std::vector<FlagfileData> & ffd,std::string & flagfile_flag)175 const char* GetFlagfileFlag(const std::vector<FlagfileData>& ffd,
176 std::string& flagfile_flag) {
177 flagfile_flag = "--flagfile=";
178 absl::string_view separator;
179 for (const auto& flagfile_data : ffd) {
180 std::string flagfile_name =
181 absl::StrCat(GetTestTempDir(), flagfile_data.file_name);
182
183 std::ofstream flagfile_out(flagfile_name);
184 for (auto line : flagfile_data.file_lines) {
185 flagfile_out << absl::Substitute(line, GetTestTempDir()) << "\n";
186 }
187
188 absl::StrAppend(&flagfile_flag, separator, flagfile_name);
189 separator = ",";
190 }
191
192 return flagfile_flag.c_str();
193 }
194
195 } // namespace
196
197 ABSL_FLAG(int, int_flag, 1, "");
198 ABSL_FLAG(double, double_flag, 1.1, "");
199 ABSL_FLAG(std::string, string_flag, "a", "");
200 ABSL_FLAG(bool, bool_flag, false, "");
201 ABSL_FLAG(UDT, udt_flag, -1, "");
202 ABSL_RETIRED_FLAG(int, legacy_int, 1, "");
203 ABSL_RETIRED_FLAG(bool, legacy_bool, false, "");
204 ABSL_RETIRED_FLAG(std::string, legacy_str, "l", "");
205
206 namespace {
207
208 namespace flags = absl::flags_internal;
209 using testing::ElementsAreArray;
210
211 class ParseTest : public testing::Test {
212 public:
~ParseTest()213 ~ParseTest() override { flags::SetFlagsHelpMode(flags::HelpMode::kNone); }
214
215 private:
216 absl::FlagSaver flag_saver_;
217 };
218
219 // --------------------------------------------------------------------
220
221 template <int N>
InvokeParse(const char * (& in_argv)[N])222 std::vector<char*> InvokeParse(const char* (&in_argv)[N]) {
223 return absl::ParseCommandLine(N, const_cast<char**>(in_argv));
224 }
225
226 // --------------------------------------------------------------------
227
228 template <int N>
TestParse(const char * (& in_argv)[N],int int_flag_value,double double_flag_val,absl::string_view string_flag_val,bool bool_flag_val,int exp_position_args=0)229 void TestParse(const char* (&in_argv)[N], int int_flag_value,
230 double double_flag_val, absl::string_view string_flag_val,
231 bool bool_flag_val, int exp_position_args = 0) {
232 auto out_args = InvokeParse(in_argv);
233
234 EXPECT_EQ(out_args.size(), 1 + exp_position_args);
235 EXPECT_STREQ(out_args[0], "testbin");
236
237 EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), int_flag_value);
238 EXPECT_NEAR(absl::GetFlag(FLAGS_double_flag), double_flag_val, 0.0001);
239 EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), string_flag_val);
240 EXPECT_EQ(absl::GetFlag(FLAGS_bool_flag), bool_flag_val);
241 }
242
243 // --------------------------------------------------------------------
244
TEST_F(ParseTest,TestEmptyArgv)245 TEST_F(ParseTest, TestEmptyArgv) {
246 const char* in_argv[] = {"testbin"};
247
248 auto out_args = InvokeParse(in_argv);
249
250 EXPECT_EQ(out_args.size(), 1);
251 EXPECT_STREQ(out_args[0], "testbin");
252 }
253
254 // --------------------------------------------------------------------
255
TEST_F(ParseTest,TestValidIntArg)256 TEST_F(ParseTest, TestValidIntArg) {
257 const char* in_args1[] = {
258 "testbin",
259 "--int_flag=10",
260 };
261 TestParse(in_args1, 10, 1.1, "a", false);
262
263 const char* in_args2[] = {
264 "testbin",
265 "-int_flag=020",
266 };
267 TestParse(in_args2, 20, 1.1, "a", false);
268
269 const char* in_args3[] = {
270 "testbin",
271 "--int_flag",
272 "-30",
273 };
274 TestParse(in_args3, -30, 1.1, "a", false);
275
276 const char* in_args4[] = {
277 "testbin",
278 "-int_flag",
279 "0x21",
280 };
281 TestParse(in_args4, 33, 1.1, "a", false);
282 }
283
284 // --------------------------------------------------------------------
285
TEST_F(ParseTest,TestValidDoubleArg)286 TEST_F(ParseTest, TestValidDoubleArg) {
287 const char* in_args1[] = {
288 "testbin",
289 "--double_flag=2.3",
290 };
291 TestParse(in_args1, 1, 2.3, "a", false);
292
293 const char* in_args2[] = {
294 "testbin",
295 "--double_flag=0x1.2",
296 };
297 TestParse(in_args2, 1, 1.125, "a", false);
298
299 const char* in_args3[] = {
300 "testbin",
301 "--double_flag",
302 "99.7",
303 };
304 TestParse(in_args3, 1, 99.7, "a", false);
305
306 const char* in_args4[] = {
307 "testbin",
308 "--double_flag",
309 "0x20.1",
310 };
311 TestParse(in_args4, 1, 32.0625, "a", false);
312 }
313
314 // --------------------------------------------------------------------
315
TEST_F(ParseTest,TestValidStringArg)316 TEST_F(ParseTest, TestValidStringArg) {
317 const char* in_args1[] = {
318 "testbin",
319 "--string_flag=aqswde",
320 };
321 TestParse(in_args1, 1, 1.1, "aqswde", false);
322
323 const char* in_args2[] = {
324 "testbin",
325 "-string_flag=a=b=c",
326 };
327 TestParse(in_args2, 1, 1.1, "a=b=c", false);
328
329 const char* in_args3[] = {
330 "testbin",
331 "--string_flag",
332 "zaxscd",
333 };
334 TestParse(in_args3, 1, 1.1, "zaxscd", false);
335
336 const char* in_args4[] = {
337 "testbin",
338 "-string_flag",
339 "--int_flag",
340 };
341 TestParse(in_args4, 1, 1.1, "--int_flag", false);
342
343 const char* in_args5[] = {
344 "testbin",
345 "--string_flag",
346 "--no_a_flag=11",
347 };
348 TestParse(in_args5, 1, 1.1, "--no_a_flag=11", false);
349 }
350
351 // --------------------------------------------------------------------
352
TEST_F(ParseTest,TestValidBoolArg)353 TEST_F(ParseTest, TestValidBoolArg) {
354 const char* in_args1[] = {
355 "testbin",
356 "--bool_flag",
357 };
358 TestParse(in_args1, 1, 1.1, "a", true);
359
360 const char* in_args2[] = {
361 "testbin",
362 "--nobool_flag",
363 };
364 TestParse(in_args2, 1, 1.1, "a", false);
365
366 const char* in_args3[] = {
367 "testbin",
368 "--bool_flag=true",
369 };
370 TestParse(in_args3, 1, 1.1, "a", true);
371
372 const char* in_args4[] = {
373 "testbin",
374 "-bool_flag=false",
375 };
376 TestParse(in_args4, 1, 1.1, "a", false);
377 }
378
379 // --------------------------------------------------------------------
380
TEST_F(ParseTest,TestValidUDTArg)381 TEST_F(ParseTest, TestValidUDTArg) {
382 const char* in_args1[] = {
383 "testbin",
384 "--udt_flag=A",
385 };
386 InvokeParse(in_args1);
387
388 EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 1);
389
390 const char* in_args2[] = {"testbin", "--udt_flag", "AAA"};
391 InvokeParse(in_args2);
392
393 EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 10);
394 }
395
396 // --------------------------------------------------------------------
397
TEST_F(ParseTest,TestValidMultipleArg)398 TEST_F(ParseTest, TestValidMultipleArg) {
399 const char* in_args1[] = {
400 "testbin", "--bool_flag", "--int_flag=2",
401 "--double_flag=0.1", "--string_flag=asd",
402 };
403 TestParse(in_args1, 2, 0.1, "asd", true);
404
405 const char* in_args2[] = {
406 "testbin", "--string_flag=", "--nobool_flag", "--int_flag",
407 "-011", "--double_flag", "-1e-2",
408 };
409 TestParse(in_args2, -11, -0.01, "", false);
410
411 const char* in_args3[] = {
412 "testbin", "--int_flag", "-0", "--string_flag", "\"\"",
413 "--bool_flag=true", "--double_flag=1e18",
414 };
415 TestParse(in_args3, 0, 1e18, "\"\"", true);
416 }
417
418 // --------------------------------------------------------------------
419
TEST_F(ParseTest,TestPositionalArgs)420 TEST_F(ParseTest, TestPositionalArgs) {
421 const char* in_args1[] = {
422 "testbin",
423 "p1",
424 "p2",
425 };
426 TestParse(in_args1, 1, 1.1, "a", false, 2);
427
428 auto out_args1 = InvokeParse(in_args1);
429
430 EXPECT_STREQ(out_args1[1], "p1");
431 EXPECT_STREQ(out_args1[2], "p2");
432
433 const char* in_args2[] = {
434 "testbin",
435 "--int_flag=2",
436 "p1",
437 };
438 TestParse(in_args2, 2, 1.1, "a", false, 1);
439
440 auto out_args2 = InvokeParse(in_args2);
441
442 EXPECT_STREQ(out_args2[1], "p1");
443
444 const char* in_args3[] = {"testbin", "p1", "--int_flag=3",
445 "p2", "--bool_flag", "true"};
446 TestParse(in_args3, 3, 1.1, "a", true, 3);
447
448 auto out_args3 = InvokeParse(in_args3);
449
450 EXPECT_STREQ(out_args3[1], "p1");
451 EXPECT_STREQ(out_args3[2], "p2");
452 EXPECT_STREQ(out_args3[3], "true");
453
454 const char* in_args4[] = {
455 "testbin",
456 "--",
457 "p1",
458 "p2",
459 };
460 TestParse(in_args4, 3, 1.1, "a", true, 2);
461
462 auto out_args4 = InvokeParse(in_args4);
463
464 EXPECT_STREQ(out_args4[1], "p1");
465 EXPECT_STREQ(out_args4[2], "p2");
466
467 const char* in_args5[] = {
468 "testbin", "p1", "--int_flag=4", "--", "--bool_flag", "false", "p2",
469 };
470 TestParse(in_args5, 4, 1.1, "a", true, 4);
471
472 auto out_args5 = InvokeParse(in_args5);
473
474 EXPECT_STREQ(out_args5[1], "p1");
475 EXPECT_STREQ(out_args5[2], "--bool_flag");
476 EXPECT_STREQ(out_args5[3], "false");
477 EXPECT_STREQ(out_args5[4], "p2");
478 }
479
480 // --------------------------------------------------------------------
481
482 using ParseDeathTest = ParseTest;
483
TEST_F(ParseDeathTest,TestUndefinedArg)484 TEST_F(ParseDeathTest, TestUndefinedArg) {
485 const char* in_args1[] = {
486 "testbin",
487 "--undefined_flag",
488 };
489 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
490 "Unknown command line flag 'undefined_flag'");
491
492 const char* in_args2[] = {
493 "testbin",
494 "--noprefixed_flag",
495 };
496 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2),
497 "Unknown command line flag 'noprefixed_flag'");
498
499 const char* in_args3[] = {
500 "testbin",
501 "--Int_flag=1",
502 };
503 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3),
504 "Unknown command line flag 'Int_flag'");
505 }
506
507 // --------------------------------------------------------------------
508
TEST_F(ParseDeathTest,TestInvalidBoolFlagFormat)509 TEST_F(ParseDeathTest, TestInvalidBoolFlagFormat) {
510 const char* in_args1[] = {
511 "testbin",
512 "--bool_flag=",
513 };
514 EXPECT_DEATH_IF_SUPPORTED(
515 InvokeParse(in_args1),
516 "Missing the value after assignment for the boolean flag 'bool_flag'");
517
518 const char* in_args2[] = {
519 "testbin",
520 "--nobool_flag=true",
521 };
522 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2),
523 "Negative form with assignment is not valid for the boolean "
524 "flag 'bool_flag'");
525 }
526
527 // --------------------------------------------------------------------
528
TEST_F(ParseDeathTest,TestInvalidNonBoolFlagFormat)529 TEST_F(ParseDeathTest, TestInvalidNonBoolFlagFormat) {
530 const char* in_args1[] = {
531 "testbin",
532 "--nostring_flag",
533 };
534 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
535 "Negative form is not valid for the flag 'string_flag'");
536
537 const char* in_args2[] = {
538 "testbin",
539 "--int_flag",
540 };
541 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2),
542 "Missing the value for the flag 'int_flag'");
543 }
544
545 // --------------------------------------------------------------------
546
TEST_F(ParseDeathTest,TestInvalidUDTFlagFormat)547 TEST_F(ParseDeathTest, TestInvalidUDTFlagFormat) {
548 const char* in_args1[] = {
549 "testbin",
550 "--udt_flag=1",
551 };
552 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
553 "Illegal value '1' specified for flag 'udt_flag'; Use values A, "
554 "AAA instead");
555
556 const char* in_args2[] = {
557 "testbin",
558 "--udt_flag",
559 "AA",
560 };
561 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2),
562 "Illegal value 'AA' specified for flag 'udt_flag'; Use values "
563 "A, AAA instead");
564 }
565
566 // --------------------------------------------------------------------
567
TEST_F(ParseTest,TestLegacyFlags)568 TEST_F(ParseTest, TestLegacyFlags) {
569 const char* in_args1[] = {
570 "testbin",
571 "--legacy_int=11",
572 };
573 TestParse(in_args1, 1, 1.1, "a", false);
574
575 const char* in_args2[] = {
576 "testbin",
577 "--legacy_bool",
578 };
579 TestParse(in_args2, 1, 1.1, "a", false);
580
581 const char* in_args3[] = {
582 "testbin", "--legacy_int", "22", "--int_flag=2",
583 "--legacy_bool", "true", "--legacy_str", "--string_flag=qwe",
584 };
585 TestParse(in_args3, 2, 1.1, "a", false, 1);
586 }
587
588 // --------------------------------------------------------------------
589
TEST_F(ParseTest,TestSimpleValidFlagfile)590 TEST_F(ParseTest, TestSimpleValidFlagfile) {
591 std::string flagfile_flag;
592
593 const char* in_args1[] = {
594 "testbin",
595 GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}},
596 flagfile_flag),
597 };
598 TestParse(in_args1, -1, 0.1, "q2w2 ", true);
599
600 const char* in_args2[] = {
601 "testbin",
602 GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}},
603 flagfile_flag),
604 };
605 TestParse(in_args2, 100, 0.1, "q2w2 ", false);
606 }
607
608 // --------------------------------------------------------------------
609
TEST_F(ParseTest,TestValidMultiFlagfile)610 TEST_F(ParseTest, TestValidMultiFlagfile) {
611 std::string flagfile_flag;
612
613 const char* in_args1[] = {
614 "testbin",
615 GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)},
616 {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}},
617 flagfile_flag),
618 };
619 TestParse(in_args1, -1, 0.1, "q2w2 ", true);
620 }
621
622 // --------------------------------------------------------------------
623
TEST_F(ParseTest,TestFlagfileMixedWithRegularFlags)624 TEST_F(ParseTest, TestFlagfileMixedWithRegularFlags) {
625 std::string flagfile_flag;
626
627 const char* in_args1[] = {
628 "testbin", "--int_flag=3",
629 GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}},
630 flagfile_flag),
631 "-double_flag=0.2"};
632 TestParse(in_args1, -1, 0.2, "q2w2 ", true);
633 }
634
635 // --------------------------------------------------------------------
636
TEST_F(ParseTest,TestFlagfileInFlagfile)637 TEST_F(ParseTest, TestFlagfileInFlagfile) {
638 std::string flagfile_flag;
639
640 constexpr const char* const ff3_data[] = {
641 "--flagfile=$0/parse_test.ff1",
642 "--flagfile=$0/parse_test.ff2",
643 };
644
645 GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)},
646 {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}},
647 flagfile_flag);
648
649 const char* in_args1[] = {
650 "testbin",
651 GetFlagfileFlag({{"parse_test.ff3", absl::MakeConstSpan(ff3_data)}},
652 flagfile_flag),
653 };
654 TestParse(in_args1, 100, 0.1, "q2w2 ", false);
655 }
656
657 // --------------------------------------------------------------------
658
TEST_F(ParseDeathTest,TestInvalidFlagfiles)659 TEST_F(ParseDeathTest, TestInvalidFlagfiles) {
660 std::string flagfile_flag;
661
662 constexpr const char* const ff4_data[] = {
663 "--unknown_flag=10"
664 };
665
666 const char* in_args1[] = {
667 "testbin",
668 GetFlagfileFlag({{"parse_test.ff4",
669 absl::MakeConstSpan(ff4_data)}}, flagfile_flag),
670 };
671 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
672 "Unknown command line flag 'unknown_flag'");
673
674 constexpr const char* const ff5_data[] = {
675 "--int_flag 10",
676 };
677
678 const char* in_args2[] = {
679 "testbin",
680 GetFlagfileFlag({{"parse_test.ff5",
681 absl::MakeConstSpan(ff5_data)}}, flagfile_flag),
682 };
683 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2),
684 "Unknown command line flag 'int_flag 10'");
685
686 constexpr const char* const ff6_data[] = {
687 "--int_flag=10", "--", "arg1", "arg2", "arg3",
688 };
689
690 const char* in_args3[] = {
691 "testbin",
692 GetFlagfileFlag({{"parse_test.ff6", absl::MakeConstSpan(ff6_data)}},
693 flagfile_flag),
694 };
695 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3),
696 "Flagfile can't contain position arguments or --");
697
698 const char* in_args4[] = {
699 "testbin",
700 "--flagfile=invalid_flag_file",
701 };
702 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args4),
703 "Can't open flagfile invalid_flag_file");
704
705 constexpr const char* const ff7_data[] = {
706 "--int_flag=10",
707 "*bin*",
708 "--str_flag=aqsw",
709 };
710
711 const char* in_args5[] = {
712 "testbin",
713 GetFlagfileFlag({{"parse_test.ff7", absl::MakeConstSpan(ff7_data)}},
714 flagfile_flag),
715 };
716 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args5),
717 "Unexpected line in the flagfile .*: \\*bin\\*");
718 }
719
720 // --------------------------------------------------------------------
721
TEST_F(ParseTest,TestReadingRequiredFlagsFromEnv)722 TEST_F(ParseTest, TestReadingRequiredFlagsFromEnv) {
723 const char* in_args1[] = {"testbin",
724 "--fromenv=int_flag,bool_flag,string_flag"};
725
726 ScopedSetEnv set_int_flag("FLAGS_int_flag", "33");
727 ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "True");
728 ScopedSetEnv set_string_flag("FLAGS_string_flag", "AQ12");
729
730 TestParse(in_args1, 33, 1.1, "AQ12", true);
731 }
732
733 // --------------------------------------------------------------------
734
TEST_F(ParseDeathTest,TestReadingUnsetRequiredFlagsFromEnv)735 TEST_F(ParseDeathTest, TestReadingUnsetRequiredFlagsFromEnv) {
736 const char* in_args1[] = {"testbin", "--fromenv=int_flag"};
737
738 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
739 "FLAGS_int_flag not found in environment");
740 }
741
742 // --------------------------------------------------------------------
743
TEST_F(ParseDeathTest,TestRecursiveFlagsFromEnv)744 TEST_F(ParseDeathTest, TestRecursiveFlagsFromEnv) {
745 const char* in_args1[] = {"testbin", "--fromenv=tryfromenv"};
746
747 ScopedSetEnv set_tryfromenv("FLAGS_tryfromenv", "int_flag");
748
749 EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1),
750 "Infinite recursion on flag tryfromenv");
751 }
752
753 // --------------------------------------------------------------------
754
TEST_F(ParseTest,TestReadingOptionalFlagsFromEnv)755 TEST_F(ParseTest, TestReadingOptionalFlagsFromEnv) {
756 const char* in_args1[] = {
757 "testbin", "--tryfromenv=int_flag,bool_flag,string_flag,other_flag"};
758
759 ScopedSetEnv set_int_flag("FLAGS_int_flag", "17");
760 ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "Y");
761
762 TestParse(in_args1, 17, 1.1, "a", true);
763 }
764
765 // --------------------------------------------------------------------
766
TEST_F(ParseTest,TestReadingFlagsFromEnvMoxedWithRegularFlags)767 TEST_F(ParseTest, TestReadingFlagsFromEnvMoxedWithRegularFlags) {
768 const char* in_args1[] = {
769 "testbin",
770 "--bool_flag=T",
771 "--tryfromenv=int_flag,bool_flag",
772 "--int_flag=-21",
773 };
774
775 ScopedSetEnv set_int_flag("FLAGS_int_flag", "-15");
776 ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "F");
777
778 TestParse(in_args1, -21, 1.1, "a", false);
779 }
780
781 // --------------------------------------------------------------------
782
TEST_F(ParseTest,TestKeepParsedArgs)783 TEST_F(ParseTest, TestKeepParsedArgs) {
784 const char* in_args1[] = {
785 "testbin", "arg1", "--bool_flag",
786 "--int_flag=211", "arg2", "--double_flag=1.1",
787 "--string_flag", "asd", "--",
788 "arg3", "arg4",
789 };
790
791 auto out_args1 = InvokeParse(in_args1);
792
793 EXPECT_THAT(
794 out_args1,
795 ElementsAreArray({absl::string_view("testbin"), absl::string_view("arg1"),
796 absl::string_view("arg2"), absl::string_view("arg3"),
797 absl::string_view("arg4")}));
798
799 auto out_args2 = flags::ParseCommandLineImpl(
800 11, const_cast<char**>(in_args1), flags::ArgvListAction::kKeepParsedArgs,
801 flags::UsageFlagsAction::kHandleUsage,
802 flags::OnUndefinedFlag::kAbortIfUndefined);
803
804 EXPECT_THAT(
805 out_args2,
806 ElementsAreArray({absl::string_view("testbin"),
807 absl::string_view("--bool_flag"),
808 absl::string_view("--int_flag=211"),
809 absl::string_view("--double_flag=1.1"),
810 absl::string_view("--string_flag"),
811 absl::string_view("asd"), absl::string_view("--"),
812 absl::string_view("arg1"), absl::string_view("arg2"),
813 absl::string_view("arg3"), absl::string_view("arg4")}));
814 }
815
816 // --------------------------------------------------------------------
817
TEST_F(ParseTest,TestIgnoreUndefinedFlags)818 TEST_F(ParseTest, TestIgnoreUndefinedFlags) {
819 const char* in_args1[] = {
820 "testbin",
821 "arg1",
822 "--undef_flag=aa",
823 "--int_flag=21",
824 };
825
826 auto out_args1 = flags::ParseCommandLineImpl(
827 4, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs,
828 flags::UsageFlagsAction::kHandleUsage,
829 flags::OnUndefinedFlag::kIgnoreUndefined);
830
831 EXPECT_THAT(out_args1, ElementsAreArray({absl::string_view("testbin"),
832 absl::string_view("arg1")}));
833
834 EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 21);
835
836 const char* in_args2[] = {
837 "testbin",
838 "arg1",
839 "--undef_flag=aa",
840 "--string_flag=AA",
841 };
842
843 auto out_args2 = flags::ParseCommandLineImpl(
844 4, const_cast<char**>(in_args2), flags::ArgvListAction::kKeepParsedArgs,
845 flags::UsageFlagsAction::kHandleUsage,
846 flags::OnUndefinedFlag::kIgnoreUndefined);
847
848 EXPECT_THAT(
849 out_args2,
850 ElementsAreArray(
851 {absl::string_view("testbin"), absl::string_view("--undef_flag=aa"),
852 absl::string_view("--string_flag=AA"), absl::string_view("arg1")}));
853
854 EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "AA");
855 }
856
857 // --------------------------------------------------------------------
858
TEST_F(ParseDeathTest,TestSimpleHelpFlagHandling)859 TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) {
860 const char* in_args1[] = {
861 "testbin",
862 "--help",
863 };
864
865 EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), "");
866
867 const char* in_args2[] = {
868 "testbin",
869 "--help",
870 "--int_flag=3",
871 };
872
873 auto out_args2 = flags::ParseCommandLineImpl(
874 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs,
875 flags::UsageFlagsAction::kIgnoreUsage,
876 flags::OnUndefinedFlag::kAbortIfUndefined);
877
878 EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant);
879 EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3);
880 }
881
882 // --------------------------------------------------------------------
883
TEST_F(ParseDeathTest,TestSubstringHelpFlagHandling)884 TEST_F(ParseDeathTest, TestSubstringHelpFlagHandling) {
885 const char* in_args1[] = {
886 "testbin",
887 "--help=abcd",
888 };
889
890 auto out_args1 = flags::ParseCommandLineImpl(
891 2, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs,
892 flags::UsageFlagsAction::kIgnoreUsage,
893 flags::OnUndefinedFlag::kAbortIfUndefined);
894
895 EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kMatch);
896 EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd");
897
898 const char* in_args2[] = {"testbin", "--help", "some_positional_arg"};
899
900 auto out_args2 = flags::ParseCommandLineImpl(
901 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs,
902 flags::UsageFlagsAction::kIgnoreUsage,
903 flags::OnUndefinedFlag::kAbortIfUndefined);
904
905 EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant);
906 }
907
908 // --------------------------------------------------------------------
909
TEST_F(ParseTest,WasPresentOnCommandLine)910 TEST_F(ParseTest, WasPresentOnCommandLine) {
911 const char* in_args1[] = {
912 "testbin", "arg1", "--bool_flag",
913 "--int_flag=211", "arg2", "--double_flag=1.1",
914 "--string_flag", "asd", "--",
915 "--some_flag", "arg4",
916 };
917
918 InvokeParse(in_args1);
919
920 EXPECT_TRUE(flags::WasPresentOnCommandLine("bool_flag"));
921 EXPECT_TRUE(flags::WasPresentOnCommandLine("int_flag"));
922 EXPECT_TRUE(flags::WasPresentOnCommandLine("double_flag"));
923 EXPECT_TRUE(flags::WasPresentOnCommandLine("string_flag"));
924 EXPECT_FALSE(flags::WasPresentOnCommandLine("some_flag"));
925 EXPECT_FALSE(flags::WasPresentOnCommandLine("another_flag"));
926 }
927
928 // --------------------------------------------------------------------
929
930 } // namespace
931