blob: f4a83beaa20d3f40f191bf626e1bfdc49405acb5 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* 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
*
* http://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 <common/libs/utils/flag_parser.h>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <gtest/gtest.h>
#include <libxml/parser.h>
#include "common/libs/utils/result_matchers.h"
#include "gmock/gmock-matchers.h"
namespace cuttlefish {
TEST(FlagParser, DuplicateAlias) {
FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"};
ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias");
}
TEST(FlagParser, ConflictingAlias) {
FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"};
FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"};
ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); },
"Overlapping flag aliases");
}
TEST(FlagParser, StringFlag) {
std::string value;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag=a"}), IsOk());
ASSERT_EQ(value, "a");
ASSERT_THAT(flag.Parse({"--myflag=b"}), IsOk());
ASSERT_EQ(value, "b");
ASSERT_THAT(flag.Parse({"-myflag", "c"}), IsOk());
ASSERT_EQ(value, "c");
ASSERT_THAT(flag.Parse({"--myflag", "d"}), IsOk());
ASSERT_EQ(value, "d");
ASSERT_THAT(flag.Parse({"--myflag="}), IsOk());
ASSERT_EQ(value, "");
}
TEST(FlagParser, NormalizedStringFlag) {
std::string value;
auto flag = GflagsCompatFlag("my_flag", value);
ASSERT_THAT(flag.Parse({"-my-flag=a"}), IsOk());
ASSERT_EQ(value, "a");
ASSERT_THAT(flag.Parse({"--my-flag=b"}), IsOk());
ASSERT_EQ(value, "b");
ASSERT_THAT(flag.Parse({"-my-flag", "c"}), IsOk());
ASSERT_EQ(value, "c");
ASSERT_THAT(flag.Parse({"--my-flag", "d"}), IsOk());
ASSERT_EQ(value, "d");
ASSERT_THAT(flag.Parse({"--my-flag="}), IsOk());
ASSERT_EQ(value, "");
}
std::optional<std::map<std::string, std::string>> flagXml(const Flag& f) {
std::stringstream xml_stream;
if (!f.WriteGflagsCompatXml(xml_stream)) {
return {};
}
auto xml = xml_stream.str();
// Holds all memory for the parsed structure.
std::unique_ptr<xmlDoc, xmlFreeFunc> doc(
xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree);
if (!doc) {
return {};
}
xmlNodePtr root_element = xmlDocGetRootElement(doc.get());
std::map<std::string, std::string> elements_map;
for (auto elem = root_element->children; elem != nullptr; elem = elem->next) {
if (elem->type != xmlElementType::XML_ELEMENT_NODE) {
continue;
}
elements_map[(char*)elem->name] = "";
if (elem->children == nullptr) {
continue;
}
if (elem->children->type != XML_TEXT_NODE) {
continue;
}
elements_map[(char*)elem->name] = (char*)elem->children->content;
}
return elements_map;
}
TEST(FlagParser, GflagsIncompatibleFlag) {
auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"});
ASSERT_FALSE(flagXml(flag));
}
TEST(FlagParser, StringFlagXml) {
std::string value = "somedefault";
auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
auto xml = flagXml(flag);
ASSERT_TRUE(xml);
ASSERT_NE((*xml)["file"], "");
ASSERT_EQ((*xml)["name"], "myflag");
ASSERT_EQ((*xml)["meaning"], "somehelp");
ASSERT_EQ((*xml)["default"], "somedefault");
ASSERT_EQ((*xml)["current"], "somedefault");
ASSERT_EQ((*xml)["type"], "string");
}
TEST(FlagParser, RepeatedStringFlag) {
std::string value;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag=a", "--myflag", "b"}), IsOk());
ASSERT_EQ(value, "b");
}
TEST(FlagParser, RepeatedListFlag) {
std::vector<std::string> elems;
auto flag = GflagsCompatFlag("myflag");
flag.Setter([&elems](const FlagMatch& match) -> Result<void> {
elems.push_back(match.value);
return {};
});
ASSERT_THAT(flag.Parse({"-myflag=a", "--myflag", "b"}), IsOk());
ASSERT_EQ(elems, (std::vector<std::string>{"a", "b"}));
}
TEST(FlagParser, FlagRemoval) {
std::string value;
auto flag = GflagsCompatFlag("myflag", value);
std::vector<std::string> flags = {"-myflag=a", "-otherflag=c"};
ASSERT_THAT(flag.Parse(flags), IsOk());
ASSERT_EQ(value, "a");
ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=c"});
flags = {"-otherflag=a", "-myflag=c"};
ASSERT_THAT(flag.Parse(flags), IsOk());
ASSERT_EQ(value, "c");
ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=a"});
}
TEST(FlagParser, IntFlag) {
std::int32_t value = 0;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag=5"}), IsOk());
ASSERT_EQ(value, 5);
ASSERT_THAT(flag.Parse({"--myflag=6"}), IsOk());
ASSERT_EQ(value, 6);
ASSERT_THAT(flag.Parse({"-myflag", "7"}), IsOk());
ASSERT_EQ(value, 7);
ASSERT_THAT(flag.Parse({"--myflag", "8"}), IsOk());
ASSERT_EQ(value, 8);
}
TEST(FlagParser, IntFlagXml) {
int value = 5;
auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
auto xml = flagXml(flag);
ASSERT_TRUE(xml);
ASSERT_NE((*xml)["file"], "");
ASSERT_EQ((*xml)["name"], "myflag");
ASSERT_EQ((*xml)["meaning"], "somehelp");
ASSERT_EQ((*xml)["default"], "5");
ASSERT_EQ((*xml)["current"], "5");
ASSERT_EQ((*xml)["type"], "string");
}
TEST(FlagParser, BoolFlag) {
bool value = false;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag"}), IsOk());
ASSERT_TRUE(value);
value = false;
ASSERT_THAT(flag.Parse({"--myflag"}), IsOk());
ASSERT_TRUE(value);
value = false;
ASSERT_THAT(flag.Parse({"-myflag=true"}), IsOk());
ASSERT_TRUE(value);
value = false;
ASSERT_THAT(flag.Parse({"--myflag=true"}), IsOk());
ASSERT_TRUE(value);
value = true;
ASSERT_THAT(flag.Parse({"-nomyflag"}), IsOk());
ASSERT_FALSE(value);
value = true;
ASSERT_THAT(flag.Parse({"--nomyflag"}), IsOk());
ASSERT_FALSE(value);
value = true;
ASSERT_THAT(flag.Parse({"-myflag=false"}), IsOk());
ASSERT_FALSE(value);
value = true;
ASSERT_THAT(flag.Parse({"--myflag=false"}), IsOk());
ASSERT_FALSE(value);
ASSERT_THAT(flag.Parse({"--myflag=nonsense"}), IsError());
}
TEST(FlagParser, BoolFlagXml) {
bool value = true;
auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
auto xml = flagXml(flag);
ASSERT_TRUE(xml);
ASSERT_NE((*xml)["file"], "");
ASSERT_EQ((*xml)["name"], "myflag");
ASSERT_EQ((*xml)["meaning"], "somehelp");
ASSERT_EQ((*xml)["default"], "true");
ASSERT_EQ((*xml)["current"], "true");
ASSERT_EQ((*xml)["type"], "bool");
}
TEST(FlagParser, StringIntFlag) {
std::int32_t int_value = 0;
std::string string_value;
auto int_flag = GflagsCompatFlag("int", int_value);
auto string_flag = GflagsCompatFlag("string", string_value);
std::vector<Flag> flags = {int_flag, string_flag};
EXPECT_THAT(ConsumeFlags(flags, {"-int=5", "-string=a"}), IsOk());
ASSERT_EQ(int_value, 5);
ASSERT_EQ(string_value, "a");
EXPECT_THAT(ConsumeFlags(flags, {"--int=6", "--string=b"}), IsOk());
ASSERT_EQ(int_value, 6);
ASSERT_EQ(string_value, "b");
EXPECT_THAT(ConsumeFlags(flags, {"-int", "7", "-string", "c"}), IsOk());
ASSERT_EQ(int_value, 7);
ASSERT_EQ(string_value, "c");
EXPECT_THAT(ConsumeFlags(flags, {"--int", "8", "--string", "d"}), IsOk());
ASSERT_EQ(int_value, 8);
ASSERT_EQ(string_value, "d");
}
TEST(FlagParser, StringVectorFlag) {
std::vector<std::string> value;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"--myflag="}), IsOk());
ASSERT_TRUE(value.empty());
ASSERT_THAT(flag.Parse({"--myflag=foo"}), IsOk());
ASSERT_EQ(value, std::vector<std::string>({"foo"}));
ASSERT_THAT(flag.Parse({"--myflag=foo,bar"}), IsOk());
ASSERT_EQ(value, std::vector<std::string>({"foo", "bar"}));
ASSERT_THAT(flag.Parse({"--myflag=,bar"}), IsOk());
ASSERT_EQ(value, std::vector<std::string>({"", "bar"}));
ASSERT_THAT(flag.Parse({"--myflag=foo,"}), IsOk());
ASSERT_EQ(value, std::vector<std::string>({"foo", ""}));
ASSERT_THAT(flag.Parse({"--myflag=,"}), IsOk());
ASSERT_EQ(value, std::vector<std::string>({"", ""}));
}
TEST(FlagParser, BoolVectorFlag) {
std::vector<bool> value;
bool default_value = true;
auto flag = GflagsCompatFlag("myflag", value, default_value);
ASSERT_THAT(flag.Parse({"--myflag="}), IsOk());
ASSERT_TRUE(value.empty());
ASSERT_THAT(flag.Parse({"--myflag=foo"}), IsError());
ASSERT_TRUE(value.empty());
ASSERT_THAT(flag.Parse({"--myflag=true,bar"}), IsError());
ASSERT_TRUE(value.empty());
ASSERT_THAT(flag.Parse({"--myflag=true"}), IsOk());
ASSERT_EQ(value, std::vector<bool>({true}));
ASSERT_TRUE(flagXml(flag));
ASSERT_EQ((*flagXml(flag))["default"], "true");
ASSERT_THAT(flag.Parse({"--myflag=true,false"}), IsOk());
ASSERT_EQ(value, std::vector<bool>({true, false}));
ASSERT_TRUE(flagXml(flag));
ASSERT_EQ((*flagXml(flag))["default"], "true,false");
ASSERT_THAT(flag.Parse({"--myflag=,false"}), IsOk());
ASSERT_EQ(value, std::vector<bool>({true, false}));
ASSERT_TRUE(flagXml(flag));
ASSERT_EQ((*flagXml(flag))["default"], "true,false");
ASSERT_THAT(flag.Parse({"--myflag=true,"}), IsOk());
ASSERT_EQ(value, std::vector<bool>({true, true}));
ASSERT_TRUE(flagXml(flag));
ASSERT_EQ((*flagXml(flag))["default"], "true,true");
ASSERT_THAT(flag.Parse({"--myflag=,"}), IsOk());
ASSERT_EQ(value, std::vector<bool>({true, true}));
ASSERT_TRUE(flagXml(flag));
ASSERT_EQ((*flagXml(flag))["default"], "true,true");
}
TEST(FlagParser, InvalidStringFlag) {
std::string value;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag"}), IsError());
ASSERT_THAT(flag.Parse({"--myflag"}), IsError());
}
TEST(FlagParser, InvalidIntFlag) {
int value;
auto flag = GflagsCompatFlag("myflag", value);
ASSERT_THAT(flag.Parse({"-myflag"}), IsError());
ASSERT_THAT(flag.Parse({"--myflag"}), IsError());
ASSERT_THAT(flag.Parse({"-myflag=abc"}), IsError());
ASSERT_THAT(flag.Parse({"--myflag=def"}), IsError());
ASSERT_THAT(flag.Parse({"-myflag", "abc"}), IsError());
ASSERT_THAT(flag.Parse({"--myflag", "def"}), IsError());
}
TEST(FlagParser, VerbosityFlag) {
android::base::LogSeverity value = android::base::VERBOSE;
auto flag = VerbosityFlag(value);
ASSERT_THAT(flag.Parse({"-verbosity=DEBUG"}), IsOk());
ASSERT_EQ(value, android::base::DEBUG);
ASSERT_THAT(flag.Parse({"--verbosity=INFO"}), IsOk());
ASSERT_EQ(value, android::base::INFO);
ASSERT_THAT(flag.Parse({"--verbosity=WARNING"}), IsOk());
ASSERT_EQ(value, android::base::WARNING);
ASSERT_THAT(flag.Parse({"--verbosity=ERROR"}), IsOk());
ASSERT_EQ(value, android::base::ERROR);
ASSERT_THAT(flag.Parse({"--verbosity=FATAL_WITHOUT_ABORT"}), IsOk());
ASSERT_EQ(value, android::base::FATAL_WITHOUT_ABORT);
ASSERT_THAT(flag.Parse({"--verbosity=FATAL"}), IsOk());
ASSERT_EQ(value, android::base::FATAL);
ASSERT_THAT(flag.Parse({"--verbosity=VERBOSE"}), IsOk());
ASSERT_EQ(value, android::base::VERBOSE);
}
TEST(FlagParser, InvalidVerbosityFlag) {
android::base::LogSeverity value = android::base::VERBOSE;
auto flag = VerbosityFlag(value);
ASSERT_THAT(flag.Parse({"-verbosity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"--verbosity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"-verbosity="}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"--verbosity="}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"-verbosity=not_a_severity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"--verbosity=not_a_severity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"-verbosity", "not_a_severity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
ASSERT_THAT(flag.Parse({"--verbosity", "not_a_severity"}), IsError());
ASSERT_EQ(value, android::base::VERBOSE);
}
TEST(FlagParser, InvalidFlagGuard) {
auto flag = InvalidFlagGuard();
ASSERT_THAT(flag.Parse({}), IsOk());
ASSERT_THAT(flag.Parse({"positional"}), IsOk());
ASSERT_THAT(flag.Parse({"positional", "positional2"}), IsOk());
ASSERT_THAT(flag.Parse({"-flag"}), IsError());
ASSERT_THAT(flag.Parse({"-"}), IsError());
}
TEST(FlagParser, UnexpectedArgumentGuard) {
auto flag = UnexpectedArgumentGuard();
ASSERT_THAT(flag.Parse({}), IsOk());
ASSERT_THAT(flag.Parse({"positional"}), IsError());
ASSERT_THAT(flag.Parse({"positional", "positional2"}), IsError());
ASSERT_THAT(flag.Parse({"-flag"}), IsError());
ASSERT_THAT(flag.Parse({"-"}), IsError());
}
TEST(FlagParser, EndOfOptionMark) {
std::vector<std::string> args{"-flag", "--", "-invalid_flag"};
bool flag = false;
std::vector<Flag> flags{GflagsCompatFlag("flag", flag), InvalidFlagGuard()};
EXPECT_THAT(ConsumeFlags(flags, args), IsError());
EXPECT_THAT(ConsumeFlags(flags, args,
/* recognize_end_of_option_mark */ true),
IsOk());
ASSERT_TRUE(flag);
}
class FlagConsumesArbitraryTest : public ::testing::Test {
protected:
void SetUp() override {
elems_.clear();
flag_ = Flag()
.Alias({FlagAliasMode::kFlagConsumesArbitrary, "--flag"})
.Setter([this](const FlagMatch& match) -> Result<void> {
elems_.push_back(match.value);
return {};
});
}
Flag flag_;
std::vector<std::string> elems_;
};
TEST_F(FlagConsumesArbitraryTest, NoValues) {
std::vector<std::string> inputs = {"--flag"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{}));
ASSERT_EQ(elems_, (std::vector<std::string>{""}));
}
TEST_F(FlagConsumesArbitraryTest, OneValue) {
std::vector<std::string> inputs = {"--flag", "value"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{}));
ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
}
TEST_F(FlagConsumesArbitraryTest, TwoValues) {
std::vector<std::string> inputs = {"--flag", "value1", "value2"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{}));
ASSERT_EQ(elems_, (std::vector<std::string>{"value1", "value2", ""}));
}
TEST_F(FlagConsumesArbitraryTest, NoValuesOtherFlag) {
std::vector<std::string> inputs = {"--flag", "--otherflag"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
ASSERT_EQ(elems_, (std::vector<std::string>{""}));
}
TEST_F(FlagConsumesArbitraryTest, OneValueOtherFlag) {
std::vector<std::string> inputs = {"--flag", "value", "--otherflag"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
}
TEST_F(FlagConsumesArbitraryTest, TwoValuesOtherFlag) {
std::vector<std::string> inputs = {"--flag", "v1", "v2", "--otherflag"};
ASSERT_THAT(flag_.Parse(inputs), IsOk());
ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
ASSERT_EQ(elems_, (std::vector<std::string>{"v1", "v2", ""}));
}
} // namespace cuttlefish