// Copyright 2018 Bloomberg Finance L.P
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <env.h>
#include <gtest/gtest.h>
#include <parsedcommand.h>
#include <parsedcommandfactory.h>

using namespace recc;

TEST(CommandBasenameTest, EmptyString)
{
    EXPECT_EQ(ParsedCommand::commandBasename(""), "");
}

TEST(CommandBasenameTest, TrivialCommands)
{
    EXPECT_EQ(ParsedCommand::commandBasename("gcc"), "gcc");
    EXPECT_EQ(ParsedCommand::commandBasename("g++"), "g++");
    EXPECT_EQ(ParsedCommand::commandBasename("CC"), "CC");
    EXPECT_EQ(ParsedCommand::commandBasename("clang"), "clang");
    EXPECT_EQ(ParsedCommand::commandBasename("clang++"), "clang++");
}

TEST(CommandBasenameTest, CommandsWithVersions)
{
    EXPECT_EQ(ParsedCommand::commandBasename("gcc-4.7"), "gcc");
    EXPECT_EQ(ParsedCommand::commandBasename("CC++-99"), "CC++");
    EXPECT_EQ(ParsedCommand::commandBasename("clang-6.0"), "clang");
    EXPECT_EQ(ParsedCommand::commandBasename("clang++-6.0"), "clang++");
}

TEST(CommandBasenameTest, CommandsAtPaths)
{
    EXPECT_EQ(ParsedCommand::commandBasename("/usr/bin/gcc"), "gcc");
    EXPECT_EQ(ParsedCommand::commandBasename("/usr/bin/g++"), "g++");
    EXPECT_EQ(ParsedCommand::commandBasename("/CC++-99"), "CC++");
    EXPECT_EQ(ParsedCommand::commandBasename("/usr/bin/clang"), "clang");
    EXPECT_EQ(ParsedCommand::commandBasename("/usr/bin/clang++"), "clang++");

#ifdef RECC_PLATFORM_COMPILER
    EXPECT_EQ(ParsedCommand::commandBasename("/foo/bin/cc"),
              RECC_PLATFORM_COMPILER);
    EXPECT_EQ(ParsedCommand::commandBasename("/foo/bin/c99"),
              RECC_PLATFORM_COMPILER);
#endif

    // Expect the `cc` symlink to be resolved
    EXPECT_EQ(ParsedCommand::commandBasename("./cc"), "foocc");
}

TEST(IsCompilerTest, Empty)
{
    auto parsedCommand = ParsedCommandFactory::createParsedCommand({});
    EXPECT_FALSE(parsedCommand.is_compiler_command());
}

TEST(IsCompilerTest, CompilerParsedCommandPath)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {"/usr/local/bin/gcc", "-c", "test/file.cpp"});
    EXPECT_TRUE(command.is_compiler_command());

    command = ParsedCommandFactory::createParsedCommand(
        {"/usr/local/bin/clang", "-c", "test/file.cpp"});
    EXPECT_TRUE(command.is_compiler_command());
}

TEST(IsCompilerTest, NotCompiler)
{
    auto command =
        ParsedCommandFactory::createParsedCommand({"cat", "something.c"});
    EXPECT_FALSE(command.is_compiler_command());

    command = ParsedCommandFactory::createParsedCommand(
        {"clang-format", "something.c"});
    EXPECT_FALSE(command.is_compiler_command());
}

TEST(IsCompilerTest, EmptyString)
{
    auto command = ParsedCommandFactory::createParsedCommand({""});
    EXPECT_FALSE(command.is_compiler_command());
}

void nonCompilerCommands(const char *gcc_or_clang)
{
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand({gcc_or_clang})
                     .is_compiler_command());
    EXPECT_FALSE(
        ParsedCommandFactory::createParsedCommand({gcc_or_clang, "-version"})
            .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {gcc_or_clang, "-dumpmachine"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {gcc_or_clang, "hello.c", "-o", "hello"})
                     .is_compiler_command());
}

