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