chromium/base/command_line_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/command_line.h"

#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "base/debug/debugging_buildflags.h"
#include "base/files/file_path.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include <shellapi.h>

#include "base/win/scoped_localalloc.h"
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
#include "base/run_loop.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#endif  // BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)

namespace base {

#if BUILDFLAG(IS_WIN)
// To test Windows quoting behavior, we use a string that has some backslashes
// and quotes.
// Consider the command-line argument: q\"bs1\bs2\\bs3q\\\"
// Here it is with C-style escapes.
static const CommandLine::StringType kTrickyQuoted =
    FILE_PATH_LITERAL("q\\\"bs1\\bs2\\\\bs3q\\\\\\\"");
#endif

// It should be parsed by Windows as: q"bs1\bs2\\bs3q\"
// Here that is with C-style escapes.
static const CommandLine::StringType kTricky =);

TEST(CommandLineTest, CommandLineConstructor) {}

TEST(CommandLineTest, CommandLineFromArgvWithoutProgram) {}

TEST(CommandLineTest, CommandLineFromString) {}

// Tests behavior with an empty input string.
TEST(CommandLineTest, EmptyString) {}

TEST(CommandLineTest, GetArgumentsString) {}

// Test methods for appending switches to a command line.
TEST(CommandLineTest, AppendSwitches) {}

TEST(CommandLineTest, AppendSwitchesDashDash) {}

#if BUILDFLAG(IS_WIN)
struct CommandLineQuoteTestCase {
  const wchar_t* const input_arg;
  const wchar_t* const expected_output_arg;
};

class CommandLineQuoteTest
    : public ::testing::TestWithParam<CommandLineQuoteTestCase> {};

INSTANTIATE_TEST_SUITE_P(
    CommandLineQuoteTestCases,
    CommandLineQuoteTest,
    ::testing::ValuesIn(std::vector<CommandLineQuoteTestCase>{
        {L"", L""},
        {L"abc = xyz", LR"("abc = xyz")"},
        {LR"(C:\AppData\Local\setup.exe)", LR"("C:\AppData\Local\setup.exe")"},
        {LR"(C:\Program Files\setup.exe)", LR"("C:\Program Files\setup.exe")"},
        {LR"("C:\Program Files\setup.exe")",
         LR"("\"C:\Program Files\setup.exe\"")"},
    }));

TEST_P(CommandLineQuoteTest, TestCases) {
  EXPECT_EQ(CommandLine::QuoteForCommandLineToArgvW(GetParam().input_arg),
            GetParam().expected_output_arg);
}

struct CommandLineQuoteAfterTestCase {
  const std::vector<std::wstring> input_args;
  const wchar_t* const expected_output;
};

class CommandLineQuoteAfterTest
    : public ::testing::TestWithParam<CommandLineQuoteAfterTestCase> {};

INSTANTIATE_TEST_SUITE_P(
    CommandLineQuoteAfterTestCases,
    CommandLineQuoteAfterTest,
    ::testing::ValuesIn(std::vector<CommandLineQuoteAfterTestCase>{
        {{L"abc=1"}, L"abc=1"},
        {{L"abc=1", L"xyz=2"}, L"abc=1 xyz=2"},
        {{L"abc=1", L"xyz=2", L"q"}, L"abc=1 xyz=2 q"},
        {{L" abc=1  ", L"  xyz=2", L"q "}, L"abc=1 xyz=2 q"},
        {{LR"("abc = 1")"}, LR"("abc = 1")"},
        {{LR"(abc" = "1)", L"xyz=2"}, LR"("abc = 1" xyz=2)"},
        {{LR"(abc" = "1)"}, LR"("abc = 1")"},
        {{LR"(\\)", LR"(\\\")"}, LR"("\\\\" "\\\"")"},
    }));

TEST_P(CommandLineQuoteAfterTest, TestCases) {
  std::wstring input_command_line =
      base::StrCat({LR"(c:\test\process.exe )",
                    base::JoinString(GetParam().input_args, L" ")});
  int num_args = 0;
  base::win::ScopedLocalAllocTyped<wchar_t*> argv(
      ::CommandLineToArgvW(&input_command_line[0], &num_args));
  ASSERT_EQ(num_args - 1U, GetParam().input_args.size());

  std::wstring recreated_command_line;
  for (int i = 1; i < num_args; ++i) {
    recreated_command_line.append(
        CommandLine::QuoteForCommandLineToArgvW(argv.get()[i]));

    if (i + 1 < num_args) {
      recreated_command_line.push_back(L' ');
    }
  }

  EXPECT_EQ(recreated_command_line, GetParam().expected_output);
}

TEST(CommandLineTest, GetCommandLineStringForShell) {
  CommandLine cl = CommandLine::FromString(
      FILE_PATH_LITERAL("program --switch /switch2 --"));
  EXPECT_EQ(
      cl.GetCommandLineStringForShell(),
      FILE_PATH_LITERAL("program --switch /switch2 -- --single-argument %1"));
}

TEST(CommandLineTest, GetCommandLineStringWithUnsafeInsertSequences) {
  CommandLine cl(FilePath(FILE_PATH_LITERAL("program")));
  cl.AppendSwitchASCII("switch", "%1");
  cl.AppendSwitch("%2");
  cl.AppendArg("%3");
  EXPECT_EQ(FILE_PATH_LITERAL("program --switch=%1 --%2 %3"),
            cl.GetCommandLineStringWithUnsafeInsertSequences());
}

