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