• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
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  *      http://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 
17 #include <common/libs/utils/flag_parser.h>
18 
19 #include <gtest/gtest.h>
20 #include <libxml/tree.h>
21 #include <map>
22 #include <optional>
23 #include <sstream>
24 #include <string>
25 #include <vector>
26 
27 namespace cuttlefish {
28 
TEST(FlagParser,DuplicateAlias)29 TEST(FlagParser, DuplicateAlias) {
30   FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"};
31   ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias");
32 }
33 
TEST(FlagParser,ConflictingAlias)34 TEST(FlagParser, ConflictingAlias) {
35   FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"};
36   FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"};
37   ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); },
38                "Overlapping flag aliases");
39 }
40 
TEST(FlagParser,StringFlag)41 TEST(FlagParser, StringFlag) {
42   std::string value;
43   auto flag = GflagsCompatFlag("myflag", value);
44   ASSERT_TRUE(flag.Parse({"-myflag=a"}));
45   ASSERT_EQ(value, "a");
46   ASSERT_TRUE(flag.Parse({"--myflag=b"}));
47   ASSERT_EQ(value, "b");
48   ASSERT_TRUE(flag.Parse({"-myflag", "c"}));
49   ASSERT_EQ(value, "c");
50   ASSERT_TRUE(flag.Parse({"--myflag", "d"}));
51   ASSERT_EQ(value, "d");
52   ASSERT_TRUE(flag.Parse({"--myflag="}));
53   ASSERT_EQ(value, "");
54 }
55 
flagXml(const Flag & f)56 std::optional<std::map<std::string, std::string>> flagXml(const Flag& f) {
57   std::stringstream xml_stream;
58   if (!f.WriteGflagsCompatXml(xml_stream)) {
59     return {};
60   }
61   auto xml = xml_stream.str();
62   // Holds all memory for the parsed structure.
63   std::unique_ptr<xmlDoc, xmlFreeFunc> doc(
64       xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree);
65   if (!doc) {
66     return {};
67   }
68   xmlNodePtr root_element = xmlDocGetRootElement(doc.get());
69   std::map<std::string, std::string> elements_map;
70   for (auto elem = root_element->children; elem != nullptr; elem = elem->next) {
71     if (elem->type != xmlElementType::XML_ELEMENT_NODE) {
72       continue;
73     }
74     elements_map[(char*)elem->name] = "";
75     if (elem->children == nullptr) {
76       continue;
77     }
78     if (elem->children->type != XML_TEXT_NODE) {
79       continue;
80     }
81     elements_map[(char*)elem->name] = (char*)elem->children->content;
82   }
83   return elements_map;
84 }
85 
TEST(FlagParser,GflagsIncompatibleFlag)86 TEST(FlagParser, GflagsIncompatibleFlag) {
87   auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"});
88   ASSERT_FALSE(flagXml(flag));
89 }
90 
TEST(FlagParser,StringFlagXml)91 TEST(FlagParser, StringFlagXml) {
92   std::string value = "somedefault";
93   auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
94   auto xml = flagXml(flag);
95   ASSERT_TRUE(xml);
96   ASSERT_NE((*xml)["file"], "");
97   ASSERT_EQ((*xml)["name"], "myflag");
98   ASSERT_EQ((*xml)["meaning"], "somehelp");
99   ASSERT_EQ((*xml)["default"], "somedefault");
100   ASSERT_EQ((*xml)["current"], "somedefault");
101   ASSERT_EQ((*xml)["type"], "string");
102 }
103 
TEST(FlagParser,RepeatedStringFlag)104 TEST(FlagParser, RepeatedStringFlag) {
105   std::string value;
106   auto flag = GflagsCompatFlag("myflag", value);
107   ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
108   ASSERT_EQ(value, "b");
109 }
110 
TEST(FlagParser,RepeatedListFlag)111 TEST(FlagParser, RepeatedListFlag) {
112   std::vector<std::string> elems;
113   auto flag = GflagsCompatFlag("myflag");
114   flag.Setter([&elems](const FlagMatch& match) {
115     elems.push_back(match.value);
116     return true;
117   });
118   ASSERT_TRUE(flag.Parse({"-myflag=a", "--myflag", "b"}));
119   ASSERT_EQ(elems, (std::vector<std::string>{"a", "b"}));
120 }
121 
TEST(FlagParser,FlagRemoval)122 TEST(FlagParser, FlagRemoval) {
123   std::string value;
124   auto flag = GflagsCompatFlag("myflag", value);
125   std::vector<std::string> flags = {"-myflag=a", "-otherflag=c"};
126   ASSERT_TRUE(flag.Parse(flags));
127   ASSERT_EQ(value, "a");
128   ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=c"});
129   flags = {"-otherflag=a", "-myflag=c"};
130   ASSERT_TRUE(flag.Parse(flags));
131   ASSERT_EQ(value, "c");
132   ASSERT_EQ(flags, std::vector<std::string>{"-otherflag=a"});
133 }
134 
TEST(FlagParser,IntFlag)135 TEST(FlagParser, IntFlag) {
136   std::int32_t value = 0;
137   auto flag = GflagsCompatFlag("myflag", value);
138   ASSERT_TRUE(flag.Parse({"-myflag=5"}));
139   ASSERT_EQ(value, 5);
140   ASSERT_TRUE(flag.Parse({"--myflag=6"}));
141   ASSERT_EQ(value, 6);
142   ASSERT_TRUE(flag.Parse({"-myflag", "7"}));
143   ASSERT_EQ(value, 7);
144   ASSERT_TRUE(flag.Parse({"--myflag", "8"}));
145   ASSERT_EQ(value, 8);
146 }
147 
TEST(FlagParser,IntFlagXml)148 TEST(FlagParser, IntFlagXml) {
149   int value = 5;
150   auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
151   auto xml = flagXml(flag);
152   ASSERT_TRUE(xml);
153   ASSERT_NE((*xml)["file"], "");
154   ASSERT_EQ((*xml)["name"], "myflag");
155   ASSERT_EQ((*xml)["meaning"], "somehelp");
156   ASSERT_EQ((*xml)["default"], "5");
157   ASSERT_EQ((*xml)["current"], "5");
158   ASSERT_EQ((*xml)["type"], "string");
159 }
160 
TEST(FlagParser,BoolFlag)161 TEST(FlagParser, BoolFlag) {
162   bool value = false;
163   auto flag = GflagsCompatFlag("myflag", value);
164   ASSERT_TRUE(flag.Parse({"-myflag"}));
165   ASSERT_TRUE(value);
166 
167   value = false;
168   ASSERT_TRUE(flag.Parse({"--myflag"}));
169   ASSERT_TRUE(value);
170 
171   value = false;
172   ASSERT_TRUE(flag.Parse({"-myflag=true"}));
173   ASSERT_TRUE(value);
174 
175   value = false;
176   ASSERT_TRUE(flag.Parse({"--myflag=true"}));
177   ASSERT_TRUE(value);
178 
179   value = true;
180   ASSERT_TRUE(flag.Parse({"-nomyflag"}));
181   ASSERT_FALSE(value);
182 
183   value = true;
184   ASSERT_TRUE(flag.Parse({"--nomyflag"}));
185   ASSERT_FALSE(value);
186 
187   value = true;
188   ASSERT_TRUE(flag.Parse({"-myflag=false"}));
189   ASSERT_FALSE(value);
190 
191   value = true;
192   ASSERT_TRUE(flag.Parse({"--myflag=false"}));
193   ASSERT_FALSE(value);
194 
195   ASSERT_FALSE(flag.Parse({"--myflag=nonsense"}));
196 }
197 
TEST(FlagParser,BoolFlagXml)198 TEST(FlagParser, BoolFlagXml) {
199   bool value = true;
200   auto flag = GflagsCompatFlag("myflag", value).Help("somehelp");
201   auto xml = flagXml(flag);
202   ASSERT_TRUE(xml);
203   ASSERT_NE((*xml)["file"], "");
204   ASSERT_EQ((*xml)["name"], "myflag");
205   ASSERT_EQ((*xml)["meaning"], "somehelp");
206   ASSERT_EQ((*xml)["default"], "true");
207   ASSERT_EQ((*xml)["current"], "true");
208   ASSERT_EQ((*xml)["type"], "bool");
209 }
210 
TEST(FlagParser,StringIntFlag)211 TEST(FlagParser, StringIntFlag) {
212   std::int32_t int_value = 0;
213   std::string string_value;
214   auto int_flag = GflagsCompatFlag("int", int_value);
215   auto string_flag = GflagsCompatFlag("string", string_value);
216   std::vector<Flag> flags = {int_flag, string_flag};
217   ASSERT_TRUE(ParseFlags(flags, {"-int=5", "-string=a"}));
218   ASSERT_EQ(int_value, 5);
219   ASSERT_EQ(string_value, "a");
220   ASSERT_TRUE(ParseFlags(flags, {"--int=6", "--string=b"}));
221   ASSERT_EQ(int_value, 6);
222   ASSERT_EQ(string_value, "b");
223   ASSERT_TRUE(ParseFlags(flags, {"-int", "7", "-string", "c"}));
224   ASSERT_EQ(int_value, 7);
225   ASSERT_EQ(string_value, "c");
226   ASSERT_TRUE(ParseFlags(flags, {"--int", "8", "--string", "d"}));
227   ASSERT_EQ(int_value, 8);
228   ASSERT_EQ(string_value, "d");
229 }
230 
TEST(FlagParser,InvalidStringFlag)231 TEST(FlagParser, InvalidStringFlag) {
232   std::string value;
233   auto flag = GflagsCompatFlag("myflag", value);
234   ASSERT_FALSE(flag.Parse({"-myflag"}));
235   ASSERT_FALSE(flag.Parse({"--myflag"}));
236 }
237 
TEST(FlagParser,InvalidIntFlag)238 TEST(FlagParser, InvalidIntFlag) {
239   int value;
240   auto flag = GflagsCompatFlag("myflag", value);
241   ASSERT_FALSE(flag.Parse({"-myflag"}));
242   ASSERT_FALSE(flag.Parse({"--myflag"}));
243   ASSERT_FALSE(flag.Parse({"-myflag=abc"}));
244   ASSERT_FALSE(flag.Parse({"--myflag=def"}));
245   ASSERT_FALSE(flag.Parse({"-myflag", "abc"}));
246   ASSERT_FALSE(flag.Parse({"--myflag", "def"}));
247 }
248 
TEST(FlagParser,InvalidFlagGuard)249 TEST(FlagParser, InvalidFlagGuard) {
250   auto flag = InvalidFlagGuard();
251   ASSERT_TRUE(flag.Parse({}));
252   ASSERT_TRUE(flag.Parse({"positional"}));
253   ASSERT_TRUE(flag.Parse({"positional", "positional2"}));
254   ASSERT_FALSE(flag.Parse({"-flag"}));
255   ASSERT_FALSE(flag.Parse({"-"}));
256 }
257 
TEST(FlagParser,UnexpectedArgumentGuard)258 TEST(FlagParser, UnexpectedArgumentGuard) {
259   auto flag = UnexpectedArgumentGuard();
260   ASSERT_TRUE(flag.Parse({}));
261   ASSERT_FALSE(flag.Parse({"positional"}));
262   ASSERT_FALSE(flag.Parse({"positional", "positional2"}));
263   ASSERT_FALSE(flag.Parse({"-flag"}));
264   ASSERT_FALSE(flag.Parse({"-"}));
265 }
266 
267 class FlagConsumesArbitraryTest : public ::testing::Test {
268  protected:
SetUp()269   void SetUp() override {
270     elems_.clear();
271     flag_ = Flag()
272                 .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--flag"})
273                 .Setter([this](const FlagMatch& match) {
274                   elems_.push_back(match.value);
275                   return true;
276                 });
277   }
278   Flag flag_;
279   std::vector<std::string> elems_;
280 };
281 
TEST_F(FlagConsumesArbitraryTest,NoValues)282 TEST_F(FlagConsumesArbitraryTest, NoValues) {
283   std::vector<std::string> inputs = {"--flag"};
284   ASSERT_TRUE(flag_.Parse(inputs));
285   ASSERT_EQ(inputs, (std::vector<std::string>{}));
286   ASSERT_EQ(elems_, (std::vector<std::string>{""}));
287 }
288 
TEST_F(FlagConsumesArbitraryTest,OneValue)289 TEST_F(FlagConsumesArbitraryTest, OneValue) {
290   std::vector<std::string> inputs = {"--flag", "value"};
291   ASSERT_TRUE(flag_.Parse(inputs));
292   ASSERT_EQ(inputs, (std::vector<std::string>{}));
293   ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
294 }
295 
TEST_F(FlagConsumesArbitraryTest,TwoValues)296 TEST_F(FlagConsumesArbitraryTest, TwoValues) {
297   std::vector<std::string> inputs = {"--flag", "value1", "value2"};
298   ASSERT_TRUE(flag_.Parse(inputs));
299   ASSERT_EQ(inputs, (std::vector<std::string>{}));
300   ASSERT_EQ(elems_, (std::vector<std::string>{"value1", "value2", ""}));
301 }
302 
TEST_F(FlagConsumesArbitraryTest,NoValuesOtherFlag)303 TEST_F(FlagConsumesArbitraryTest, NoValuesOtherFlag) {
304   std::vector<std::string> inputs = {"--flag", "--otherflag"};
305   ASSERT_TRUE(flag_.Parse(inputs));
306   ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
307   ASSERT_EQ(elems_, (std::vector<std::string>{""}));
308 }
309 
TEST_F(FlagConsumesArbitraryTest,OneValueOtherFlag)310 TEST_F(FlagConsumesArbitraryTest, OneValueOtherFlag) {
311   std::vector<std::string> inputs = {"--flag", "value", "--otherflag"};
312   ASSERT_TRUE(flag_.Parse(inputs));
313   ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
314   ASSERT_EQ(elems_, (std::vector<std::string>{"value", ""}));
315 }
316 
TEST_F(FlagConsumesArbitraryTest,TwoValuesOtherFlag)317 TEST_F(FlagConsumesArbitraryTest, TwoValuesOtherFlag) {
318   std::vector<std::string> inputs = {"--flag", "v1", "v2", "--otherflag"};
319   ASSERT_TRUE(flag_.Parse(inputs));
320   ASSERT_EQ(inputs, (std::vector<std::string>{"--otherflag"}));
321   ASSERT_EQ(elems_, (std::vector<std::string>{"v1", "v2", ""}));
322 }
323 
324 }  // namespace cuttlefish
325