TEST(GccClangTest, NonCompilerCommands)
{
    nonCompilerCommands("gcc");
    nonCompilerCommands("clang");
}

void simpleCommand(const char *gcc_or_clang)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {gcc_or_clang, "-c", "hello.c"});
    std::vector<std::string> expectedCommand = {gcc_or_clang, "-c", "hello.c"};
    std::vector<std::string> expectedDepsCommand = {gcc_or_clang, "-c",
                                                    "hello.c", "-M"};
    if (RECC_DEPS_GLOBAL_PATHS && command.is_clang()) {
        expectedDepsCommand.emplace_back("-v");
    }
    std::set<std::string> expectedProducts = {};

    ASSERT_TRUE(command.is_compiler_command());
    EXPECT_EQ(command.get_command(), expectedCommand);
    EXPECT_EQ(command.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_FALSE(command.produces_sun_make_rules());
}

TEST(GccClangTest, SimpleCommand)
{
    simpleCommand("gcc");
    simpleCommand("clang");
}

void outputArgument(const char *gcc_or_clang)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {gcc_or_clang, "-c", "hello.c", "-o", "hello.o"});
    std::vector<std::string> expectedCommand = {gcc_or_clang, "-c", "hello.c",
                                                "-o", "hello.o"};
    std::vector<std::string> expectedDepsCommand = {gcc_or_clang, "-c",
                                                    "hello.c", "-M"};
    if (RECC_DEPS_GLOBAL_PATHS && command.is_clang()) {
        expectedDepsCommand.emplace_back("-v");
    }
    std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(command.is_compiler_command());
    EXPECT_EQ(command.get_command(), expectedCommand);
    EXPECT_EQ(command.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_FALSE(command.produces_sun_make_rules());
}

TEST(GccClangTest, OutputArgument)
{
    outputArgument("gcc");
    outputArgument("clang");
}

void outputArgumentNoSpace(const char *gcc_or_clang)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {gcc_or_clang, "-c", "-ohello.o", "hello.c"});
    std::vector<std::string> expectedCommand = {gcc_or_clang, "-c",
                                                "-ohello.o", "hello.c"};
    std::vector<std::string> expectedDepsCommand = {gcc_or_clang, "-c",
                                                    "hello.c", "-M"};
    if (RECC_DEPS_GLOBAL_PATHS && command.is_clang()) {
        expectedDepsCommand.emplace_back("-v");
    }
    std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(command.is_compiler_command());
    EXPECT_EQ(command.get_command(), expectedCommand);
    EXPECT_EQ(command.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_FALSE(command.produces_sun_make_rules());
}

TEST(GccClangTest, OutputArgumentNoSpace)
{
    outputArgumentNoSpace("gcc");
    outputArgumentNoSpace("clang");
}

void preprocessorArguments(const char *gcc_or_clang)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {gcc_or_clang, "-c", "-Xpreprocessor", "-MMD", "hello.c",
         "-Wp,hello.d,-MV,-valid", "-ohello.o"});

    std::vector<std::string> expectedDepsCommand = {
        gcc_or_clang, "-c", "hello.c", "-Xpreprocessor", "-valid", "-M"};

    if (RECC_DEPS_GLOBAL_PATHS && command.is_clang()) {
        expectedDepsCommand.emplace_back("-v");
    }

    std::set<std::string> expectedProducts = {"hello.o"};
    std::set<std::string> expectedDepsProducts = {"hello.d"};

    ASSERT_TRUE(command.is_compiler_command());
    EXPECT_EQ(command.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_EQ(command.get_deps_products(), expectedDepsProducts);
    EXPECT_FALSE(command.produces_sun_make_rules());
}

TEST(GccClangTest, PreprocessorArguments)
{
    preprocessorArguments("gcc");
    preprocessorArguments("clang");
}

TEST(GccClangTest, RecogniseClang)
{
    EXPECT_FALSE(
        ParsedCommandFactory::createParsedCommand({"gcc"}).is_clang());
    EXPECT_FALSE(
        ParsedCommandFactory::createParsedCommand({"g++"}).is_clang());

    EXPECT_TRUE(
        ParsedCommandFactory::createParsedCommand({"clang"}).is_clang());
    EXPECT_TRUE(
        ParsedCommandFactory::createParsedCommand({"clang++"}).is_clang());
    EXPECT_TRUE(
        ParsedCommandFactory::createParsedCommand({"clang-6.0"}).is_clang());
    EXPECT_TRUE(
        ParsedCommandFactory::createParsedCommand({"clang++-6.0"}).is_clang());
}

TEST(GccClangTest, PreprocessorOnlyArguments)
{
    const auto compilers = {"clang", "gcc"};
    for (auto compiler : compilers) {
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-M"})
                         .is_compiler_command());
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-MM"})
                         .is_compiler_command());
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-E"})
                         .is_compiler_command());
    }
}

TEST(GccClangTest, UnsupportedOutputs)
{
    const auto compilers = {"clang", "gcc"};
    for (auto compiler : compilers) {
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-save-temps"})
                         .is_compiler_command());
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-save-temps=cwd"})
                         .is_compiler_command());
        EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                         {compiler, "-c", "hello.c", "-fdump-go-spec"})
                         .is_compiler_command());
    }
}

TEST(SolarisCCTest, NonCompilerCommands)
{
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand({"CC"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "hello.c", "-o", "hello"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "hello.c", "-c", "-###"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "hello.c", "-c", "-xprofile=test"})
                     .is_compiler_command());
}

TEST(SolarisCCTest, OutputArgument)
{
    auto command = ParsedCommandFactory::createParsedCommand(
        {"CC", "-c", "hello.c", "-o", "hello.o"});
    std::vector<std::string> expectedCommand = {"CC", "-c", "hello.c", "-o",
                                                "hello.o"};
    std::vector<std::string> expectedDepsCommand = {"CC", "-c", "hello.c",
                                                    "-xM"};
    std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(command.is_compiler_command());
    EXPECT_EQ(command.get_command(), expectedCommand);
    EXPECT_EQ(command.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_TRUE(command.produces_sun_make_rules());
}

TEST(SolarisCCTest, PreprocessorOnlyArguments)
{
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "-c", "hello.c", "-xM"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "-c", "hello.c", "-xM1"})
                     .is_compiler_command());
    EXPECT_FALSE(ParsedCommandFactory::createParsedCommand(
                     {"CC", "-c", "hello.c", "-E"})
                     .is_compiler_command());
}

