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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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. |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |