1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/fml/command_line.h"
6
7 #include <string_view>
8 #include <utility>
9
10 #include "flutter/fml/macros.h"
11 #include "flutter/fml/size.h"
12 #include "gtest/gtest.h"
13
14 namespace fml {
15 namespace {
16
TEST(CommandLineTest,Basic)17 TEST(CommandLineTest, Basic) {
18 // Making this const verifies that the methods called are const.
19 const auto cl = CommandLineFromInitializerList(
20 {"my_program", "--flag1", "--flag2=value2", "arg1", "arg2", "arg3"});
21
22 EXPECT_TRUE(cl.has_argv0());
23 EXPECT_EQ("my_program", cl.argv0());
24
25 EXPECT_EQ(2u, cl.options().size());
26 EXPECT_EQ("flag1", cl.options()[0].name);
27 EXPECT_EQ(std::string(), cl.options()[0].value);
28 EXPECT_EQ("flag2", cl.options()[1].name);
29 EXPECT_EQ("value2", cl.options()[1].value);
30
31 EXPECT_EQ(3u, cl.positional_args().size());
32 EXPECT_EQ("arg1", cl.positional_args()[0]);
33 EXPECT_EQ("arg2", cl.positional_args()[1]);
34 EXPECT_EQ("arg3", cl.positional_args()[2]);
35
36 EXPECT_TRUE(cl.HasOption("flag1"));
37 EXPECT_TRUE(cl.HasOption("flag1", nullptr));
38 size_t index = static_cast<size_t>(-1);
39 EXPECT_TRUE(cl.HasOption("flag2", &index));
40 EXPECT_EQ(1u, index);
41 EXPECT_FALSE(cl.HasOption("flag3"));
42 EXPECT_FALSE(cl.HasOption("flag3", nullptr));
43
44 std::string value = "nonempty";
45 EXPECT_TRUE(cl.GetOptionValue("flag1", &value));
46 EXPECT_EQ(std::string(), value);
47 EXPECT_TRUE(cl.GetOptionValue("flag2", &value));
48 EXPECT_EQ("value2", value);
49 EXPECT_FALSE(cl.GetOptionValue("flag3", &value));
50
51 EXPECT_EQ(std::string(), cl.GetOptionValueWithDefault("flag1", "nope"));
52 EXPECT_EQ("value2", cl.GetOptionValueWithDefault("flag2", "nope"));
53 EXPECT_EQ("nope", cl.GetOptionValueWithDefault("flag3", "nope"));
54 }
55
TEST(CommandLineTest,DefaultConstructor)56 TEST(CommandLineTest, DefaultConstructor) {
57 CommandLine cl;
58 EXPECT_FALSE(cl.has_argv0());
59 EXPECT_EQ(std::string(), cl.argv0());
60 EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());
61 EXPECT_EQ(std::vector<std::string>(), cl.positional_args());
62 }
63
TEST(CommandLineTest,ComponentConstructor)64 TEST(CommandLineTest, ComponentConstructor) {
65 const std::string argv0 = "my_program";
66 const std::vector<CommandLine::Option> options = {
67 CommandLine::Option("flag", "value")};
68 const std::vector<std::string> positional_args = {"arg"};
69
70 CommandLine cl(argv0, options, positional_args);
71 EXPECT_TRUE(cl.has_argv0());
72 EXPECT_EQ(argv0, cl.argv0());
73 EXPECT_EQ(options, cl.options());
74 EXPECT_EQ(positional_args, cl.positional_args());
75 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
76 }
77
TEST(CommandLineTest,CommandLineFromIteratorsFindFirstPositionalArg)78 TEST(CommandLineTest, CommandLineFromIteratorsFindFirstPositionalArg) {
79 // This shows how one might process subcommands.
80 {
81 static std::vector<std::string> argv = {"my_program", "--flag1",
82 "--flag2", "subcommand",
83 "--subflag", "subarg"};
84 auto first = argv.cbegin();
85 auto last = argv.cend();
86 std::vector<std::string>::const_iterator sub_first;
87 auto cl =
88 CommandLineFromIteratorsFindFirstPositionalArg(first, last, &sub_first);
89 EXPECT_TRUE(cl.has_argv0());
90 EXPECT_EQ(argv[0], cl.argv0());
91 std::vector<CommandLine::Option> expected_options = {
92 CommandLine::Option("flag1"), CommandLine::Option("flag2")};
93 EXPECT_EQ(expected_options, cl.options());
94 std::vector<std::string> expected_positional_args = {argv[3], argv[4],
95 argv[5]};
96 EXPECT_EQ(expected_positional_args, cl.positional_args());
97 EXPECT_TRUE(cl.HasOption("flag1", nullptr));
98 EXPECT_TRUE(cl.HasOption("flag2", nullptr));
99 EXPECT_FALSE(cl.HasOption("subflag", nullptr));
100
101 EXPECT_EQ(first + 3, sub_first);
102 auto sub_cl = CommandLineFromIterators(sub_first, last);
103 EXPECT_TRUE(sub_cl.has_argv0());
104 EXPECT_EQ(argv[3], sub_cl.argv0());
105 std::vector<CommandLine::Option> expected_sub_options = {
106 CommandLine::Option("subflag")};
107 EXPECT_EQ(expected_sub_options, sub_cl.options());
108 std::vector<std::string> expected_sub_positional_args = {argv[5]};
109 EXPECT_EQ(expected_sub_positional_args, sub_cl.positional_args());
110 EXPECT_FALSE(sub_cl.HasOption("flag1", nullptr));
111 EXPECT_FALSE(sub_cl.HasOption("flag2", nullptr));
112 EXPECT_TRUE(sub_cl.HasOption("subflag", nullptr));
113 }
114
115 // No positional argument.
116 {
117 static std::vector<std::string> argv = {"my_program", "--flag"};
118 std::vector<std::string>::const_iterator sub_first;
119 auto cl = CommandLineFromIteratorsFindFirstPositionalArg(
120 argv.cbegin(), argv.cend(), &sub_first);
121 EXPECT_EQ(argv.cend(), sub_first);
122 }
123
124 // Multiple positional arguments.
125 {
126 static std::vector<std::string> argv = {"my_program", "arg1", "arg2"};
127 std::vector<std::string>::const_iterator sub_first;
128 auto cl = CommandLineFromIteratorsFindFirstPositionalArg(
129 argv.cbegin(), argv.cend(), &sub_first);
130 EXPECT_EQ(argv.cbegin() + 1, sub_first);
131 }
132
133 // "--".
134 {
135 static std::vector<std::string> argv = {"my_program", "--", "--arg"};
136 std::vector<std::string>::const_iterator sub_first;
137 auto cl = CommandLineFromIteratorsFindFirstPositionalArg(
138 argv.cbegin(), argv.cend(), &sub_first);
139 EXPECT_EQ(argv.cbegin() + 2, sub_first);
140 }
141 }
142
TEST(CommandLineTest,CommmandLineFromIterators)143 TEST(CommandLineTest, CommmandLineFromIterators) {
144 {
145 // Note (here and below): The |const| ensures that the factory method can
146 // accept const iterators.
147 const std::vector<std::string> argv = {"my_program", "--flag=value", "arg"};
148
149 auto cl = CommandLineFromIterators(argv.begin(), argv.end());
150 EXPECT_TRUE(cl.has_argv0());
151 EXPECT_EQ(argv[0], cl.argv0());
152 std::vector<CommandLine::Option> expected_options = {
153 CommandLine::Option("flag", "value")};
154 EXPECT_EQ(expected_options, cl.options());
155 std::vector<std::string> expected_positional_args = {argv[2]};
156 EXPECT_EQ(expected_positional_args, cl.positional_args());
157 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
158 }
159
160 // Can handle empty argv.
161 {
162 const std::vector<std::string> argv;
163
164 auto cl = CommandLineFromIterators(argv.begin(), argv.end());
165 EXPECT_FALSE(cl.has_argv0());
166 EXPECT_EQ(std::string(), cl.argv0());
167 EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());
168 EXPECT_EQ(std::vector<std::string>(), cl.positional_args());
169 }
170
171 // Can handle empty |argv[0]|.
172 {
173 const std::vector<std::string> argv = {""};
174
175 auto cl = CommandLineFromIterators(argv.begin(), argv.end());
176 EXPECT_TRUE(cl.has_argv0());
177 EXPECT_EQ(std::string(), cl.argv0());
178 EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());
179 EXPECT_EQ(std::vector<std::string>(), cl.positional_args());
180 }
181
182 // Can also take a vector of |const char*|s.
183 {
184 const std::vector<const char*> argv = {"my_program", "--flag=value", "arg"};
185
186 auto cl = CommandLineFromIterators(argv.begin(), argv.end());
187 EXPECT_TRUE(cl.has_argv0());
188 EXPECT_EQ(argv[0], cl.argv0());
189 std::vector<CommandLine::Option> expected_options = {
190 CommandLine::Option("flag", "value")};
191 EXPECT_EQ(expected_options, cl.options());
192 std::vector<std::string> expected_positional_args = {argv[2]};
193 EXPECT_EQ(expected_positional_args, cl.positional_args());
194 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
195 }
196
197 // Or a plain old array.
198 {
199 static const char* const argv[] = {"my_program", "--flag=value", "arg"};
200
201 auto cl = CommandLineFromIterators(argv, argv + fml::size(argv));
202 EXPECT_TRUE(cl.has_argv0());
203 EXPECT_EQ(argv[0], cl.argv0());
204 std::vector<CommandLine::Option> expected_options = {
205 CommandLine::Option("flag", "value")};
206 EXPECT_EQ(expected_options, cl.options());
207 std::vector<std::string> expected_positional_args = {argv[2]};
208 EXPECT_EQ(expected_positional_args, cl.positional_args());
209 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
210 }
211 }
212
TEST(CommandLineTest,CommandLineFromArgcArgv)213 TEST(CommandLineTest, CommandLineFromArgcArgv) {
214 static const char* const argv[] = {"my_program", "--flag=value", "arg"};
215 const int argc = static_cast<int>(fml::size(argv));
216
217 auto cl = CommandLineFromArgcArgv(argc, argv);
218 EXPECT_TRUE(cl.has_argv0());
219 EXPECT_EQ(argv[0], cl.argv0());
220 std::vector<CommandLine::Option> expected_options = {
221 CommandLine::Option("flag", "value")};
222 EXPECT_EQ(expected_options, cl.options());
223 std::vector<std::string> expected_positional_args = {argv[2]};
224 EXPECT_EQ(expected_positional_args, cl.positional_args());
225 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
226 }
227
TEST(CommandLineTest,CommandLineFromInitializerList)228 TEST(CommandLineTest, CommandLineFromInitializerList) {
229 {
230 std::initializer_list<const char*> il = {"my_program", "--flag=value",
231 "arg"};
232 auto cl = CommandLineFromInitializerList(il);
233 EXPECT_TRUE(cl.has_argv0());
234 EXPECT_EQ("my_program", cl.argv0());
235 std::vector<CommandLine::Option> expected_options = {
236 CommandLine::Option("flag", "value")};
237 EXPECT_EQ(expected_options, cl.options());
238 std::vector<std::string> expected_positional_args = {"arg"};
239 EXPECT_EQ(expected_positional_args, cl.positional_args());
240 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
241 }
242
243 {
244 std::initializer_list<std::string> il = {"my_program", "--flag=value",
245 "arg"};
246 auto cl = CommandLineFromInitializerList(il);
247 EXPECT_TRUE(cl.has_argv0());
248 EXPECT_EQ("my_program", cl.argv0());
249 std::vector<CommandLine::Option> expected_options = {
250 CommandLine::Option("flag", "value")};
251 EXPECT_EQ(expected_options, cl.options());
252 std::vector<std::string> expected_positional_args = {"arg"};
253 EXPECT_EQ(expected_positional_args, cl.positional_args());
254 EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope"));
255 }
256 }
257
TEST(CommandLineTest,OddArguments)258 TEST(CommandLineTest, OddArguments) {
259 {
260 // Except for "arg", these are all options.
261 auto cl = CommandLineFromInitializerList(
262 {"my_program", "--=", "--=foo", "--bar=", "--==", "--===", "--==x",
263 "arg"});
264 EXPECT_TRUE(cl.has_argv0());
265 EXPECT_EQ("my_program", cl.argv0());
266 std::vector<CommandLine::Option> expected_options = {
267 CommandLine::Option("="), CommandLine::Option("=foo"),
268 CommandLine::Option("bar"), CommandLine::Option("="),
269 CommandLine::Option("=", "="), CommandLine::Option("=", "x")};
270 EXPECT_EQ(expected_options, cl.options());
271 std::vector<std::string> expected_positional_args = {"arg"};
272 EXPECT_EQ(expected_positional_args, cl.positional_args());
273 }
274
275 // "-x" is an argument, not an options.
276 {
277 auto cl = CommandLineFromInitializerList({"", "-x"});
278 EXPECT_TRUE(cl.has_argv0());
279 EXPECT_EQ(std::string(), cl.argv0());
280 EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());
281 std::vector<std::string> expected_positional_args = {"-x"};
282 EXPECT_EQ(expected_positional_args, cl.positional_args());
283 }
284
285 // Ditto for "-".
286 {
287 auto cl = CommandLineFromInitializerList({"", "-"});
288 EXPECT_TRUE(cl.has_argv0());
289 EXPECT_EQ(std::string(), cl.argv0());
290 EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());
291 std::vector<std::string> expected_positional_args = {"-"};
292 EXPECT_EQ(expected_positional_args, cl.positional_args());
293 }
294
295 // "--" terminates option processing, but isn't an argument in the first
296 // occurrence.
297 {
298 auto cl = CommandLineFromInitializerList(
299 {"", "--flag=value", "--", "--not-a-flag", "arg", "--"});
300 EXPECT_TRUE(cl.has_argv0());
301 EXPECT_EQ(std::string(), cl.argv0());
302 std::vector<CommandLine::Option> expected_options = {
303 CommandLine::Option("flag", "value")};
304 std::vector<std::string> expected_positional_args = {"--not-a-flag", "arg",
305 "--"};
306 EXPECT_EQ(expected_positional_args, cl.positional_args());
307 }
308 }
309
TEST(CommandLineTest,MultipleOccurrencesOfOption)310 TEST(CommandLineTest, MultipleOccurrencesOfOption) {
311 auto cl = CommandLineFromInitializerList(
312 {"my_program", "--flag1=value1", "--flag2=value2", "--flag1=value3"});
313 std::vector<CommandLine::Option> expected_options = {
314 CommandLine::Option("flag1", "value1"),
315 CommandLine::Option("flag2", "value2"),
316 CommandLine::Option("flag1", "value3")};
317 EXPECT_EQ("value3", cl.GetOptionValueWithDefault("flag1", "nope"));
318 EXPECT_EQ("value2", cl.GetOptionValueWithDefault("flag2", "nope"));
319 std::vector<std::string_view> values = cl.GetOptionValues("flag1");
320 ASSERT_EQ(2u, values.size());
321 EXPECT_EQ("value1", values[0]);
322 EXPECT_EQ("value3", values[1]);
323 }
324
325 // |cl1| and |cl2| should be not equal.
ExpectNotEqual(const char * message,std::initializer_list<std::string> c1,std::initializer_list<std::string> c2)326 void ExpectNotEqual(const char* message,
327 std::initializer_list<std::string> c1,
328 std::initializer_list<std::string> c2) {
329 SCOPED_TRACE(message);
330
331 const auto cl1 = CommandLineFromInitializerList(c1);
332 const auto cl2 = CommandLineFromInitializerList(c2);
333
334 // These are tautological.
335 EXPECT_TRUE(cl1 == cl1);
336 EXPECT_FALSE(cl1 != cl1);
337 EXPECT_TRUE(cl2 == cl2);
338 EXPECT_FALSE(cl2 != cl2);
339
340 // These rely on |cl1| not being equal to |cl2|.
341 EXPECT_FALSE(cl1 == cl2);
342 EXPECT_TRUE(cl1 != cl2);
343 EXPECT_FALSE(cl2 == cl1);
344 EXPECT_TRUE(cl2 != cl1);
345 }
346
ExpectEqual(const char * message,std::initializer_list<std::string> c1,std::initializer_list<std::string> c2)347 void ExpectEqual(const char* message,
348 std::initializer_list<std::string> c1,
349 std::initializer_list<std::string> c2) {
350 SCOPED_TRACE(message);
351
352 const auto cl1 = CommandLineFromInitializerList(c1);
353 const auto cl2 = CommandLineFromInitializerList(c2);
354
355 // These are tautological.
356 EXPECT_TRUE(cl1 == cl1);
357 EXPECT_FALSE(cl1 != cl1);
358 EXPECT_TRUE(cl2 == cl2);
359 EXPECT_FALSE(cl2 != cl2);
360
361 // These rely on |cl1| being equal to |cl2|.
362 EXPECT_TRUE(cl1 == cl2);
363 EXPECT_FALSE(cl1 != cl2);
364 EXPECT_TRUE(cl2 == cl1);
365 EXPECT_FALSE(cl2 != cl1);
366 }
367
TEST(CommandLineTest,ComparisonOperators)368 TEST(CommandLineTest, ComparisonOperators) {
369 ExpectNotEqual("1", {}, {""});
370 ExpectNotEqual("2", {"abc"}, {"def"});
371 ExpectNotEqual("3", {"abc", "--flag"}, {"abc"});
372 ExpectNotEqual("4", {"abc", "--flag1"}, {"abc", "--flag2"});
373 ExpectNotEqual("5", {"abc", "--flag1", "--flag2"}, {"abc", "--flag1"});
374 ExpectNotEqual("6", {"abc", "arg"}, {"abc"});
375 ExpectNotEqual("7", {"abc", "arg1"}, {"abc", "arg2"});
376 ExpectNotEqual("8", {"abc", "arg1", "arg2"}, {"abc", "arg1"});
377 ExpectNotEqual("9", {"abc", "--flag", "arg1"}, {"abc", "--flag", "arg2"});
378
379 // However, the presence of an unnecessary "--" shouldn't affect what's
380 // constructed.
381 ExpectEqual("10", {"abc", "--flag", "arg"}, {"abc", "--flag", "--", "arg"});
382 }
383
TEST(CommandLineTest,MoveAndCopy)384 TEST(CommandLineTest, MoveAndCopy) {
385 const auto cl = CommandLineFromInitializerList(
386 {"my_program", "--flag1=value1", "--flag2", "arg"});
387
388 // Copy constructor.
389 CommandLine cl2(cl);
390 EXPECT_EQ(cl, cl2);
391 // Check that |option_index_| gets copied too.
392 EXPECT_EQ("value1", cl2.GetOptionValueWithDefault("flag1", "nope"));
393
394 // Move constructor.
395 CommandLine cl3(std::move(cl2));
396 EXPECT_EQ(cl, cl3);
397 EXPECT_EQ("value1", cl3.GetOptionValueWithDefault("flag1", "nope"));
398
399 // Copy assignment.
400 CommandLine cl4;
401 EXPECT_NE(cl, cl4);
402 cl4 = cl;
403 EXPECT_EQ(cl, cl4);
404 EXPECT_EQ("value1", cl4.GetOptionValueWithDefault("flag1", "nope"));
405
406 // Move assignment.
407 CommandLine cl5;
408 EXPECT_NE(cl, cl5);
409 cl5 = std::move(cl4);
410 EXPECT_EQ(cl, cl5);
411 EXPECT_EQ("value1", cl5.GetOptionValueWithDefault("flag1", "nope"));
412 }
413
ToArgvHelper(const char * message,std::initializer_list<std::string> c)414 void ToArgvHelper(const char* message, std::initializer_list<std::string> c) {
415 SCOPED_TRACE(message);
416 std::vector<std::string> argv = c;
417 auto cl = CommandLineFromInitializerList(c);
418 EXPECT_EQ(argv, CommandLineToArgv(cl));
419 }
420
TEST(CommandLineTest,CommandLineToArgv)421 TEST(CommandLineTest, CommandLineToArgv) {
422 ToArgvHelper("1", {});
423 ToArgvHelper("2", {""});
424 ToArgvHelper("3", {"my_program"});
425 ToArgvHelper("4", {"my_program", "--flag"});
426 ToArgvHelper("5", {"my_program", "--flag1", "--flag2=value"});
427 ToArgvHelper("6", {"my_program", "arg"});
428 ToArgvHelper("7", {"my_program", "arg1", "arg2"});
429 ToArgvHelper("8", {"my_program", "--flag1", "--flag2=value", "arg1", "arg2"});
430 ToArgvHelper("9", {"my_program", "--flag", "--", "--not-a-flag"});
431 ToArgvHelper("10", {"my_program", "--flag", "arg", "--"});
432
433 // However, |CommandLineToArgv()| will "strip" an unneeded "--".
434 {
435 auto cl = CommandLineFromInitializerList({"my_program", "--"});
436 std::vector<std::string> argv = {"my_program"};
437 EXPECT_EQ(argv, CommandLineToArgv(cl));
438 }
439 {
440 auto cl =
441 CommandLineFromInitializerList({"my_program", "--flag", "--", "arg"});
442 std::vector<std::string> argv = {"my_program", "--flag", "arg"};
443 EXPECT_EQ(argv, CommandLineToArgv(cl));
444 }
445 }
446
447 } // namespace
448 } // namespace fml
449