TEST(RewriteAbsolutePathsTest, SimpleCompileCommand)
{
    RECC_PROJECT_ROOT = "/home/nobody/test/";
    const std::vector<std::string> command = {
        "gcc", "-c", "/home/nobody/test/hello.c", "-o",
        "/home/nobody/test/hello.o"};
    auto parsedCommand = ParsedCommandFactory::createParsedCommand(
        command, "/home/nobody/test");

    const std::vector<std::string> expectedCommand = {"gcc", "-c", "hello.c",
                                                      "-o", "hello.o"};
    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/nobody/test/hello.c", "-M"};
    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(NoPathRewriteTest, SimpleCompileCommand)
{
    RECC_PROJECT_ROOT = "/home/nobody/test/";
    RECC_NO_PATH_REWRITE = true;
    const std::vector<std::string> command = {
        "gcc", "-c", "/home/nobody/test/hello.c", "-o",
        "/home/nobody/test/hello.o"};
    auto parsedCommand = ParsedCommandFactory::createParsedCommand(
        command, "/home/nobody/test");

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/nobody/test/hello.c", "-M"};
    const std::set<std::string> expectedProducts = {
        "/home/nobody/test/hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), command);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    RECC_NO_PATH_REWRITE = false;
}

TEST(RewriteAbsolutePathsTest, ComplexOptions)
{
    RECC_PROJECT_ROOT = "/home/nobody/";
    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "/home/nobody/test/hello.c",
        "-o",
        "/home/nobody/test/hello.o",
        "-I/home/nobody/headers",
        "-I",
        "/home/nobody/test/moreheaders/",
        "-Wp,-I,/home/nobody/evenmoreheaders",
        "-Xpreprocessor",
        "-I",
        "-Xpreprocessor",
        "/usr/include/something"};
    auto parsedCommand = ParsedCommandFactory::createParsedCommand(
        command, "/home/nobody/test");

    const std::vector<std::string> expectedCommand = {
        "gcc",
        "-c",
        "hello.c",
        "-o",
        "hello.o",
        "-I../headers",
        "-I",
        "moreheaders",
        "-Xpreprocessor",
        "-I",
        "-Xpreprocessor",
        "../evenmoreheaders",
        "-Xpreprocessor",
        "-I",
        "-Xpreprocessor",
        "/usr/include/something"};
    const std::vector<std::string> expectedDepsCommand = {
        "gcc",
        "-c",
        "/home/nobody/test/hello.c",
        "-I/home/nobody/headers",
        "-I",
        "/home/nobody/test/moreheaders/",
        "-Xpreprocessor",
        "-I",
        "-Xpreprocessor",
        "/home/nobody/evenmoreheaders",
        "-Xpreprocessor",
        "-I",
        "-Xpreprocessor",
        "/usr/include/something",
        "-M"};
    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(ReplacePathTest, SimpleRewrite)
{
    RECC_PREFIX_REPLACEMENT = {{"/usr/bin/include", "/include"}};
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "gcc", "-c", "hello.c", "-I/usr/bin/include/headers", "-o", "hello.o"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    // -I should be replaced.
    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "hello.c", "-I/include/headers", "-o", "hello.o"};

    // Deps command shouldn't be rewritten.
    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "hello.c", "-I/usr/bin/include/headers", "-M"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(ReplacePathTest, NormalizeNonCompilerPaths)
{
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "./gcc", "-c",       "./hello.c", "-I/usr/../usr/bin/include/headers",
        "-o",    "./hello.o"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    // Normalization occurs in the actual command
    const std::vector<std::string> expectedCommand = {
        "./gcc", "-c",     "hello.c", "-I/usr/bin/include/headers",
        "-o",    "hello.o"};

    // Deps command shouldn't be normalized.
    const std::vector<std::string> expectedDepsCommand = {
        "./gcc", "-c", "./hello.c", "-I/usr/../usr/bin/include/headers", "-M"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(ReplacePathTest, PathInProjectRoot)
{
    // Path to replace is inside project root, the behavior expected is:
    // Path replaced by path in PREFIX_MAP, then if still relative to
    // PROJECT_ROOT Replaced again to be made relative.
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc", "-c",     "hello.c", "-I/home/usr/bin/include/headers",
        "-o",  "hello.o"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    // Deps command shouldn't be rewritten.
    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "hello.c", "-I/home/usr/bin/include/headers", "-M"};

    // -I should be replaced, and made relative
    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "hello.c", "-Ibin/include/headers", "-o", "hello.o"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(ReplacePathTest, SimpleCompilePathReplacement)
{
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    const std::vector<std::string> command = {"gcc", "-c",
                                              "/home/usr/bin/hello.c"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "");

    // Deps command shouldn't be rewritten.
    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/usr/bin/hello.c", "-M"};

    const std::vector<std::string> expectedCommand = {"gcc", "-c",
                                                      "/home/bin/hello.c"};
    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
}

TEST(ReplacePathTest, ReplaceCompilePathInProjectRoot)
{
    // Path to replace is inside project root, the behavior expected is:
    // Path replaced by path in PREFIX_MAP, then if still relative to
    // PROJECT_ROOT Replaced again to be made relative.
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "/home/usr/bin/hello.c",
        "-I/home/usr/bin/include/headers",
        "-o",
        "hello.o"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    // Deps command shouldn't be rewritten.
    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/usr/bin/hello.c",
        "-I/home/usr/bin/include/headers", "-M"};

    // -I should be replaced, and made relative
    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "bin/hello.c", "-Ibin/include/headers", "-o", "hello.o"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
}

TEST(TestParsedCommandFactory, RspSimpleCompilePathReplacement)
{
    // This test mimics ReplacePathTest-SimpleCompilePathReplacement
    // with the addition of response file handling
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    const std::vector<std::string> command = {
        "gcc", "-c",
        "@recc/response-files/RspSimpleCompilePathReplacement.rsp"};
    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c",
        "@recc/response-files/"
        "RspSimpleCompilePathReplacement.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    // Expected contents of the temporary response file that will be included
    // in the deps command
    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "/home/usr/bin/hello.c", "-I/home/usr/bin/include/headers"};

    // Expected contents of the temporary response file that will be included
    // for remote execution
    const std::vector<std::string> expectedCommandContentsInRsp = {
        "/home/bin/hello.c", "-I/home/bin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    // Get the contents of the temporary rsp files created
    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    // Compare the merkle path mapping for the temporary rsp file created
    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspSimpleCompilePathReplacement.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspSimpleCompilePathReplacementMultiLine)
{
    // Same as RspSimpleCompilePathReplacement but with a response file that
    // contains line breaks
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    const std::vector<std::string> command = {
        "gcc", "-c",
        "@recc/response-files/RspSimpleCompilePathReplacementMultiLine.rsp"};
    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c",
        "@recc/response-files/"
        "RspSimpleCompilePathReplacementMultiLine.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    // Expected contents of the temporary response file that will be included
    // in the deps command
    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "/home/usr/bin/hello.c", "-I/home/usr/bin/include/headers"};

    // Expected contents of the temporary response file that will be included
    // for remote execution
    const std::vector<std::string> expectedCommandContentsInRsp = {
        "/home/bin/hello.c", "-I/home/bin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    // Get the contents of the temporary rsp files created
    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    // Compare the merkle path mapping for the temporary rsp file created
    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspSimpleCompilePathReplacementMultiLine.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspSimpleCompilePathReplacementSplit)
{
    // This test mimics ReplacePathTest-SimpleCompilePathReplacement
    // with the addition of response file handling where some arguments are in
    // command, and some in rsp
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    const std::vector<std::string> command = {
        "gcc", "-c", "/home/usr/bin/hello.c",
        "@recc/response-files/RspSimpleCompilePathReplacementSplit.rsp"};
    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "/home/bin/hello.c",
        "@recc/response-files/"
        "RspSimpleCompilePathReplacementSplit.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    // Expected contents of the temporary response file that will be included
    // in the deps command: only what was in the rsp file originally, minus
    // outputs
    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "-I/home/usr/bin/include/headers"};

    // Expected contents of the temporary response file that will be included
    // for remote execution
    const std::vector<std::string> expectedCommandContentsInRsp = {
        "-I/home/bin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    // Get the contents of the temporary rsp files created
    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    // Compare the merkle path mapping for the temporary rsp file created
    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspSimpleCompilePathReplacementSplit.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/usr/bin/hello.c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspReplaceCompilePathInProjectRoot)
{
    // This test mimics ReplacePathTest-ReplaceCompilePathInProjectRoot
    // with the addition of response file handling
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc", "-c",
        "@recc/response-files/RspReplaceCompilePathInProjectRoot.rsp"};
    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c",
        "@recc/response-files/"
        "RspReplaceCompilePathInProjectRoot.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    // Expected contents of the temporary response file that will be included
    // in the deps command
    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "/home/usr/bin/hello.c", "-I/home/usr/bin/include/headers"};

    // Expected contents of the temporary response file that will be included
    // for remote execution
    const std::vector<std::string> expectedCommandContentsInRsp = {
        "bin/hello.c", "-Ibin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    // Get the contents of the temporary rsp files created
    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    // Compare the merkle path mapping for the temporary rsp file created
    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspReplaceCompilePathInProjectRoot.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspReplaceCompilePathInProjectRootSplit)
{
    // This test mimics ReplacePathTest-ReplaceCompilePathInProjectRoot
    // with the addition of response file handling where some arguments are in
    // command, and some in rsp
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc", "-c", "/home/usr/bin/hello.c",
        "@recc/response-files/RspReplaceCompilePathInProjectRootSplit.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "bin/hello.c",
        "@recc/response-files/"
        "RspReplaceCompilePathInProjectRootSplit.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    // Expected contents of the temporary response file that will be included
    // in the deps command: only what was in the rsp file originally, minus
    // outputs
    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "-I/home/usr/bin/include/headers"};

    // Expected contents of the temporary response file that will be included
    // for remote execution
    const std::vector<std::string> expectedCommandContentsInRsp = {
        "-Ibin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    // Get the contents of the temporary rsp files created
    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    // Compare the merkle path mapping for the temporary rsp file created
    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspReplaceCompilePathInProjectRootSplit.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "/home/usr/bin/hello.c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspSimpleRewrite)
{
    RECC_PREFIX_REPLACEMENT = {{"/usr/bin/include", "/include"}};
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "gcc", "-c", "@recc/response-files/RspSimpleRewrite.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c",
        "@recc/response-files/RspSimpleRewrite.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "hello.c", "-I/usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "hello.c", "-I/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/RspSimpleRewrite.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspSimpleRewriteSplit)
{
    RECC_PREFIX_REPLACEMENT = {{"/usr/bin/include", "/include"}};
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "gcc", "-c", "hello.c",
        "@recc/response-files/RspSimpleRewriteSplit.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "hello.c",
        "@recc/response-files/RspSimpleRewriteSplit.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "-I/usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "-I/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/RspSimpleRewriteSplit.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "hello.c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspNormalizeNonCompilerPaths)
{
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "./gcc", "-c",
        "@recc/response-files/RspNormalizeNonCompilerPaths.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    const std::vector<std::string> expectedCommand = {
        "./gcc", "-c",
        "@recc/response-files/"
        "RspNormalizeNonCompilerPaths.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "./hello.c", "-I/usr/../usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "hello.c", "-I/usr/bin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspNormalizeNonCompilerPaths.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "./gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspNormalizeNonCompilerPathsSplit)
{
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "./gcc", "-c", "./hello.c",
        "@recc/response-files/RspNormalizeNonCompilerPathsSplit.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    const std::vector<std::string> expectedCommand = {
        "./gcc", "-c", "hello.c",
        "@recc/response-files/"
        "RspNormalizeNonCompilerPathsSplit.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "-I/usr/../usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "-I/usr/bin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspNormalizeNonCompilerPathsSplit.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "./gcc", "-c", "./hello.c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspReplacePathInProjectRoot)
{
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc", "-c", "@recc/response-files/RspReplacePathInProjectRoot.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c",
        "@recc/response-files/"
        "RspReplacePathInProjectRoot.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "hello.c", "-I/home/usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "hello.c", "-Ibin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspReplacePathInProjectRoot.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(TestParsedCommandFactory, RspReplacePathInProjectRootSplit)
{
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc", "-c", "hello.c",
        "@recc/response-files/RspReplacePathInProjectRootSplit.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "hello.c",
        "@recc/response-files/"
        "RspReplacePathInProjectRootSplit.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedDepsCommandContentsInRsp = {
        "-I/home/usr/bin/include/headers"};

    const std::vector<std::string> expectedCommandContentsInRsp = {
        "-Ibin/include/headers", "-o", "hello.o"};

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpDepRspFile =
        parsedCommand.d_tmpDepRspFilesStore.back()->strname();
    std::string tmpRspFile =
        parsedCommand.d_tmpRspFilesStore.back()->strname();
    auto commandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpRspFile);
    auto depsCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpDepRspFile);

    const std::string expectedMerklePathOfTmpRspFile =
        "recc/response-files/"
        "RspReplacePathInProjectRootSplit.rsp.recc_rewritten_rsp";
    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    const std::vector<std::string> expectedDepsCommand = {
        "gcc", "-c", "hello.c", "@" + tmpDepRspFile, "-M"};

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_dependencies_command(), expectedDepsCommand);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(depsCommandContentsInRsp, expectedDepsCommandContentsInRsp);
    EXPECT_EQ(commandContentsInRsp, expectedCommandContentsInRsp);
    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
}

TEST(ReplacePathTest, RspFilePathRewriteReplaceCompilePathInProjectRoot)
{
    // Path to replace is inside project root, the behavior expected is:
    // Path replaced by path in PREFIX_MAP, then if still relative to
    // PROJECT_ROOT Replaced again to be made relative.
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "@/home/usr/bin/dummy.rsp",
    };

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    // Because the parsing failed due to the rsp file not existing, we only
    // check that the path replacement and normalization occurred as expected
    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    std::string expectedMerklePathOfTmpRspFile =
        "bin/dummy.rsp.recc_rewritten_rsp";

    // -I should be replaced, and made relative
    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(ReplacePathTest, RspFilePathRewriteNormalizeNonCompilerPaths)
{
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "./gcc",
        "-c",
        "@/usr/../usr/bin/include/dummy.rsp",
    };

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    // Normalization should occur: /usr/../usr/bin/include -> /usr/bin/include
    std::string expectedMerklePathOfTmpRspFile =
        "/usr/bin/include/dummy.rsp.recc_rewritten_rsp";

    const std::vector<std::string> expectedCommand = {
        "./gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(ReplacePathTest, RspFilePathRewriteCompilePathInProjectRoot)
{
    // Corresponds to RewriteAbsolutePathsTest with RSP file as the path
    RECC_PROJECT_ROOT = "/home/nobody/test/";
    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "@/home/nobody/test/dummy.rsp",
    };
    auto parsedCommand = ParsedCommandFactory::createParsedCommand(
        command, "/home/nobody/test");

    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    // Absolute path inside project root should be relativized
    std::string expectedMerklePathOfTmpRspFile =
        "dummy.rsp.recc_rewritten_rsp";

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(ReplacePathTest, RspFilePathRewritePathInProjectRoot)
{
    // Path to replace is inside project root
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    RECC_PROJECT_ROOT = "/home/";

    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "@/home/usr/bin/include/dummy.rsp",
    };

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home/");

    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    // /home/usr/bin/include/dummy.rsp -> /home/bin/include/dummy.rsp ->
    // bin/include/dummy.rsp
    std::string expectedMerklePathOfTmpRspFile =
        "bin/include/dummy.rsp.recc_rewritten_rsp";

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(ReplacePathTest, RspFilePathRewriteSimpleCompilePathReplacement)
{
    RECC_PREFIX_REPLACEMENT = {{"/home/usr/bin", "/home/bin"}};
    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "@/home/usr/bin/dummy.rsp",
    };

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "");

    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    // /home/usr/bin/dummy.rsp -> /home/bin/dummy.rsp
    std::string expectedMerklePathOfTmpRspFile =
        "/home/bin/dummy.rsp.recc_rewritten_rsp";

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(ReplacePathTest, RspFilePathRewriteSimpleRewrite)
{
    RECC_PREFIX_REPLACEMENT = {{"/usr/bin/include", "/include"}};
    RECC_PROJECT_ROOT = "/home/nobody/";

    const std::vector<std::string> command = {
        "gcc",
        "-c",
        "@/usr/bin/include/dummy.rsp",
    };

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, "/home");

    std::string tmpRspFile = parsedCommand.d_tmpRspFiles.back()->strname();

    std::string mappedMerklePath =
        parsedCommand.get_tmp_rsp_file_merkle_path(tmpRspFile);

    // /usr/bin/include/dummy.rsp -> /include/dummy.rsp
    std::string expectedMerklePathOfTmpRspFile =
        "/include/dummy.rsp.recc_rewritten_rsp";

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@" + expectedMerklePathOfTmpRspFile};

    EXPECT_EQ(mappedMerklePath, expectedMerklePathOfTmpRspFile);
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
}

