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