| // | |
| // Copyright 2019 The Abseil Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "absl/flags/parse.h" | |
| #include <stdlib.h> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/base/internal/scoped_set_env.h" | |
| #include "absl/flags/config.h" | |
| #include "absl/flags/flag.h" | |
| #include "absl/flags/internal/parse.h" | |
| #include "absl/flags/internal/usage.h" | |
| #include "absl/flags/reflection.h" | |
| #include "absl/log/log.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/substitute.h" | |
| #include "absl/types/span.h" | |
| #ifdef _WIN32 | |
| #include <windows.h> | |
| #endif | |
| // Define 125 similar flags to test kMaxHints for flag suggestions. | |
| #define FLAG_MULT(x) F3(x) | |
| #define TEST_FLAG_HEADER FLAG_HEADER_ | |
| #define F(name) ABSL_FLAG(int, name, 0, "") | |
| #define F1(name) \ | |
| F(name##1); \ | |
| F(name##2); \ | |
| F(name##3); \ | |
| F(name##4); \ | |
| F(name##5) | |
| /**/ | |
| #define F2(name) \ | |
| F1(name##1); \ | |
| F1(name##2); \ | |
| F1(name##3); \ | |
| F1(name##4); \ | |
| F1(name##5) | |
| /**/ | |
| #define F3(name) \ | |
| F2(name##1); \ | |
| F2(name##2); \ | |
| F2(name##3); \ | |
| F2(name##4); \ | |
| F2(name##5) | |
| /**/ | |
| FLAG_MULT(TEST_FLAG_HEADER); | |
| namespace { | |
| using absl::base_internal::ScopedSetEnv; | |
| struct UDT { | |
| UDT() = default; | |
| UDT(const UDT&) = default; | |
| UDT& operator=(const UDT&) = default; | |
| UDT(int v) : value(v) {} // NOLINT | |
| int value; | |
| }; | |
| bool AbslParseFlag(absl::string_view in, UDT* udt, std::string* err) { | |
| if (in == "A") { | |
| udt->value = 1; | |
| return true; | |
| } | |
| if (in == "AAA") { | |
| udt->value = 10; | |
| return true; | |
| } | |
| *err = "Use values A, AAA instead"; | |
| return false; | |
| } | |
| std::string AbslUnparseFlag(const UDT& udt) { | |
| return udt.value == 1 ? "A" : "AAA"; | |
| } | |
| std::string GetTestTmpDirEnvVar(const char* const env_var_name) { | |
| #ifdef _WIN32 | |
| char buf[MAX_PATH]; | |
| auto get_res = GetEnvironmentVariableA(env_var_name, buf, sizeof(buf)); | |
| if (get_res >= sizeof(buf) || get_res == 0) { | |
| return ""; | |
| } | |
| return std::string(buf, get_res); | |
| #else | |
| const char* val = ::getenv(env_var_name); | |
| if (val == nullptr) { | |
| return ""; | |
| } | |
| return val; | |
| #endif | |
| } | |
| const std::string& GetTestTempDir() { | |
| static std::string* temp_dir_name = []() -> std::string* { | |
| std::string* res = new std::string(GetTestTmpDirEnvVar("TEST_TMPDIR")); | |
| if (res->empty()) { | |
| *res = GetTestTmpDirEnvVar("TMPDIR"); | |
| } | |
| if (res->empty()) { | |
| #ifdef _WIN32 | |
| char temp_path_buffer[MAX_PATH]; | |
| auto len = GetTempPathA(MAX_PATH, temp_path_buffer); | |
| if (len < MAX_PATH && len != 0) { | |
| std::string temp_dir_name = temp_path_buffer; | |
| if (!absl::EndsWith(temp_dir_name, "\\")) { | |
| temp_dir_name.push_back('\\'); | |
| } | |
| absl::StrAppend(&temp_dir_name, "parse_test.", GetCurrentProcessId()); | |
| if (CreateDirectoryA(temp_dir_name.c_str(), nullptr)) { | |
| *res = temp_dir_name; | |
| } | |
| } | |
| #else | |
| char temp_dir_template[] = "/tmp/parse_test.XXXXXX"; | |
| if (auto* unique_name = ::mkdtemp(temp_dir_template)) { | |
| *res = unique_name; | |
| } | |
| #endif | |
| } | |
| if (res->empty()) { | |
| LOG(FATAL) << "Failed to make temporary directory for data files"; | |
| } | |
| #ifdef _WIN32 | |
| *res += "\\"; | |
| #else | |
| *res += "/"; | |
| #endif | |
| return res; | |
| }(); | |
| return *temp_dir_name; | |
| } | |
| struct FlagfileData { | |
| const absl::string_view file_name; | |
| const absl::Span<const char* const> file_lines; | |
| }; | |
| // clang-format off | |
| constexpr const char* const ff1_data[] = { | |
| "# comment ", | |
| " # comment ", | |
| "", | |
| " ", | |
| "--int_flag=-1", | |
| " --string_flag=q2w2 ", | |
| " ## ", | |
| " --double_flag=0.1", | |
| "--bool_flag=Y " | |
| }; | |
| constexpr const char* const ff2_data[] = { | |
| "# Setting legacy flag", | |
| "--legacy_int=1111", | |
| "--legacy_bool", | |
| "--nobool_flag", | |
| "--legacy_str=aqsw", | |
| "--int_flag=100", | |
| " ## =============" | |
| }; | |
| // clang-format on | |
| // Builds flagfile flag in the flagfile_flag buffer and returns it. This | |
| // function also creates a temporary flagfile based on FlagfileData input. | |
| // We create a flagfile in a temporary directory with the name specified in | |
| // FlagfileData and populate it with lines specified in FlagfileData. If $0 is | |
| // referenced in any of the lines in FlagfileData they are replaced with | |
| // temporary directory location. This way we can test inclusion of one flagfile | |
| // from another flagfile. | |
| const char* GetFlagfileFlag(const std::vector<FlagfileData>& ffd, | |
| std::string& flagfile_flag) { | |
| flagfile_flag = "--flagfile="; | |
| absl::string_view separator; | |
| for (const auto& flagfile_data : ffd) { | |
| std::string flagfile_name = | |
| absl::StrCat(GetTestTempDir(), flagfile_data.file_name); | |
| std::ofstream flagfile_out(flagfile_name); | |
| for (auto line : flagfile_data.file_lines) { | |
| flagfile_out << absl::Substitute(line, GetTestTempDir()) << "\n"; | |
| } | |
| absl::StrAppend(&flagfile_flag, separator, flagfile_name); | |
| separator = ","; | |
| } | |
| return flagfile_flag.c_str(); | |
| } | |
| } // namespace | |
| ABSL_FLAG(int, int_flag, 1, ""); | |
| ABSL_FLAG(double, double_flag, 1.1, ""); | |
| ABSL_FLAG(std::string, string_flag, "a", ""); | |
| ABSL_FLAG(bool, bool_flag, false, ""); | |
| ABSL_FLAG(UDT, udt_flag, -1, ""); | |
| ABSL_RETIRED_FLAG(int, legacy_int, 1, ""); | |
| ABSL_RETIRED_FLAG(bool, legacy_bool, false, ""); | |
| ABSL_RETIRED_FLAG(std::string, legacy_str, "l", ""); | |
| namespace { | |
| namespace flags = absl::flags_internal; | |
| using testing::AllOf; | |
| using testing::ElementsAreArray; | |
| using testing::HasSubstr; | |
| class ParseTest : public testing::Test { | |
| public: | |
| ~ParseTest() override { flags::SetFlagsHelpMode(flags::HelpMode::kNone); } | |
| void SetUp() override { | |
| #if ABSL_FLAGS_STRIP_NAMES | |
| GTEST_SKIP() << "This test requires flag names to be present"; | |
| #endif | |
| } | |
| private: | |
| absl::FlagSaver flag_saver_; | |
| }; | |
| // -------------------------------------------------------------------- | |
| template <int N> | |
| flags::HelpMode InvokeParseAbslOnlyImpl(const char* (&in_argv)[N]) { | |
| std::vector<char*> positional_args; | |
| std::vector<absl::UnrecognizedFlag> unrecognized_flags; | |
| return flags::ParseAbseilFlagsOnlyImpl(N, const_cast<char**>(in_argv), | |
| positional_args, unrecognized_flags, | |
| flags::UsageFlagsAction::kHandleUsage); | |
| } | |
| // -------------------------------------------------------------------- | |
| template <int N> | |
| void InvokeParseAbslOnly(const char* (&in_argv)[N]) { | |
| std::vector<char*> positional_args; | |
| std::vector<absl::UnrecognizedFlag> unrecognized_flags; | |
| absl::ParseAbseilFlagsOnly(2, const_cast<char**>(in_argv), positional_args, | |
| unrecognized_flags); | |
| } | |
| // -------------------------------------------------------------------- | |
| template <int N> | |
| std::vector<char*> InvokeParseCommandLineImpl(const char* (&in_argv)[N]) { | |
| return flags::ParseCommandLineImpl( | |
| N, const_cast<char**>(in_argv), flags::UsageFlagsAction::kHandleUsage, | |
| flags::OnUndefinedFlag::kAbortIfUndefined, std::cerr); | |
| } | |
| // -------------------------------------------------------------------- | |
| template <int N> | |
| std::vector<char*> InvokeParse(const char* (&in_argv)[N]) { | |
| return absl::ParseCommandLine(N, const_cast<char**>(in_argv)); | |
| } | |
| // -------------------------------------------------------------------- | |
| template <int N> | |
| void 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) { | |
| auto out_args = InvokeParse(in_argv); | |
| EXPECT_EQ(out_args.size(), 1 + exp_position_args); | |
| EXPECT_STREQ(out_args[0], "testbin"); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), int_flag_value); | |
| EXPECT_NEAR(absl::GetFlag(FLAGS_double_flag), double_flag_val, 0.0001); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), string_flag_val); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_bool_flag), bool_flag_val); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestEmptyArgv) { | |
| const char* in_argv[] = {"testbin"}; | |
| auto out_args = InvokeParse(in_argv); | |
| EXPECT_EQ(out_args.size(), 1); | |
| EXPECT_STREQ(out_args[0], "testbin"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidIntArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--int_flag=10", | |
| }; | |
| TestParse(in_args1, 10, 1.1, "a", false); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "-int_flag=020", | |
| }; | |
| TestParse(in_args2, 20, 1.1, "a", false); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--int_flag", | |
| "-30", | |
| }; | |
| TestParse(in_args3, -30, 1.1, "a", false); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "-int_flag", | |
| "0x21", | |
| }; | |
| TestParse(in_args4, 33, 1.1, "a", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidDoubleArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--double_flag=2.3", | |
| }; | |
| TestParse(in_args1, 1, 2.3, "a", false); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--double_flag=0x1.2", | |
| }; | |
| TestParse(in_args2, 1, 1.125, "a", false); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--double_flag", | |
| "99.7", | |
| }; | |
| TestParse(in_args3, 1, 99.7, "a", false); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "--double_flag", | |
| "0x20.1", | |
| }; | |
| TestParse(in_args4, 1, 32.0625, "a", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidStringArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--string_flag=aqswde", | |
| }; | |
| TestParse(in_args1, 1, 1.1, "aqswde", false); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "-string_flag=a=b=c", | |
| }; | |
| TestParse(in_args2, 1, 1.1, "a=b=c", false); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--string_flag", | |
| "zaxscd", | |
| }; | |
| TestParse(in_args3, 1, 1.1, "zaxscd", false); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "-string_flag", | |
| "--int_flag", | |
| }; | |
| TestParse(in_args4, 1, 1.1, "--int_flag", false); | |
| const char* in_args5[] = { | |
| "testbin", | |
| "--string_flag", | |
| "--no_a_flag=11", | |
| }; | |
| TestParse(in_args5, 1, 1.1, "--no_a_flag=11", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidBoolArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--bool_flag", | |
| }; | |
| TestParse(in_args1, 1, 1.1, "a", true); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--nobool_flag", | |
| }; | |
| TestParse(in_args2, 1, 1.1, "a", false); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--bool_flag=true", | |
| }; | |
| TestParse(in_args3, 1, 1.1, "a", true); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "-bool_flag=false", | |
| }; | |
| TestParse(in_args4, 1, 1.1, "a", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidUDTArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--udt_flag=A", | |
| }; | |
| InvokeParse(in_args1); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 1); | |
| const char* in_args2[] = {"testbin", "--udt_flag", "AAA"}; | |
| InvokeParse(in_args2); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 10); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidMultipleArg) { | |
| const char* in_args1[] = { | |
| "testbin", "--bool_flag", "--int_flag=2", | |
| "--double_flag=0.1", "--string_flag=asd", | |
| }; | |
| TestParse(in_args1, 2, 0.1, "asd", true); | |
| const char* in_args2[] = { | |
| "testbin", "--string_flag=", "--nobool_flag", "--int_flag", | |
| "-011", "--double_flag", "-1e-2", | |
| }; | |
| TestParse(in_args2, -11, -0.01, "", false); | |
| const char* in_args3[] = { | |
| "testbin", "--int_flag", "-0", "--string_flag", "\"\"", | |
| "--bool_flag=true", "--double_flag=1e18", | |
| }; | |
| TestParse(in_args3, 0, 1e18, "\"\"", true); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestPositionalArgs) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "p1", | |
| "p2", | |
| }; | |
| TestParse(in_args1, 1, 1.1, "a", false, 2); | |
| auto out_args1 = InvokeParse(in_args1); | |
| EXPECT_STREQ(out_args1[1], "p1"); | |
| EXPECT_STREQ(out_args1[2], "p2"); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--int_flag=2", | |
| "p1", | |
| }; | |
| TestParse(in_args2, 2, 1.1, "a", false, 1); | |
| auto out_args2 = InvokeParse(in_args2); | |
| EXPECT_STREQ(out_args2[1], "p1"); | |
| const char* in_args3[] = {"testbin", "p1", "--int_flag=3", | |
| "p2", "--bool_flag", "true"}; | |
| TestParse(in_args3, 3, 1.1, "a", true, 3); | |
| auto out_args3 = InvokeParse(in_args3); | |
| EXPECT_STREQ(out_args3[1], "p1"); | |
| EXPECT_STREQ(out_args3[2], "p2"); | |
| EXPECT_STREQ(out_args3[3], "true"); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "--", | |
| "p1", | |
| "p2", | |
| }; | |
| TestParse(in_args4, 3, 1.1, "a", true, 2); | |
| auto out_args4 = InvokeParse(in_args4); | |
| EXPECT_STREQ(out_args4[1], "p1"); | |
| EXPECT_STREQ(out_args4[2], "p2"); | |
| const char* in_args5[] = { | |
| "testbin", "p1", "--int_flag=4", "--", "--bool_flag", "false", "p2", | |
| }; | |
| TestParse(in_args5, 4, 1.1, "a", true, 4); | |
| auto out_args5 = InvokeParse(in_args5); | |
| EXPECT_STREQ(out_args5[1], "p1"); | |
| EXPECT_STREQ(out_args5[2], "--bool_flag"); | |
| EXPECT_STREQ(out_args5[3], "false"); | |
| EXPECT_STREQ(out_args5[4], "p2"); | |
| } | |
| // -------------------------------------------------------------------- | |
| using ParseDeathTest = ParseTest; | |
| TEST_F(ParseDeathTest, TestUndefinedArg) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--undefined_flag", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "Unknown command line flag 'undefined_flag'"); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--noprefixed_flag", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), | |
| "Unknown command line flag 'noprefixed_flag'"); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--Int_flag=1", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3), | |
| "Unknown command line flag 'Int_flag'"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestInvalidBoolFlagFormat) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--bool_flag=", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED( | |
| InvokeParse(in_args1), | |
| "Missing the value after assignment for the boolean flag 'bool_flag'"); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--nobool_flag=true", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), | |
| "Negative form with assignment is not valid for the boolean " | |
| "flag 'bool_flag'"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestInvalidNonBoolFlagFormat) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--nostring_flag", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "Negative form is not valid for the flag 'string_flag'"); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--int_flag", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), | |
| "Missing the value for the flag 'int_flag'"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestInvalidUDTFlagFormat) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--udt_flag=1", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "Illegal value '1' specified for flag 'udt_flag'; Use values A, " | |
| "AAA instead"); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--udt_flag", | |
| "AA", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), | |
| "Illegal value 'AA' specified for flag 'udt_flag'; Use values " | |
| "A, AAA instead"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestFlagSuggestions) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--legacy_boo", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED( | |
| InvokeParse(in_args1), | |
| "Unknown command line flag 'legacy_boo'. Did you mean: legacy_bool ?"); | |
| const char* in_args2[] = {"testbin", "--foo", "--undefok=foo1"}; | |
| EXPECT_DEATH_IF_SUPPORTED( | |
| InvokeParse(in_args2), | |
| "Unknown command line flag 'foo'. Did you mean: foo1 \\(undefok\\)?"); | |
| const char* in_args3[] = { | |
| "testbin", | |
| "--nolegacy_ino", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3), | |
| "Unknown command line flag 'nolegacy_ino'. Did " | |
| "you mean: nolegacy_bool, legacy_int ?"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, GetHints) { | |
| EXPECT_THAT(absl::flags_internal::GetMisspellingHints("legacy_boo"), | |
| testing::ContainerEq(std::vector<std::string>{"legacy_bool"})); | |
| EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_itn"), | |
| testing::ContainerEq(std::vector<std::string>{"legacy_int"})); | |
| EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int1"), | |
| testing::ContainerEq(std::vector<std::string>{"legacy_int"})); | |
| EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int"), | |
| testing::ContainerEq(std::vector<std::string>{"legacy_int"})); | |
| EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_ino"), | |
| testing::ContainerEq( | |
| std::vector<std::string>{"nolegacy_bool", "legacy_int"})); | |
| EXPECT_THAT( | |
| absl::flags_internal::GetMisspellingHints("FLAG_HEADER_000").size(), 100); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestLegacyFlags) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--legacy_int=11", | |
| }; | |
| TestParse(in_args1, 1, 1.1, "a", false); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--legacy_bool", | |
| }; | |
| TestParse(in_args2, 1, 1.1, "a", false); | |
| const char* in_args3[] = { | |
| "testbin", "--legacy_int", "22", "--int_flag=2", | |
| "--legacy_bool", "true", "--legacy_str", "--string_flag=qwe", | |
| }; | |
| TestParse(in_args3, 2, 1.1, "a", false, 1); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestSimpleValidFlagfile) { | |
| std::string flagfile_flag; | |
| const char* in_args1[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, | |
| flagfile_flag), | |
| }; | |
| TestParse(in_args1, -1, 0.1, "q2w2 ", true); | |
| const char* in_args2[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}}, | |
| flagfile_flag), | |
| }; | |
| TestParse(in_args2, 100, 0.1, "q2w2 ", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestValidMultiFlagfile) { | |
| std::string flagfile_flag; | |
| const char* in_args1[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}, | |
| {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, | |
| flagfile_flag), | |
| }; | |
| TestParse(in_args1, -1, 0.1, "q2w2 ", true); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestFlagfileMixedWithRegularFlags) { | |
| std::string flagfile_flag; | |
| const char* in_args1[] = { | |
| "testbin", "--int_flag=3", | |
| GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, | |
| flagfile_flag), | |
| "-double_flag=0.2"}; | |
| TestParse(in_args1, -1, 0.2, "q2w2 ", true); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestFlagfileInFlagfile) { | |
| std::string flagfile_flag; | |
| constexpr const char* const ff3_data[] = { | |
| "--flagfile=$0/parse_test.ff1", | |
| "--flagfile=$0/parse_test.ff2", | |
| }; | |
| GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}, | |
| {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, | |
| flagfile_flag); | |
| const char* in_args1[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff3", absl::MakeConstSpan(ff3_data)}}, | |
| flagfile_flag), | |
| }; | |
| TestParse(in_args1, 100, 0.1, "q2w2 ", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestInvalidFlagfiles) { | |
| std::string flagfile_flag; | |
| constexpr const char* const ff4_data[] = { | |
| "--unknown_flag=10" | |
| }; | |
| const char* in_args1[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff4", | |
| absl::MakeConstSpan(ff4_data)}}, flagfile_flag), | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "Unknown command line flag 'unknown_flag'"); | |
| constexpr const char* const ff5_data[] = { | |
| "--int_flag 10", | |
| }; | |
| const char* in_args2[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff5", | |
| absl::MakeConstSpan(ff5_data)}}, flagfile_flag), | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), | |
| "Unknown command line flag 'int_flag 10'"); | |
| constexpr const char* const ff6_data[] = { | |
| "--int_flag=10", "--", "arg1", "arg2", "arg3", | |
| }; | |
| const char* in_args3[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff6", absl::MakeConstSpan(ff6_data)}}, | |
| flagfile_flag), | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3), | |
| "Flagfile can't contain position arguments or --"); | |
| const char* in_args4[] = { | |
| "testbin", | |
| "--flagfile=invalid_flag_file", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args4), | |
| "Can't open flagfile invalid_flag_file"); | |
| constexpr const char* const ff7_data[] = { | |
| "--int_flag=10", | |
| "*bin*", | |
| "--str_flag=aqsw", | |
| }; | |
| const char* in_args5[] = { | |
| "testbin", | |
| GetFlagfileFlag({{"parse_test.ff7", absl::MakeConstSpan(ff7_data)}}, | |
| flagfile_flag), | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args5), | |
| "Unexpected line 2 in the flagfile .*"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestReadingRequiredFlagsFromEnv) { | |
| const char* in_args1[] = {"testbin", | |
| "--fromenv=int_flag,bool_flag,string_flag"}; | |
| ScopedSetEnv set_int_flag("FLAGS_int_flag", "33"); | |
| ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "True"); | |
| ScopedSetEnv set_string_flag("FLAGS_string_flag", "AQ12"); | |
| TestParse(in_args1, 33, 1.1, "AQ12", true); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestReadingUnsetRequiredFlagsFromEnv) { | |
| const char* in_args1[] = {"testbin", "--fromenv=int_flag"}; | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "FLAGS_int_flag not found in environment"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestRecursiveFlagsFromEnv) { | |
| const char* in_args1[] = {"testbin", "--fromenv=tryfromenv"}; | |
| ScopedSetEnv set_tryfromenv("FLAGS_tryfromenv", "int_flag"); | |
| EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), | |
| "Infinite recursion on flag tryfromenv"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestReadingOptionalFlagsFromEnv) { | |
| const char* in_args1[] = { | |
| "testbin", "--tryfromenv=int_flag,bool_flag,string_flag,other_flag"}; | |
| ScopedSetEnv set_int_flag("FLAGS_int_flag", "17"); | |
| ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "Y"); | |
| TestParse(in_args1, 17, 1.1, "a", true); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestReadingFlagsFromEnvMoxedWithRegularFlags) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--bool_flag=T", | |
| "--tryfromenv=int_flag,bool_flag", | |
| "--int_flag=-21", | |
| }; | |
| ScopedSetEnv set_int_flag("FLAGS_int_flag", "-15"); | |
| ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "F"); | |
| TestParse(in_args1, -21, 1.1, "a", false); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--help", | |
| }; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kImportant); | |
| EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); | |
| const char* in_args2[] = { | |
| "testbin", | |
| "--help", | |
| "--int_flag=3", | |
| }; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kImportant); | |
| EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); | |
| const char* in_args3[] = {"testbin", "--help", "some_positional_arg"}; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args3), flags::HelpMode::kImportant); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestSubstringHelpFlagHandling) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--help=abcd", | |
| }; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kMatch); | |
| EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, TestVersionHandling) { | |
| const char* in_args1[] = { | |
| "testbin", | |
| "--version", | |
| }; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kVersion); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, TestCheckArgsHandling) { | |
| const char* in_args1[] = {"testbin", "--only_check_args", "--int_flag=211"}; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kOnlyCheckArgs); | |
| EXPECT_EXIT(InvokeParseAbslOnly(in_args1), testing::ExitedWithCode(0), ""); | |
| EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(0), ""); | |
| const char* in_args2[] = {"testbin", "--only_check_args", "--unknown_flag=a"}; | |
| EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kOnlyCheckArgs); | |
| EXPECT_EXIT(InvokeParseAbslOnly(in_args2), testing::ExitedWithCode(0), ""); | |
| EXPECT_EXIT(InvokeParse(in_args2), testing::ExitedWithCode(1), ""); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, WasPresentOnCommandLine) { | |
| const char* in_args1[] = { | |
| "testbin", "arg1", "--bool_flag", | |
| "--int_flag=211", "arg2", "--double_flag=1.1", | |
| "--string_flag", "asd", "--", | |
| "--some_flag", "arg4", | |
| }; | |
| InvokeParse(in_args1); | |
| EXPECT_TRUE(flags::WasPresentOnCommandLine("bool_flag")); | |
| EXPECT_TRUE(flags::WasPresentOnCommandLine("int_flag")); | |
| EXPECT_TRUE(flags::WasPresentOnCommandLine("double_flag")); | |
| EXPECT_TRUE(flags::WasPresentOnCommandLine("string_flag")); | |
| EXPECT_FALSE(flags::WasPresentOnCommandLine("some_flag")); | |
| EXPECT_FALSE(flags::WasPresentOnCommandLine("another_flag")); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, ParseAbseilFlagsOnlySuccess) { | |
| const char* in_args[] = { | |
| "testbin", | |
| "arg1", | |
| "--bool_flag", | |
| "--int_flag=211", | |
| "arg2", | |
| "--double_flag=1.1", | |
| "--undef_flag1", | |
| "--undef_flag2=123", | |
| "--string_flag", | |
| "asd", | |
| "--", | |
| "--some_flag", | |
| "arg4", | |
| }; | |
| std::vector<char*> positional_args; | |
| std::vector<absl::UnrecognizedFlag> unrecognized_flags; | |
| absl::ParseAbseilFlagsOnly(13, const_cast<char**>(in_args), positional_args, | |
| unrecognized_flags); | |
| EXPECT_THAT(positional_args, | |
| ElementsAreArray( | |
| {absl::string_view("testbin"), absl::string_view("arg1"), | |
| absl::string_view("arg2"), absl::string_view("--some_flag"), | |
| absl::string_view("arg4")})); | |
| EXPECT_THAT(unrecognized_flags, | |
| ElementsAreArray( | |
| {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, | |
| "undef_flag1"), | |
| absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, | |
| "undef_flag2")})); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, ParseAbseilFlagsOnlyFailure) { | |
| const char* in_args[] = { | |
| "testbin", | |
| "--int_flag=21.1", | |
| }; | |
| EXPECT_DEATH_IF_SUPPORTED( | |
| InvokeParseAbslOnly(in_args), | |
| "Illegal value '21.1' specified for flag 'int_flag'"); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, UndefOkFlagsAreIgnored) { | |
| const char* in_args[] = { | |
| "testbin", "--undef_flag1", | |
| "--undef_flag2=123", "--undefok=undef_flag2", | |
| "--undef_flag3", "value", | |
| }; | |
| std::vector<char*> positional_args; | |
| std::vector<absl::UnrecognizedFlag> unrecognized_flags; | |
| absl::ParseAbseilFlagsOnly(6, const_cast<char**>(in_args), positional_args, | |
| unrecognized_flags); | |
| EXPECT_THAT(positional_args, ElementsAreArray({absl::string_view("testbin"), | |
| absl::string_view("value")})); | |
| EXPECT_THAT(unrecognized_flags, | |
| ElementsAreArray( | |
| {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, | |
| "undef_flag1"), | |
| absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, | |
| "undef_flag3")})); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseTest, AllUndefOkFlagsAreIgnored) { | |
| const char* in_args[] = { | |
| "testbin", | |
| "--undef_flag1", | |
| "--undef_flag2=123", | |
| "--undefok=undef_flag2,undef_flag1,undef_flag3", | |
| "--undef_flag3", | |
| "value", | |
| "--", | |
| "--undef_flag4", | |
| }; | |
| std::vector<char*> positional_args; | |
| std::vector<absl::UnrecognizedFlag> unrecognized_flags; | |
| absl::ParseAbseilFlagsOnly(8, const_cast<char**>(in_args), positional_args, | |
| unrecognized_flags); | |
| EXPECT_THAT(positional_args, | |
| ElementsAreArray({absl::string_view("testbin"), | |
| absl::string_view("value"), | |
| absl::string_view("--undef_flag4")})); | |
| EXPECT_THAT(unrecognized_flags, testing::IsEmpty()); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, ExitOnUnrecognizedFlagPrintsHelp) { | |
| const char* in_args[] = { | |
| "testbin", | |
| "--undef_flag1", | |
| "--help=int_flag", | |
| }; | |
| EXPECT_EXIT(InvokeParseCommandLineImpl(in_args), testing::ExitedWithCode(1), | |
| AllOf(HasSubstr("Unknown command line flag 'undef_flag1'"), | |
| HasSubstr("Try --helpfull to get a list of all flags"))); | |
| } | |
| // -------------------------------------------------------------------- | |
| TEST_F(ParseDeathTest, DisableFlagfileAndEnvParsing) { | |
| EXPECT_EXIT( | |
| ([]() { | |
| absl::DisableFlagfileAndEnvParsing(); | |
| std::string flagfile_flag; | |
| const char* args[] = { | |
| "testbin", | |
| "--string_flag=abc", | |
| GetFlagfileFlag({{"parse_test.ff_disabled", {"--int_flag=123"}}}, | |
| flagfile_flag), | |
| }; | |
| InvokeParse(args); | |
| exit(absl::GetFlag(FLAGS_int_flag) == 123 || | |
| absl::GetFlag(FLAGS_string_flag) != "abc" | |
| ? 1 | |
| : 0); | |
| })(), | |
| testing::ExitedWithCode(0), | |
| "Skipping expansion of --flagfile as it is disabled"); | |
| EXPECT_EXIT(([]() { | |
| absl::DisableFlagfileAndEnvParsing(); | |
| ScopedSetEnv set_int_flag("FLAGS_int_flag", "123"); | |
| const char* args[] = {"testbin", "--string_flag=abc", | |
| "--fromenv=int_flag"}; | |
| InvokeParse(args); | |
| exit(absl::GetFlag(FLAGS_int_flag) == 123 || | |
| absl::GetFlag(FLAGS_string_flag) != "abc" | |
| ? 1 | |
| : 0); | |
| })(), | |
| testing::ExitedWithCode(0), | |
| "Skipping expansion of --fromenv/--tryfromenv as it is disabled"); | |
| EXPECT_EXIT(([]() { | |
| absl::DisableFlagfileAndEnvParsing(); | |
| ScopedSetEnv set_int_flag("FLAGS_int_flag", "123"); | |
| const char* args[] = {"testbin", "--string_flag=abc", | |
| "--tryfromenv=int_flag"}; | |
| InvokeParse(args); | |
| exit(absl::GetFlag(FLAGS_int_flag) == 123 || | |
| absl::GetFlag(FLAGS_string_flag) != "abc" | |
| ? 1 | |
| : 0); | |
| })(), | |
| testing::ExitedWithCode(0), | |
| "Skipping expansion of --fromenv/--tryfromenv as it is disabled"); | |
| } | |
| TEST_F(ParseDeathTest, IsIndirectFlagExpansionEnabledWorks) { | |
| EXPECT_EXIT( | |
| ([]() { | |
| if (!absl::flags_internal::IsIndirectFlagExpansionEnabled()) { | |
| exit(1); | |
| } | |
| absl::DisableFlagfileAndEnvParsing(); | |
| exit(absl::flags_internal::IsIndirectFlagExpansionEnabled() ? 2 : 0); | |
| })(), | |
| testing::ExitedWithCode(0), ""); | |
| } | |
| } // namespace |