TEST(TestParsedCommandFactory, NestedRspFile)
{
    const std::vector<std::string> command = {
        "gcc", "-c", "@recc/response-files/outer.rsp"};

    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, ".");

    const std::vector<std::string> expectedCommand = {
        "gcc", "-c", "@recc/response-files/outer.rsp.recc_rewritten_rsp"};

    const std::set<std::string> expectedProducts = {"hello.o"};

    const std::vector<std::string> expectedOuterCommandContentsInRsp = {
        "hello.cpp", "@recc/response-files/inner.rsp.recc_rewritten_rsp"};

    const std::vector<std::string> expectedInnerCommandContentsInRsp = {
        "-o", "hello.o"};

    auto dependenciesCommand = parsedCommand.get_dependencies_command();

    ASSERT_FALSE(parsedCommand.d_tmpDepRspFilesStore.empty());
    ASSERT_FALSE(parsedCommand.d_tmpRspFilesStore.empty());

    std::string tmpInnerRspFileName =
        parsedCommand.d_tmpRspFilesStore[0]->strname();
    std::string tmpOuterRspFileName =
        parsedCommand.d_tmpRspFilesStore[1]->strname();

    auto outerCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpOuterRspFileName);
    auto innerCommandContentsInRsp =
        ParsedCommandFactory::argsFromRspFile(tmpInnerRspFileName);

    ASSERT_TRUE(parsedCommand.is_compiler_command());
    EXPECT_EQ(parsedCommand.get_command(), expectedCommand);
    EXPECT_EQ(parsedCommand.get_products(), expectedProducts);
    EXPECT_EQ(outerCommandContentsInRsp, expectedOuterCommandContentsInRsp);
    EXPECT_EQ(innerCommandContentsInRsp, expectedInnerCommandContentsInRsp);
}

