xref: /aosp_15_r20/external/cronet/base/command_line_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
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 "base/command_line.h"
6 
7 #include <memory>
8 #include <string>
9 #include <string_view>
10 #include <vector>
11 
12 #include "base/debug/debugging_buildflags.h"
13 #include "base/files/file_path.h"
14 #include "base/strings/strcat.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "build/build_config.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 
21 #if BUILDFLAG(IS_WIN)
22 #include <windows.h>
23 
24 #include <shellapi.h>
25 
26 #include "base/win/scoped_localalloc.h"
27 #endif  // BUILDFLAG(IS_WIN)
28 
29 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
30 #include "base/run_loop.h"
31 #include "base/task/thread_pool.h"
32 #include "base/test/bind.h"
33 #include "base/test/task_environment.h"
34 #endif  // BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
35 
36 namespace base {
37 
38 #if BUILDFLAG(IS_WIN)
39 // To test Windows quoting behavior, we use a string that has some backslashes
40 // and quotes.
41 // Consider the command-line argument: q\"bs1\bs2\\bs3q\\\"
42 // Here it is with C-style escapes.
43 static const CommandLine::StringType kTrickyQuoted =
44     FILE_PATH_LITERAL("q\\\"bs1\\bs2\\\\bs3q\\\\\\\"");
45 #endif
46 
47 // It should be parsed by Windows as: q"bs1\bs2\\bs3q\"
48 // Here that is with C-style escapes.
49 static const CommandLine::StringType kTricky =
50     FILE_PATH_LITERAL("q\"bs1\\bs2\\\\bs3q\\\"");
51 
TEST(CommandLineTest,CommandLineConstructor)52 TEST(CommandLineTest, CommandLineConstructor) {
53   const CommandLine::CharType* argv[] = {
54       FILE_PATH_LITERAL("program"),
55       FILE_PATH_LITERAL("--foo="),
56       FILE_PATH_LITERAL("-bAr"),
57       FILE_PATH_LITERAL("-spaetzel=pierogi"),
58       FILE_PATH_LITERAL("-baz"),
59       FILE_PATH_LITERAL("flim"),
60       FILE_PATH_LITERAL("--other-switches=--dog=canine --cat=feline"),
61       FILE_PATH_LITERAL("-spaetzle=Crepe"),
62       FILE_PATH_LITERAL("-=loosevalue"),
63       FILE_PATH_LITERAL("-"),
64       FILE_PATH_LITERAL("FLAN"),
65       FILE_PATH_LITERAL("a"),
66       FILE_PATH_LITERAL("--input-translation=45--output-rotation"),
67       FILE_PATH_LITERAL("--"),
68       FILE_PATH_LITERAL("--"),
69       FILE_PATH_LITERAL("--not-a-switch"),
70       FILE_PATH_LITERAL("\"in the time of submarines...\""),
71       FILE_PATH_LITERAL("unquoted arg-with-space")};
72   CommandLine cl(std::size(argv), argv);
73 
74   EXPECT_FALSE(cl.GetCommandLineString().empty());
75   EXPECT_FALSE(cl.HasSwitch("cruller"));
76   EXPECT_FALSE(cl.HasSwitch("flim"));
77   EXPECT_FALSE(cl.HasSwitch("program"));
78   EXPECT_FALSE(cl.HasSwitch("dog"));
79   EXPECT_FALSE(cl.HasSwitch("cat"));
80   EXPECT_FALSE(cl.HasSwitch("output-rotation"));
81   EXPECT_FALSE(cl.HasSwitch("not-a-switch"));
82   EXPECT_FALSE(cl.HasSwitch("--"));
83 
84   EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")).value(),
85             cl.GetProgram().value());
86 
87   EXPECT_TRUE(cl.HasSwitch("foo"));
88 #if BUILDFLAG(IS_WIN)
89   EXPECT_TRUE(cl.HasSwitch("bar"));
90 #else
91   EXPECT_FALSE(cl.HasSwitch("bar"));
92 #endif
93   EXPECT_TRUE(cl.HasSwitch("baz"));
94   EXPECT_TRUE(cl.HasSwitch("spaetzle"));
95   EXPECT_TRUE(cl.HasSwitch("other-switches"));
96   EXPECT_TRUE(cl.HasSwitch("input-translation"));
97 
98   EXPECT_EQ("Crepe", cl.GetSwitchValueASCII("spaetzle"));
99   EXPECT_EQ("", cl.GetSwitchValueASCII("foo"));
100   EXPECT_EQ("", cl.GetSwitchValueASCII("bar"));
101   EXPECT_EQ("", cl.GetSwitchValueASCII("cruller"));
102   EXPECT_EQ("--dog=canine --cat=feline", cl.GetSwitchValueASCII(
103       "other-switches"));
104   EXPECT_EQ("45--output-rotation", cl.GetSwitchValueASCII("input-translation"));
105 
106   const CommandLine::StringVector& args = cl.GetArgs();
107   ASSERT_EQ(8U, args.size());
108 
109   auto iter = args.begin();
110   EXPECT_EQ(FILE_PATH_LITERAL("flim"), *iter);
111   ++iter;
112   EXPECT_EQ(FILE_PATH_LITERAL("-"), *iter);
113   ++iter;
114   EXPECT_EQ(FILE_PATH_LITERAL("FLAN"), *iter);
115   ++iter;
116   EXPECT_EQ(FILE_PATH_LITERAL("a"), *iter);
117   ++iter;
118   EXPECT_EQ(FILE_PATH_LITERAL("--"), *iter);
119   ++iter;
120   EXPECT_EQ(FILE_PATH_LITERAL("--not-a-switch"), *iter);
121   ++iter;
122   EXPECT_EQ(FILE_PATH_LITERAL("\"in the time of submarines...\""), *iter);
123   ++iter;
124   EXPECT_EQ(FILE_PATH_LITERAL("unquoted arg-with-space"), *iter);
125   ++iter;
126   EXPECT_TRUE(iter == args.end());
127 }
128 
TEST(CommandLineTest,CommandLineFromArgvWithoutProgram)129 TEST(CommandLineTest, CommandLineFromArgvWithoutProgram) {
130   CommandLine::StringVector argv = {FILE_PATH_LITERAL("--switch1"),
131                                     FILE_PATH_LITERAL("--switch2=value2")};
132 
133   CommandLine cl = CommandLine::FromArgvWithoutProgram(argv);
134 
135   EXPECT_EQ(base::FilePath(), cl.GetProgram());
136   EXPECT_TRUE(cl.HasSwitch("switch1"));
137   EXPECT_EQ("value2", cl.GetSwitchValueASCII("switch2"));
138 }
139 
TEST(CommandLineTest,CommandLineFromString)140 TEST(CommandLineTest, CommandLineFromString) {
141 #if BUILDFLAG(IS_WIN)
142   CommandLine cl = CommandLine::FromString(
143       L"program --foo= -bAr  /Spaetzel=pierogi /Baz flim "
144       L"--other-switches=\"--dog=canine --cat=feline\" "
145       L"-spaetzle=Crepe   -=loosevalue  FLAN "
146       L"--input-translation=\"45\"--output-rotation "
147       L"--quotes=" +
148       kTrickyQuoted +
149       L" -- -- --not-a-switch \"in the time of submarines...\"");
150 
151   EXPECT_FALSE(cl.GetCommandLineString().empty());
152   EXPECT_FALSE(cl.HasSwitch("cruller"));
153   EXPECT_FALSE(cl.HasSwitch("flim"));
154   EXPECT_FALSE(cl.HasSwitch("program"));
155   EXPECT_FALSE(cl.HasSwitch("dog"));
156   EXPECT_FALSE(cl.HasSwitch("cat"));
157   EXPECT_FALSE(cl.HasSwitch("output-rotation"));
158   EXPECT_FALSE(cl.HasSwitch("not-a-switch"));
159   EXPECT_FALSE(cl.HasSwitch("--"));
160 
161   EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")).value(),
162             cl.GetProgram().value());
163 
164   EXPECT_TRUE(cl.HasSwitch("foo"));
165   EXPECT_TRUE(cl.HasSwitch("bar"));
166   EXPECT_TRUE(cl.HasSwitch("baz"));
167   EXPECT_TRUE(cl.HasSwitch("spaetzle"));
168   EXPECT_TRUE(cl.HasSwitch("other-switches"));
169   EXPECT_TRUE(cl.HasSwitch("input-translation"));
170   EXPECT_TRUE(cl.HasSwitch("quotes"));
171 
172   EXPECT_EQ("Crepe", cl.GetSwitchValueASCII("spaetzle"));
173   EXPECT_EQ("", cl.GetSwitchValueASCII("foo"));
174   EXPECT_EQ("", cl.GetSwitchValueASCII("bar"));
175   EXPECT_EQ("", cl.GetSwitchValueASCII("cruller"));
176   EXPECT_EQ("--dog=canine --cat=feline", cl.GetSwitchValueASCII(
177       "other-switches"));
178   EXPECT_EQ("45--output-rotation", cl.GetSwitchValueASCII("input-translation"));
179   EXPECT_EQ(kTricky, cl.GetSwitchValueNative("quotes"));
180 
181   const CommandLine::StringVector& args = cl.GetArgs();
182   ASSERT_EQ(5U, args.size());
183 
184   std::vector<CommandLine::StringType>::const_iterator iter = args.begin();
185   EXPECT_EQ(FILE_PATH_LITERAL("flim"), *iter);
186   ++iter;
187   EXPECT_EQ(FILE_PATH_LITERAL("FLAN"), *iter);
188   ++iter;
189   EXPECT_EQ(FILE_PATH_LITERAL("--"), *iter);
190   ++iter;
191   EXPECT_EQ(FILE_PATH_LITERAL("--not-a-switch"), *iter);
192   ++iter;
193   EXPECT_EQ(FILE_PATH_LITERAL("in the time of submarines..."), *iter);
194   ++iter;
195   EXPECT_TRUE(iter == args.end());
196 
197   // Check that a generated string produces an equivalent command line.
198   CommandLine cl_duplicate = CommandLine::FromString(cl.GetCommandLineString());
199   EXPECT_EQ(cl.GetCommandLineString(), cl_duplicate.GetCommandLineString());
200 #endif
201 }
202 
203 // Tests behavior with an empty input string.
TEST(CommandLineTest,EmptyString)204 TEST(CommandLineTest, EmptyString) {
205 #if BUILDFLAG(IS_WIN)
206   CommandLine cl_from_string = CommandLine::FromString(std::wstring());
207   EXPECT_TRUE(cl_from_string.GetCommandLineString().empty());
208   EXPECT_TRUE(cl_from_string.GetProgram().empty());
209   EXPECT_EQ(1U, cl_from_string.argv().size());
210   EXPECT_TRUE(cl_from_string.GetArgs().empty());
211 #endif
212   CommandLine cl_from_argv(0, nullptr);
213   EXPECT_TRUE(cl_from_argv.GetCommandLineString().empty());
214   EXPECT_TRUE(cl_from_argv.GetProgram().empty());
215   EXPECT_EQ(1U, cl_from_argv.argv().size());
216   EXPECT_TRUE(cl_from_argv.GetArgs().empty());
217 }
218 
TEST(CommandLineTest,GetArgumentsString)219 TEST(CommandLineTest, GetArgumentsString) {
220   static const FilePath::CharType kPath1[] =
221       FILE_PATH_LITERAL("C:\\Some File\\With Spaces.ggg");
222   static const FilePath::CharType kPath2[] =
223       FILE_PATH_LITERAL("C:\\no\\spaces.ggg");
224 
225   static const char kFirstArgName[] = "first-arg";
226   static const char kSecondArgName[] = "arg2";
227   static const char kThirdArgName[] = "arg with space";
228   static const char kFourthArgName[] = "nospace";
229 
230   CommandLine cl(CommandLine::NO_PROGRAM);
231   cl.AppendSwitchPath(kFirstArgName, FilePath(kPath1));
232   cl.AppendSwitchPath(kSecondArgName, FilePath(kPath2));
233   cl.AppendArg(kThirdArgName);
234   cl.AppendArg(kFourthArgName);
235 
236 #if BUILDFLAG(IS_WIN)
237   CommandLine::StringType expected_first_arg(UTF8ToWide(kFirstArgName));
238   CommandLine::StringType expected_second_arg(UTF8ToWide(kSecondArgName));
239   CommandLine::StringType expected_third_arg(UTF8ToWide(kThirdArgName));
240   CommandLine::StringType expected_fourth_arg(UTF8ToWide(kFourthArgName));
241 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
242   CommandLine::StringType expected_first_arg(kFirstArgName);
243   CommandLine::StringType expected_second_arg(kSecondArgName);
244   CommandLine::StringType expected_third_arg(kThirdArgName);
245   CommandLine::StringType expected_fourth_arg(kFourthArgName);
246 #endif
247 
248 #if BUILDFLAG(IS_WIN)
249 #define QUOTE_ON_WIN FILE_PATH_LITERAL("\"")
250 #else
251 #define QUOTE_ON_WIN FILE_PATH_LITERAL("")
252 #endif  // BUILDFLAG(IS_WIN)
253 
254   CommandLine::StringType expected_str;
255   expected_str.append(FILE_PATH_LITERAL("--"))
256       .append(expected_first_arg)
257       .append(FILE_PATH_LITERAL("="))
258       .append(QUOTE_ON_WIN)
259       .append(kPath1)
260       .append(QUOTE_ON_WIN)
261       .append(FILE_PATH_LITERAL(" "))
262       .append(FILE_PATH_LITERAL("--"))
263       .append(expected_second_arg)
264       .append(FILE_PATH_LITERAL("="))
265       .append(QUOTE_ON_WIN)
266       .append(kPath2)
267       .append(QUOTE_ON_WIN)
268       .append(FILE_PATH_LITERAL(" "))
269       .append(QUOTE_ON_WIN)
270       .append(expected_third_arg)
271       .append(QUOTE_ON_WIN)
272       .append(FILE_PATH_LITERAL(" "))
273       .append(expected_fourth_arg);
274   EXPECT_EQ(expected_str, cl.GetArgumentsString());
275 }
276 
277 // Test methods for appending switches to a command line.
TEST(CommandLineTest,AppendSwitches)278 TEST(CommandLineTest, AppendSwitches) {
279   std::string switch1 = "switch1";
280   std::string switch2 = "switch2";
281   std::string value2 = "value";
282   std::string switch3 = "switch3";
283   std::string value3 = "a value with spaces";
284   std::string switch4 = "switch4";
285   std::string value4 = "\"a value with quotes\"";
286   std::string switch5 = "quotes";
287   CommandLine::StringType value5 = kTricky;
288 
289   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
290 
291   cl.AppendSwitch(switch1);
292   cl.AppendSwitchASCII(switch2, value2);
293   cl.AppendSwitchASCII(switch3, value3);
294   cl.AppendSwitchASCII(switch4, value4);
295   cl.AppendSwitchASCII(switch5, value4);
296   cl.AppendSwitchNative(switch5, value5);
297 
298   EXPECT_TRUE(cl.HasSwitch(switch1));
299   EXPECT_TRUE(cl.HasSwitch(switch2));
300   EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch2));
301   EXPECT_TRUE(cl.HasSwitch(switch3));
302   EXPECT_EQ(value3, cl.GetSwitchValueASCII(switch3));
303   EXPECT_TRUE(cl.HasSwitch(switch4));
304   EXPECT_EQ(value4, cl.GetSwitchValueASCII(switch4));
305   EXPECT_TRUE(cl.HasSwitch(switch5));
306   EXPECT_EQ(value5, cl.GetSwitchValueNative(switch5));
307 
308 #if BUILDFLAG(IS_WIN)
309   EXPECT_EQ(
310       L"Program "
311       L"--switch1 "
312       L"--switch2=value "
313       L"--switch3=\"a value with spaces\" "
314       L"--switch4=\"\\\"a value with quotes\\\"\" "
315       // Even though the switches are unique, appending can add repeat
316       // switches to argv.
317       L"--quotes=\"\\\"a value with quotes\\\"\" "
318       L"--quotes=\"" +
319           kTrickyQuoted + L"\"",
320       cl.GetCommandLineString());
321 #endif
322 }
323 
TEST(CommandLineTest,AppendSwitchesDashDash)324 TEST(CommandLineTest, AppendSwitchesDashDash) {
325   const CommandLine::CharType* const raw_argv[] = {FILE_PATH_LITERAL("prog"),
326                                                    FILE_PATH_LITERAL("--"),
327                                                    FILE_PATH_LITERAL("--arg1")};
328   CommandLine cl(std::size(raw_argv), raw_argv);
329 
330   cl.AppendSwitch("switch1");
331   cl.AppendSwitchASCII("switch2", "foo");
332 
333   cl.AppendArg("--arg2");
334 
335   EXPECT_EQ(FILE_PATH_LITERAL("prog --switch1 --switch2=foo -- --arg1 --arg2"),
336             cl.GetCommandLineString());
337   CommandLine::StringVector cl_argv = cl.argv();
338   EXPECT_EQ(FILE_PATH_LITERAL("prog"), cl_argv[0]);
339   EXPECT_EQ(FILE_PATH_LITERAL("--switch1"), cl_argv[1]);
340   EXPECT_EQ(FILE_PATH_LITERAL("--switch2=foo"), cl_argv[2]);
341   EXPECT_EQ(FILE_PATH_LITERAL("--"), cl_argv[3]);
342   EXPECT_EQ(FILE_PATH_LITERAL("--arg1"), cl_argv[4]);
343   EXPECT_EQ(FILE_PATH_LITERAL("--arg2"), cl_argv[5]);
344 }
345 
346 #if BUILDFLAG(IS_WIN)
347 struct CommandLineQuoteTestCase {
348   const wchar_t* const input_arg;
349   const wchar_t* const expected_output_arg;
350 };
351 
352 class CommandLineQuoteTest
353     : public ::testing::TestWithParam<CommandLineQuoteTestCase> {};
354 
355 INSTANTIATE_TEST_SUITE_P(
356     CommandLineQuoteTestCases,
357     CommandLineQuoteTest,
358     ::testing::ValuesIn(std::vector<CommandLineQuoteTestCase>{
359         {L"", L""},
360         {L"abc = xyz", LR"("abc = xyz")"},
361         {LR"(C:\AppData\Local\setup.exe)", LR"("C:\AppData\Local\setup.exe")"},
362         {LR"(C:\Program Files\setup.exe)", LR"("C:\Program Files\setup.exe")"},
363         {LR"("C:\Program Files\setup.exe")",
364          LR"("\"C:\Program Files\setup.exe\"")"},
365     }));
366 
TEST_P(CommandLineQuoteTest,TestCases)367 TEST_P(CommandLineQuoteTest, TestCases) {
368   EXPECT_EQ(CommandLine::QuoteForCommandLineToArgvW(GetParam().input_arg),
369             GetParam().expected_output_arg);
370 }
371 
372 struct CommandLineQuoteAfterTestCase {
373   const std::vector<std::wstring> input_args;
374   const wchar_t* const expected_output;
375 };
376 
377 class CommandLineQuoteAfterTest
378     : public ::testing::TestWithParam<CommandLineQuoteAfterTestCase> {};
379 
380 INSTANTIATE_TEST_SUITE_P(
381     CommandLineQuoteAfterTestCases,
382     CommandLineQuoteAfterTest,
383     ::testing::ValuesIn(std::vector<CommandLineQuoteAfterTestCase>{
384         {{L"abc=1"}, L"abc=1"},
385         {{L"abc=1", L"xyz=2"}, L"abc=1 xyz=2"},
386         {{L"abc=1", L"xyz=2", L"q"}, L"abc=1 xyz=2 q"},
387         {{L" abc=1  ", L"  xyz=2", L"q "}, L"abc=1 xyz=2 q"},
388         {{LR"("abc = 1")"}, LR"("abc = 1")"},
389         {{LR"(abc" = "1)", L"xyz=2"}, LR"("abc = 1" xyz=2)"},
390         {{LR"(abc" = "1)"}, LR"("abc = 1")"},
391         {{LR"(\\)", LR"(\\\")"}, LR"("\\\\" "\\\"")"},
392     }));
393 
394 TEST_P(CommandLineQuoteAfterTest, TestCases) {
395   std::wstring input_command_line =
396       base::StrCat({LR"(c:\test\process.exe )",
397                     base::JoinString(GetParam().input_args, L" ")});
398   int num_args = 0;
399   base::win::ScopedLocalAllocTyped<wchar_t*> argv(
400       ::CommandLineToArgvW(&input_command_line[0], &num_args));
401   ASSERT_EQ(num_args - 1U, GetParam().input_args.size());
402 
403   std::wstring recreated_command_line;
404   for (int i = 1; i < num_args; ++i) {
405     recreated_command_line.append(
406         CommandLine::QuoteForCommandLineToArgvW(argv.get()[i]));
407 
408     if (i + 1 < num_args) {
409       recreated_command_line.push_back(L' ');
410     }
411   }
412 
413   EXPECT_EQ(recreated_command_line, GetParam().expected_output);
414 }
415 
416 TEST(CommandLineTest, GetCommandLineStringForShell) {
417   CommandLine cl = CommandLine::FromString(
418       FILE_PATH_LITERAL("program --switch /switch2 --"));
419   EXPECT_EQ(
420       cl.GetCommandLineStringForShell(),
421       FILE_PATH_LITERAL("program --switch /switch2 -- --single-argument %1"));
422 }
423 
424 TEST(CommandLineTest, GetCommandLineStringWithUnsafeInsertSequences) {
425   CommandLine cl(FilePath(FILE_PATH_LITERAL("program")));
426   cl.AppendSwitchASCII("switch", "%1");
427   cl.AppendSwitch("%2");
428   cl.AppendArg("%3");
429   EXPECT_EQ(FILE_PATH_LITERAL("program --switch=%1 --%2 %3"),
430             cl.GetCommandLineStringWithUnsafeInsertSequences());
431 }
432 
433 TEST(CommandLineTest, HasSingleArgument) {
434   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
435   cl.AppendSwitchASCII("switch2", "foo");
436   EXPECT_FALSE(cl.HasSingleArgumentSwitch());
437   CommandLine cl_for_shell(
438       CommandLine::FromString(cl.GetCommandLineStringForShell()));
439   EXPECT_TRUE(cl_for_shell.HasSingleArgumentSwitch());
440 }
441 
442 // Test that creating a new command line from the string version of a single
443 // argument command line maintains the single argument switch, and the
444 // argument.
445 TEST(CommandLineTest, MaintainSingleArgument) {
446   // Putting a space in the file name will force escaping of the argument.
447   static const CommandLine::StringType kCommandLine =
448       FILE_PATH_LITERAL("program --switch --single-argument foo bar.html");
449   CommandLine cl = CommandLine::FromString(kCommandLine);
450   CommandLine cl_for_shell = CommandLine::FromString(cl.GetCommandLineString());
451   EXPECT_TRUE(cl_for_shell.HasSingleArgumentSwitch());
452   // Verify that we command line survives the round trip with an escaped arg.
453   EXPECT_EQ(kCommandLine, cl_for_shell.GetCommandLineString());
454 }
455 
456 #endif  // BUILDFLAG(IS_WIN)
457 
458 // Tests that when AppendArguments is called that the program is set correctly
459 // on the target CommandLine object and the switches from the source
460 // CommandLine are added to the target.
461 TEST(CommandLineTest, AppendArguments) {
462   CommandLine cl1(FilePath(FILE_PATH_LITERAL("Program")));
463   cl1.AppendSwitch("switch1");
464   cl1.AppendSwitchASCII("switch2", "foo");
465 
466   CommandLine cl2(CommandLine::NO_PROGRAM);
467   cl2.AppendArguments(cl1, true);
468   EXPECT_EQ(cl1.GetProgram().value(), cl2.GetProgram().value());
469   EXPECT_EQ(cl1.GetCommandLineString(), cl2.GetCommandLineString());
470 
471   CommandLine c1(FilePath(FILE_PATH_LITERAL("Program1")));
472   c1.AppendSwitch("switch1");
473   CommandLine c2(FilePath(FILE_PATH_LITERAL("Program2")));
474   c2.AppendSwitch("switch2");
475 
476   c1.AppendArguments(c2, true);
477   EXPECT_EQ(c1.GetProgram().value(), c2.GetProgram().value());
478   EXPECT_TRUE(c1.HasSwitch("switch1"));
479   EXPECT_TRUE(c1.HasSwitch("switch2"));
480 }
481 
482 #if BUILDFLAG(IS_WIN)
483 // Make sure that the command line string program paths are quoted as necessary.
484 // This only makes sense on Windows and the test is basically here to guard
485 // against regressions.
486 TEST(CommandLineTest, ProgramQuotes) {
487   // Check that quotes are not added for paths without spaces.
488   const FilePath kProgram(L"Program");
489   CommandLine cl_program(kProgram);
490   EXPECT_EQ(kProgram.value(), cl_program.GetProgram().value());
491   EXPECT_EQ(kProgram.value(), cl_program.GetCommandLineString());
492 
493   const FilePath kProgramPath(L"Program Path");
494 
495   // Check that quotes are not returned from GetProgram().
496   CommandLine cl_program_path(kProgramPath);
497   EXPECT_EQ(kProgramPath.value(), cl_program_path.GetProgram().value());
498 
499   // Check that quotes are added to command line string paths containing spaces.
500   CommandLine::StringType cmd_string(cl_program_path.GetCommandLineString());
501   EXPECT_EQ(L"\"Program Path\"", cmd_string);
502 }
503 #endif
504 
505 // Calling Init multiple times should not modify the previous CommandLine.
TEST(CommandLineTest,Init)506 TEST(CommandLineTest, Init) {
507   // Call Init without checking output once so we know it's been called
508   // whether or not the test runner does so.
509   CommandLine::Init(0, nullptr);
510   CommandLine* initial = CommandLine::ForCurrentProcess();
511   EXPECT_FALSE(CommandLine::Init(0, nullptr));
512   CommandLine* current = CommandLine::ForCurrentProcess();
513   EXPECT_EQ(initial, current);
514 }
515 
516 // Test that copies of CommandLine have a valid std::string_view map.
TEST(CommandLineTest,Copy)517 TEST(CommandLineTest, Copy) {
518   auto initial = std::make_unique<CommandLine>(CommandLine::NO_PROGRAM);
519   initial->AppendSwitch("a");
520   initial->AppendSwitch("bbbbbbbbbbbbbbb");
521   initial->AppendSwitch("c");
522   CommandLine copy_constructed(*initial);
523   CommandLine assigned = *initial;
524   CommandLine::SwitchMap switch_map = initial->GetSwitches();
525   initial.reset();
526   for (const auto& pair : switch_map)
527     EXPECT_TRUE(copy_constructed.HasSwitch(pair.first));
528   for (const auto& pair : switch_map)
529     EXPECT_TRUE(assigned.HasSwitch(pair.first));
530 }
531 
TEST(CommandLineTest,CopySwitches)532 TEST(CommandLineTest, CopySwitches) {
533   CommandLine source(CommandLine::NO_PROGRAM);
534   source.AppendSwitch("a");
535   source.AppendSwitch("bbbb");
536   source.AppendSwitch("c");
537   EXPECT_THAT(source.argv(), testing::ElementsAre(FILE_PATH_LITERAL(""),
538                                                   FILE_PATH_LITERAL("--a"),
539                                                   FILE_PATH_LITERAL("--bbbb"),
540                                                   FILE_PATH_LITERAL("--c")));
541 
542   CommandLine cl(CommandLine::NO_PROGRAM);
543   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("")));
544 
545   cl.CopySwitchesFrom(source, {});
546   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("")));
547 
548   static const char* const kSwitchesToCopy[] = {"a", "nosuch", "c"};
549   cl.CopySwitchesFrom(source, kSwitchesToCopy);
550   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL(""),
551                                               FILE_PATH_LITERAL("--a"),
552                                               FILE_PATH_LITERAL("--c")));
553 }
554 
TEST(CommandLineTest,Move)555 TEST(CommandLineTest, Move) {
556   static constexpr std::string_view kSwitches[] = {
557       "a",
558       "bbbbbbbbb",
559       "c",
560   };
561   static constexpr CommandLine::StringPieceType kArgs[] = {
562       FILE_PATH_LITERAL("beebop"),
563       FILE_PATH_LITERAL("alouie"),
564   };
565   CommandLine initial(CommandLine::NO_PROGRAM);
566   for (auto a_switch : kSwitches) {
567     initial.AppendSwitch(a_switch);
568   }
569   for (auto an_arg : kArgs) {
570     initial.AppendArgNative(an_arg);
571   }
572 
573   // Move construct and verify.
574   CommandLine move_constructed(std::move(initial));
575   initial = CommandLine(CommandLine::NO_PROGRAM);
576   for (auto a_switch : kSwitches) {
577     EXPECT_TRUE(move_constructed.HasSwitch(a_switch));
578   }
579   EXPECT_THAT(move_constructed.GetArgs(),
580               ::testing::ElementsAre(kArgs[0], kArgs[1]));
581 
582   // Move assign and verify
583   initial = std::move(move_constructed);
584   move_constructed = CommandLine(CommandLine::NO_PROGRAM);
585   for (auto a_switch : kSwitches) {
586     EXPECT_TRUE(initial.HasSwitch(a_switch));
587   }
588   EXPECT_THAT(initial.GetArgs(), ::testing::ElementsAre(kArgs[0], kArgs[1]));
589 }
590 
TEST(CommandLineTest,PrependSimpleWrapper)591 TEST(CommandLineTest, PrependSimpleWrapper) {
592   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
593   cl.AppendSwitch("a");
594   cl.AppendSwitch("b");
595   cl.PrependWrapper(FILE_PATH_LITERAL("wrapper --foo --bar"));
596 
597   EXPECT_EQ(6u, cl.argv().size());
598   EXPECT_EQ(FILE_PATH_LITERAL("wrapper"), cl.argv()[0]);
599   EXPECT_EQ(FILE_PATH_LITERAL("--foo"), cl.argv()[1]);
600   EXPECT_EQ(FILE_PATH_LITERAL("--bar"), cl.argv()[2]);
601   EXPECT_EQ(FILE_PATH_LITERAL("Program"), cl.argv()[3]);
602   EXPECT_EQ(FILE_PATH_LITERAL("--a"), cl.argv()[4]);
603   EXPECT_EQ(FILE_PATH_LITERAL("--b"), cl.argv()[5]);
604 }
605 
TEST(CommandLineTest,PrependComplexWrapper)606 TEST(CommandLineTest, PrependComplexWrapper) {
607   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
608   cl.AppendSwitch("a");
609   cl.AppendSwitch("b");
610   cl.PrependWrapper(
611       FILE_PATH_LITERAL("wrapper --foo='hello world' --bar=\"let's go\""));
612 
613   EXPECT_EQ(6u, cl.argv().size());
614   EXPECT_EQ(FILE_PATH_LITERAL("wrapper"), cl.argv()[0]);
615   EXPECT_EQ(FILE_PATH_LITERAL("--foo='hello world'"), cl.argv()[1]);
616   EXPECT_EQ(FILE_PATH_LITERAL("--bar=\"let's go\""), cl.argv()[2]);
617   EXPECT_EQ(FILE_PATH_LITERAL("Program"), cl.argv()[3]);
618   EXPECT_EQ(FILE_PATH_LITERAL("--a"), cl.argv()[4]);
619   EXPECT_EQ(FILE_PATH_LITERAL("--b"), cl.argv()[5]);
620 }
621 
TEST(CommandLineTest,RemoveSwitch)622 TEST(CommandLineTest, RemoveSwitch) {
623   const std::string switch1 = "switch1";
624   const std::string switch2 = "switch2";
625   const std::string value2 = "value";
626 
627   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
628 
629   cl.AppendSwitch(switch1);
630   cl.AppendSwitchASCII(switch2, value2);
631 
632   EXPECT_TRUE(cl.HasSwitch(switch1));
633   EXPECT_TRUE(cl.HasSwitch(switch2));
634   EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch2));
635   EXPECT_THAT(cl.argv(),
636               testing::ElementsAre(FILE_PATH_LITERAL("Program"),
637                                    FILE_PATH_LITERAL("--switch1"),
638                                    FILE_PATH_LITERAL("--switch2=value")));
639 
640   cl.RemoveSwitch(switch1);
641 
642   EXPECT_FALSE(cl.HasSwitch(switch1));
643   EXPECT_TRUE(cl.HasSwitch(switch2));
644   EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch2));
645   EXPECT_THAT(cl.argv(),
646               testing::ElementsAre(FILE_PATH_LITERAL("Program"),
647                                    FILE_PATH_LITERAL("--switch2=value")));
648 }
649 
TEST(CommandLineTest,RemoveSwitchWithValue)650 TEST(CommandLineTest, RemoveSwitchWithValue) {
651   const std::string switch1 = "switch1";
652   const std::string switch2 = "switch2";
653   const std::string value2 = "value";
654 
655   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
656 
657   cl.AppendSwitch(switch1);
658   cl.AppendSwitchASCII(switch2, value2);
659 
660   EXPECT_TRUE(cl.HasSwitch(switch1));
661   EXPECT_TRUE(cl.HasSwitch(switch2));
662   EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch2));
663   EXPECT_THAT(cl.argv(),
664               testing::ElementsAre(FILE_PATH_LITERAL("Program"),
665                                    FILE_PATH_LITERAL("--switch1"),
666                                    FILE_PATH_LITERAL("--switch2=value")));
667 
668   cl.RemoveSwitch(switch2);
669 
670   EXPECT_TRUE(cl.HasSwitch(switch1));
671   EXPECT_FALSE(cl.HasSwitch(switch2));
672   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program"),
673                                               FILE_PATH_LITERAL("--switch1")));
674 }
675 
TEST(CommandLineTest,RemoveSwitchDropsMultipleSameSwitches)676 TEST(CommandLineTest, RemoveSwitchDropsMultipleSameSwitches) {
677   const std::string switch1 = "switch1";
678   const std::string value2 = "value2";
679 
680   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
681 
682   cl.AppendSwitch(switch1);
683   cl.AppendSwitchASCII(switch1, value2);
684 
685   EXPECT_TRUE(cl.HasSwitch(switch1));
686   EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch1));
687   EXPECT_THAT(cl.argv(),
688               testing::ElementsAre(FILE_PATH_LITERAL("Program"),
689                                    FILE_PATH_LITERAL("--switch1"),
690                                    FILE_PATH_LITERAL("--switch1=value2")));
691 
692   cl.RemoveSwitch(switch1);
693 
694   EXPECT_FALSE(cl.HasSwitch(switch1));
695   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program")));
696 }
697 
TEST(CommandLineTest,AppendAndRemoveSwitchWithDefaultPrefix)698 TEST(CommandLineTest, AppendAndRemoveSwitchWithDefaultPrefix) {
699   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
700 
701   cl.AppendSwitch("foo");
702   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program"),
703                                               FILE_PATH_LITERAL("--foo")));
704   EXPECT_EQ(0u, cl.GetArgs().size());
705 
706   cl.RemoveSwitch("foo");
707   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program")));
708   EXPECT_EQ(0u, cl.GetArgs().size());
709 }
710 
TEST(CommandLineTest,AppendAndRemoveSwitchWithAlternativePrefix)711 TEST(CommandLineTest, AppendAndRemoveSwitchWithAlternativePrefix) {
712   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
713 
714   cl.AppendSwitch("-foo");
715   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program"),
716                                               FILE_PATH_LITERAL("-foo")));
717   EXPECT_EQ(0u, cl.GetArgs().size());
718 
719   cl.RemoveSwitch("foo");
720   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program")));
721   EXPECT_EQ(0u, cl.GetArgs().size());
722 }
723 
TEST(CommandLineTest,AppendAndRemoveSwitchPreservesOtherSwitchesAndArgs)724 TEST(CommandLineTest, AppendAndRemoveSwitchPreservesOtherSwitchesAndArgs) {
725   CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
726 
727   cl.AppendSwitch("foo");
728   cl.AppendSwitch("bar");
729   cl.AppendArg("arg");
730   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program"),
731                                               FILE_PATH_LITERAL("--foo"),
732                                               FILE_PATH_LITERAL("--bar"),
733                                               FILE_PATH_LITERAL("arg")));
734   EXPECT_THAT(cl.GetArgs(), testing::ElementsAre(FILE_PATH_LITERAL("arg")));
735 
736   cl.RemoveSwitch("foo");
737   EXPECT_THAT(cl.argv(), testing::ElementsAre(FILE_PATH_LITERAL("Program"),
738                                               FILE_PATH_LITERAL("--bar"),
739                                               FILE_PATH_LITERAL("arg")));
740   EXPECT_THAT(cl.GetArgs(), testing::ElementsAre(FILE_PATH_LITERAL("arg")));
741 }
742 
TEST(CommandLineTest,MultipleSameSwitch)743 TEST(CommandLineTest, MultipleSameSwitch) {
744   const CommandLine::CharType* argv[] = {
745       FILE_PATH_LITERAL("program"),
746       FILE_PATH_LITERAL("--foo=one"),  // --foo first time
747       FILE_PATH_LITERAL("-baz"),
748       FILE_PATH_LITERAL("--foo=two")  // --foo second time
749   };
750   CommandLine cl(std::size(argv), argv);
751 
752   EXPECT_TRUE(cl.HasSwitch("foo"));
753   EXPECT_TRUE(cl.HasSwitch("baz"));
754 
755   EXPECT_EQ("two", cl.GetSwitchValueASCII("foo"));
756 }
757 
758 // Helper class for the next test case
759 class MergeDuplicateFoosSemicolon : public DuplicateSwitchHandler {
760  public:
761   ~MergeDuplicateFoosSemicolon() override;
762 
763   void ResolveDuplicate(std::string_view key,
764                         CommandLine::StringPieceType new_value,
765                         CommandLine::StringType& out_value) override;
766 };
767 
768 MergeDuplicateFoosSemicolon::~MergeDuplicateFoosSemicolon() = default;
769 
ResolveDuplicate(std::string_view key,CommandLine::StringPieceType new_value,CommandLine::StringType & out_value)770 void MergeDuplicateFoosSemicolon::ResolveDuplicate(
771     std::string_view key,
772     CommandLine::StringPieceType new_value,
773     CommandLine::StringType& out_value) {
774   if (key != "mergeable-foo") {
775     out_value = CommandLine::StringType(new_value);
776     return;
777   }
778   if (!out_value.empty()) {
779 #if BUILDFLAG(IS_WIN)
780     StrAppend(&out_value, {L";"});
781 #else
782     StrAppend(&out_value, {";"});
783 #endif
784   }
785   StrAppend(&out_value, {new_value});
786 }
787 
788 // This flag is an exception to the rule that the second duplicate flag wins
789 // Not thread safe
TEST(CommandLineTest,MultipleFilterFileSwitch)790 TEST(CommandLineTest, MultipleFilterFileSwitch) {
791   const CommandLine::CharType* const argv[] = {
792       FILE_PATH_LITERAL("program"),
793       FILE_PATH_LITERAL("--mergeable-foo=one"),  // --first time
794       FILE_PATH_LITERAL("-baz"),
795       FILE_PATH_LITERAL("--mergeable-foo=two")  // --second time
796   };
797   CommandLine::SetDuplicateSwitchHandler(
798       std::make_unique<MergeDuplicateFoosSemicolon>());
799 
800   CommandLine cl(std::size(argv), argv);
801 
802   EXPECT_TRUE(cl.HasSwitch("mergeable-foo"));
803   EXPECT_TRUE(cl.HasSwitch("baz"));
804 
805   EXPECT_EQ("one;two", cl.GetSwitchValueASCII("mergeable-foo"));
806   CommandLine::SetDuplicateSwitchHandler(nullptr);
807 }
808 
809 #if BUILDFLAG(IS_WIN)
TEST(CommandLineTest,ParseAsSingleArgument)810 TEST(CommandLineTest, ParseAsSingleArgument) {
811   CommandLine cl = CommandLine::FromString(
812       FILE_PATH_LITERAL("program --switch_before arg_before "
813                         "--single-argument arg with spaces \"and quotes\" \""));
814 
815   EXPECT_FALSE(cl.GetCommandLineString().empty());
816   EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")), cl.GetProgram());
817   EXPECT_TRUE(cl.HasSwitch("switch_before"));
818   EXPECT_EQ(cl.GetArgs(), CommandLine::StringVector({FILE_PATH_LITERAL(
819                               "arg with spaces \"and quotes\" \"")}));
820 
821   CommandLine cl_without_arg =
822       CommandLine::FromString(FILE_PATH_LITERAL("program --single-argument "));
823 
824   EXPECT_FALSE(cl_without_arg.GetCommandLineString().empty());
825   EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")),
826             cl_without_arg.GetProgram());
827   EXPECT_TRUE(cl_without_arg.GetArgs().empty());
828 }
829 #endif  // BUILDFLAG(IS_WIN)
830 
831 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
TEST(CommandLineDeathTest,ThreadChecks)832 TEST(CommandLineDeathTest, ThreadChecks) {
833   test::TaskEnvironment task_environment;
834   RunLoop run_loop;
835   EXPECT_DEATH_IF_SUPPORTED(
836       {
837         ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&run_loop]() {
838                                auto* command_line =
839                                    CommandLine::ForCurrentProcess();
840                                command_line->AppendSwitch("test");
841                                run_loop.Quit();
842                              }));
843 
844         run_loop.Run();
845       },
846       "");
847 }
848 #endif  // BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
849 
850 }  // namespace base
851