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