TEST(CommandLineTest, HasSingleArgument) {
  CommandLine cl(FilePath(FILE_PATH_LITERAL("Program")));
  cl.AppendSwitchASCII("switch2", "foo");
  EXPECT_FALSE(cl.HasSingleArgumentSwitch());
  CommandLine cl_for_shell(
      CommandLine::FromString(cl.GetCommandLineStringForShell()));
  EXPECT_TRUE(cl_for_shell.HasSingleArgumentSwitch());
}

// Test that creating a new command line from the string version of a single
// argument command line maintains the single argument switch, and the
// argument.
TEST(CommandLineTest, MaintainSingleArgument) {
  // Putting a space in the file name will force escaping of the argument.
  static const CommandLine::StringType kCommandLine =
      FILE_PATH_LITERAL("program --switch --single-argument foo bar.html");
  CommandLine cl = CommandLine::FromString(kCommandLine);
  CommandLine cl_for_shell = CommandLine::FromString(cl.GetCommandLineString());
  EXPECT_TRUE(cl_for_shell.HasSingleArgumentSwitch());
  // Verify that we command line survives the round trip with an escaped arg.
  EXPECT_EQ(kCommandLine, cl_for_shell.GetCommandLineString());
}

#endif  // BUILDFLAG(IS_WIN)

// Tests that when AppendArguments is called that the program is set correctly
// on the target CommandLine object and the switches from the source
// CommandLine are added to the target.
TEST(CommandLineTest, AppendArguments) {}

#if BUILDFLAG(IS_WIN)
// Make sure that the command line string program paths are quoted as necessary.
// This only makes sense on Windows and the test is basically here to guard
// against regressions.
TEST(CommandLineTest, ProgramQuotes) {
  // Check that quotes are not added for paths without spaces.
  const FilePath kProgram(L"Program");
  CommandLine cl_program(kProgram);
  EXPECT_EQ(kProgram.value(), cl_program.GetProgram().value());
  EXPECT_EQ(kProgram.value(), cl_program.GetCommandLineString());

  const FilePath kProgramPath(L"Program Path");

  // Check that quotes are not returned from GetProgram().
  CommandLine cl_program_path(kProgramPath);
  EXPECT_EQ(kProgramPath.value(), cl_program_path.GetProgram().value());

  // Check that quotes are added to command line string paths containing spaces.
  CommandLine::StringType cmd_string(cl_program_path.GetCommandLineString());
  EXPECT_EQ(L"\"Program Path\"", cmd_string);
}
#endif

// Calling Init multiple times should not modify the previous CommandLine.
TEST(CommandLineTest, Init) {}

// Test that copies of CommandLine have a valid std::string_view map.
TEST(CommandLineTest, Copy) {}

TEST(CommandLineTest, CopySwitches) {}

TEST(CommandLineTest, Move) {}

TEST(CommandLineTest, PrependSimpleWrapper) {}

TEST(CommandLineTest, PrependComplexWrapper) {}

TEST(CommandLineTest, RemoveSwitch) {}

TEST(CommandLineTest, RemoveSwitchWithValue) {}

TEST(CommandLineTest, RemoveSwitchDropsMultipleSameSwitches) {}

TEST(CommandLineTest, AppendAndRemoveSwitchWithDefaultPrefix) {}

TEST(CommandLineTest, AppendAndRemoveSwitchWithAlternativePrefix) {}

TEST(CommandLineTest, AppendAndRemoveSwitchPreservesOtherSwitchesAndArgs) {}

TEST(CommandLineTest, MultipleSameSwitch) {}

// Helper class for the next test case
class MergeDuplicateFoosSemicolon : public DuplicateSwitchHandler {};

MergeDuplicateFoosSemicolon::~MergeDuplicateFoosSemicolon() = default;

void MergeDuplicateFoosSemicolon::ResolveDuplicate(
    std::string_view key,
    CommandLine::StringViewType new_value,
    CommandLine::StringType& out_value) {}

// This flag is an exception to the rule that the second duplicate flag wins
// Not thread safe
TEST(CommandLineTest, MultipleFilterFileSwitch) {}

#if BUILDFLAG(IS_WIN)
TEST(CommandLineTest, ParseAsSingleArgument) {
  CommandLine cl = CommandLine::FromString(
      FILE_PATH_LITERAL("program --switch_before arg_before "
                        "--single-argument arg with spaces \"and quotes\" \""));

  EXPECT_FALSE(cl.GetCommandLineString().empty());
  EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")), cl.GetProgram());
  EXPECT_TRUE(cl.HasSwitch("switch_before"));
  EXPECT_EQ(cl.GetArgs(), CommandLine::StringVector({FILE_PATH_LITERAL(
                              "arg with spaces \"and quotes\" \"")}));

  CommandLine cl_without_arg =
      CommandLine::FromString(FILE_PATH_LITERAL("program --single-argument "));

  EXPECT_FALSE(cl_without_arg.GetCommandLineString().empty());
  EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")),
            cl_without_arg.GetProgram());
  EXPECT_TRUE(cl_without_arg.GetArgs().empty());
}
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
TEST(CommandLineDeathTest, ThreadChecks) {}
#endif  // BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)

}  // namespace base