TEST(TestParsedCommandFactory, CrossBoundaryRspFile)
{
    const std::vector<std::string> command = {
        "gcc", "-c", "hello.cpp", "@recc/response-files/cross_boundary.rsp",
        "hello.o"};
    auto parsedCommand =
        ParsedCommandFactory::createParsedCommand(command, ".");
    ASSERT_FALSE(parsedCommand.is_compiler_command());
}

#ifndef __sun
TEST(LinkerTest, DependencyFileWithEquals)
{
    auto command = ParsedCommandFactory::createParsedLinkerCommand(
        {"ld", "foo.o", "bar.o", "-o", "myprogram",
         "--dependency-file=myprogram.d"},
        ".");

    std::set<std::string> expectedProducts = {"myprogram"};
    std::set<std::string> expectedDepsProducts = {"myprogram.d"};

    ASSERT_TRUE(command.is_linker_command());
    EXPECT_FALSE(command.is_compiler_command());
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_EQ(command.get_deps_products(), expectedDepsProducts);
}

TEST(LinkerTest, DependencyFileWithSpace)
{
    auto command = ParsedCommandFactory::createParsedLinkerCommand(
        {"ld", "foo.o", "bar.o", "-o", "myprogram", "--dependency-file",
         "myprogram.d"},
        ".");

    std::set<std::string> expectedProducts = {"myprogram"};
    std::set<std::string> expectedDepsProducts = {"myprogram.d"};

    ASSERT_TRUE(command.is_linker_command());
    EXPECT_FALSE(command.is_compiler_command());
    EXPECT_EQ(command.get_products(), expectedProducts);
    EXPECT_EQ(command.get_deps_products(), expectedDepsProducts);
}
#endif
