// 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 <fileutils.h>

#include <env.h>
#include <subprocess.h>

#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_temporarydirectory.h>

#include <gtest/gtest.h>

#include <cstdlib>
#include <exception>
#include <functional>
#include <gtest/gtest.h>
#include <mutex>
#include <sys/stat.h>
#include <vector>
using namespace recc;

namespace {
const auto digestFunctionInitializer = []() {
    buildboxcommon::DigestGenerator::init();
    return 0;
}();
} // namespace

TEST(FileUtilsTest, FileContents)
{
    buildboxcommon::TemporaryDirectory tempDir;
    const std::string fileName = tempDir.name() + std::string("/testfile.txt");

    EXPECT_THROW(buildboxcommon::FileUtils::getFileContents(fileName.c_str()),
                 std::exception);
    buildboxcommon::FileUtils::writeFileAtomically(fileName, "File contents");
    EXPECT_EQ(buildboxcommon::FileUtils::getFileContents(fileName.c_str()),
              "File contents");

    buildboxcommon::FileUtils::writeFileAtomically(fileName,
                                                   "Overwrite, don't append");
    EXPECT_EQ(buildboxcommon::FileUtils::getFileContents(fileName.c_str()),
              "Overwrite, don't append");
}

TEST(HasPathPrefixTest, AbsolutePaths)
{
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/b/c/", "/a/b"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/b/c/", "/a/b/"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/b/c", "/a/b"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/b/c", "/a/b/"));

    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/c/d", "/a/b/"));

    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/boo", "/a/b/"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/boo", "/a/b"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/boo", "/a/b/a/boo"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/boo", "/a/b/a/boo/"));

    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/../b/", "/a"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/../b/", "/a/"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/../b", "/a"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/../b", "/a/"));
}

TEST(HasPathPrefixTest, RelativePaths)
{
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/b/c/", "a/b"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/b/c/", "a/b/"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/b/c", "a/b"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/b/c", "a/b/"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("/a/b/c", "/a/b/c"));

    EXPECT_FALSE(FileUtils::hasPathPrefix("a/c/d", "a/b/"));

    EXPECT_FALSE(FileUtils::hasPathPrefix("a/boo", "a/b/"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("a/boo", "a/b"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("a/boo", "a/b/a/boo"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("a/boo", "a/b/a/boo/"));

    EXPECT_TRUE(FileUtils::hasPathPrefix("a/../b/", "a"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/../b/", "a/"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/../b", "a"));
    EXPECT_TRUE(FileUtils::hasPathPrefix("a/../b", "a/"));

    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/b/c/", "a/b/"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/b/c/", "a/b"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/b/c", "a/b/"));
    EXPECT_FALSE(FileUtils::hasPathPrefix("/a/b/c", "a/b"));
}

TEST(HasPathPrefixesTest, PathTests)
{
    const std::set<std::string> prefixes = {"/usr/include",
                                            "/opt/rh/devtoolset-7"};

    EXPECT_TRUE(FileUtils::hasPathPrefixes("/usr/include/stat.h", prefixes));
    EXPECT_FALSE(FileUtils::hasPathPrefixes("usr/include/stat.h", prefixes));
    EXPECT_TRUE(
        FileUtils::hasPathPrefixes("/opt/rh/devtoolset-7/foo.h", prefixes));
    EXPECT_FALSE(FileUtils::hasPathPrefixes("/opt/rh/foo.h", prefixes));

    EXPECT_TRUE(FileUtils::hasPathPrefixes("/some/dir/foo.h", {"/"}));
    EXPECT_FALSE(FileUtils::hasPathPrefixes("/", {"/some/other/dir"}));

    EXPECT_TRUE(FileUtils::hasPathPrefixes("/some/dir,withcomma/foo.h",
                                           {"/some/dir,withcomma/"}));
}

TEST(FileUtilsTest, GetCurrentWorkingDirectory)
{
    const std::vector<std::string> command = {"pwd"};
    const auto commandResult = Subprocess::execute(command, true);
    if (commandResult.d_exitCode == 0) {
        EXPECT_EQ(commandResult.d_stdOut,
                  FileUtils::getCurrentWorkingDirectory() + "\n");
    }
}

TEST(FileUtilsTest, ParentDirectoryLevels)
{
    EXPECT_EQ(FileUtils::parentDirectoryLevels(""), 0);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("/"), 0);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("."), 0);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("./"), 0);

    EXPECT_EQ(FileUtils::parentDirectoryLevels(".."), 1);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("../"), 1);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("../.."), 2);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("../../"), 2);

    EXPECT_EQ(FileUtils::parentDirectoryLevels("a/b/c.txt"), 0);
    EXPECT_EQ(FileUtils::parentDirectoryLevels("a/../../b.txt"), 1);
    EXPECT_EQ(
        FileUtils::parentDirectoryLevels("a/../../b/c/d/../../../../test.txt"),
        2);
}

TEST(FileUtilsTest, LastNSegments)
{
    EXPECT_EQ(FileUtils::lastNSegments("abc", 0), "");
    EXPECT_EQ(FileUtils::lastNSegments("abc", 1), "abc");
    EXPECT_ANY_THROW(FileUtils::lastNSegments("abc", 2));
    EXPECT_ANY_THROW(FileUtils::lastNSegments("abc", 3));

    EXPECT_EQ(FileUtils::lastNSegments("/abc", 0), "");
    EXPECT_EQ(FileUtils::lastNSegments("/abc", 1), "abc");
    EXPECT_ANY_THROW(FileUtils::lastNSegments("/abc", 2));
    EXPECT_ANY_THROW(FileUtils::lastNSegments("/abc", 3));

    EXPECT_EQ(FileUtils::lastNSegments("/a/bc", 0), "");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bc", 1), "bc");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bc", 2), "a/bc");
    EXPECT_ANY_THROW(FileUtils::lastNSegments("/a/bc", 3));

    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 0), "");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 1), "e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 2), "dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 3), "c/dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 4), "bb/c/dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e", 5), "a/bb/c/dd/e");
    EXPECT_ANY_THROW(FileUtils::lastNSegments("/a/bb/c/dd/e", 6));

    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 0), "");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 1), "e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 2), "dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 3), "c/dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 4), "bb/c/dd/e");
    EXPECT_EQ(FileUtils::lastNSegments("/a/bb/c/dd/e/", 5), "a/bb/c/dd/e");
    EXPECT_ANY_THROW(FileUtils::lastNSegments("/a/bb/c/dd/e/", 6));
}

TEST(FileUtilsTest, AbsolutePaths)
{
    EXPECT_EQ(false, FileUtils::isAbsolutePath("../hello"));
    EXPECT_EQ(true, FileUtils::isAbsolutePath("/../hello/"));
    EXPECT_EQ(false, FileUtils::isAbsolutePath(""));
    EXPECT_EQ(true, FileUtils::isAbsolutePath("/hello/world"));
}

// Test paths are rewritten
TEST(PathRewriteTest, SimpleRewriting)
{
    RECC_PREFIX_REPLACEMENT = {{"/hello/hi", "/hello"},
                               {"/usr/bin/system/bin/hello", "/usr/system"}};
    std::string testPath = "/hello/hi/file.txt";
    ASSERT_EQ("/hello/file.txt",
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));

    testPath = "/usr/bin/system/bin/hello/file.txt";
    ASSERT_EQ("/usr/system/file.txt",
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));

    testPath = "/hello/bin/not_replaced.txt";
    ASSERT_EQ(testPath,
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));
}

// Test more complicated paths
TEST(PathRewriteTest, ComplicatedPathRewriting)
{
    RECC_PREFIX_REPLACEMENT = {{"/hello/hi", "/hello"},
                               {"/usr/bin/system/bin/hello", "/usr/system"},
                               {"/bin", "/"}};

    auto testPath = "/usr/bin/system/bin/hello/world/";
    ASSERT_EQ("/usr/system/world",
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));

    // Don't rewrite non-absolute path
    testPath = "../hello/hi/hi.txt";
    ASSERT_EQ(testPath,
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));

    testPath = "/bin/hello/file.txt";
    ASSERT_EQ("/hello/file.txt",
              FileUtils::resolvePathPrefixFromLocalToRemote(testPath));
}

TEST(PathReplacement, modifyRemotePathUnmodified)
{
    // If a given path doesn't match any PREFIX_REPLACEMENT
    // rules and can't be made relative, it's returned unmodified
    RECC_PROJECT_ROOT = "/home/nobody/";
    RECC_PREFIX_REPLACEMENT = {{"/home", "/hi"}};

    const auto workingDir = "/home";

    const std::string replacedPath =
        FileUtils::modifyPathForRemote("/other/dir/nobody/test", workingDir);

    EXPECT_EQ("/other/dir/nobody/test", replacedPath);
}

TEST(PathReplacement, modifyRemotePathPrefixMatch)
{
    // Match a PREFIX_REPLACEMENT rule, but the replaced path
    // isn't eligable to be made relative, so it's returned absolute
    RECC_PROJECT_ROOT = "/home/nobody/";
    RECC_PREFIX_REPLACEMENT = {{"/home", "/hi"}};

    const auto workingDir = "/home";

    const std::string replacedPath =
        FileUtils::modifyPathForRemote("/home/nobody/test", workingDir);

    EXPECT_EQ("/hi/nobody/test", replacedPath);
}

TEST(PathReplacement, modifyRemotePathMadeRelative)
{
    // Path doesn't match any PREFIX_REPLACEMENT rules,
    // but can be made relative to RECC_PROJECT_ROOT
    RECC_PROJECT_ROOT = "/other";
    RECC_PREFIX_REPLACEMENT = {{"/home", "/hi"}};

    const auto workingDir = "/other";

    const std::string replacedPath =
        FileUtils::modifyPathForRemote("/other/nobody/test", workingDir);

    EXPECT_EQ("nobody/test", replacedPath);
}

TEST(PathReplacement, modifyRemotePathPrefixAndRelativeMatch)
{
    // Path matches a PREFIX_REPLACEMENT rule, and the replaced
    // path can be made relative to RECC_PROJECT_ROOT
    RECC_PROJECT_ROOT = "/home/";
    RECC_PREFIX_REPLACEMENT = {{"/home/nobody/", "/home"}};

    const auto workingDir = "/home";

    const std::string replacedPath =
        FileUtils::modifyPathForRemote("/home/nobody/test", workingDir);

    EXPECT_EQ("test", replacedPath);
}

TEST(PathReplacement, normalizeRemotePath)
{
    RECC_PROJECT_ROOT = "/home/nobody/";
    RECC_PREFIX_REPLACEMENT = {{"/home", "/hi"}};

    const auto workingDir = "/home";

    // If a given path doesn't match any PREFIX_REPLACEMENT
    // rules and can't be made relative, it's returned unmodified
    // if RECC_NO_PATH_REWRITE is set
    RECC_NO_PATH_REWRITE = true;
    const std::string replacedPathNoRewrite =
        FileUtils::modifyPathForRemote("//other/dir/nobody/test", workingDir);
    EXPECT_EQ("//other/dir/nobody/test", replacedPathNoRewrite);

    // It's normalized but otherwise unmodified
    // if RECC_NO_PATH_REWRITE is not set
    RECC_NO_PATH_REWRITE = false;
    const std::string replacedPath =
        FileUtils::modifyPathForRemote("//other/dir/nobody/test", workingDir);
    EXPECT_EQ("/other/dir/nobody/test", replacedPath);
}

// A generic wrapper for testing the expected prefix resolve on a downloaded
// dependency file.
void testExpectedDownloadResolve(const std::string &testFileName,
                                 const std::string &testFileContents,
                                 const std::string &expectedResultContents)
{
    // Create a temporary file to test with.
    // NOTE: It cannot use buildboxcommon::TemporaryFile here or it will suffix
    // with a random string resulting in a path like /tmp/world.dzhSUh5 which
    // is not a valid dependency file. Instead create a new file inside a
    // temporary directory.
    buildboxcommon::TemporaryDirectory testDirectory;
    const std::string testFilePath =
        testDirectory.name() + std::string("/") + std::string(testFileName);

    // Write the contents to the test file.
    buildboxcommon::FileUtils::writeFileAtomically(testFilePath,
                                                   testFileContents);

    // Perform the remapping.
    FileUtils::resolvePathsInsideDependencyFileForLocal(testFilePath);

    // Check the file has the expected contents.
    EXPECT_EQ(
        expectedResultContents,
        buildboxcommon::FileUtils::getFileContents(testFilePath.c_str()));
}

TEST(ResolvePathsFromDownload, absolutePathReplace)
{
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/remote/hello"}};

    const std::string testFileName = "world";
    const std::string testFileContents = "/remote/hello/file.txt";
    const std::string expectedResultContents = "/local/hello/file.txt";

    testExpectedDownloadResolve(testFileName, testFileContents,
                                expectedResultContents);
}

TEST(ResolvePathsFromDownload, relativeReplace)
{
    RECC_PREFIX_REPLACEMENT = {{"local/hello", "remote/hello"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "remote/hello/file.txt";
    const std::string expectedResultContents = "local/hello/file.txt";

    testExpectedDownloadResolve(testFileName, testFileContents,
                                expectedResultContents);
}

TEST(ResolvePathsFromDownload, replaceOrder)
{
    // Check the first remapping in RECC_PREFIX_REPLACEMENT is used when
    // multiple are valid.
    RECC_PREFIX_REPLACEMENT = {{"/local", "/remote"},
                               {"/some_other_place", "/remote"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "/remote/file.txt";
    const std::string expectedResultContents = "/local/file.txt";

    testExpectedDownloadResolve(testFileName, testFileContents,
                                expectedResultContents);
}

TEST(ResolvePathsFromDownload, multiPathMultiLineReplace)
{
    // Check it can remap multiple paths across multiple lines.
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/remote/hello"},
                               {"/local/stuff", "/remote/stuff"},
                               {"foo", "remote/foo"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents =
        "remote/foo/file.txt remote/foo/file.txt \\\n"
        "/remote/hello/file.txt /lib/foo \\\n"
        "/remote/stuff/hello/file.txt";
    const std::string expectedResultContents =
        "foo/file.txt foo/file.txt \\\n"
        "/local/hello/file.txt /lib/foo \\\n"
        "/local/stuff/hello/file.txt";

    testExpectedDownloadResolve(testFileName, testFileContents,
                                expectedResultContents);
}

TEST(ResolvePathsFromDownload, noRemappingProvided)
{
    RECC_PREFIX_REPLACEMENT = {};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "/hello/file.txt";

    // If there is no RECC_PREFIX_REPLACEMENT there should be no change.
    const std::string &expectedResultContents = testFileContents;

    testExpectedDownloadResolve(testFileName, testFileContents,
                                expectedResultContents);
}

TEST(ResolvePathsFromDownload, noFileAtPath)
{
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/hello"}};

    // Performing the remapping on a non existant file shouldn't throw.
    EXPECT_NO_THROW(
        FileUtils::resolvePathsInsideDependencyFileForLocal(std::string("")));
}

// A generic wrapper for testing the expected prefix resolve for a dependency
// file to be uploaded.
void testExpectedUploadResolve(const std::string &testFileName,
                               const std::string &testFileContents,
                               const std::string &expectedResultContents)
{

    // Create a temporary file to test with.
    // NOTE: It cannot use buildboxcommon::TemporaryFile here or it will suffix
    // with a random string resulting in a path like /tmp/world.dzhSUh5 which
    // is not a valid dependency file. Instead create a new file inside a
    // temporary directory.
    buildboxcommon::TemporaryDirectory testDirectory("testDirectory");

    const std::string testFilePath =
        testDirectory.name() + std::string("/") + std::string(testFileName);

    // Write the contents to the test file.
    buildboxcommon::FileUtils::writeFileAtomically(testFilePath,
                                                   testFileContents);

    // Create a directory to store possible return file.
    buildboxcommon::TemporaryDirectory modifiedUploadsDirectory(
        "modifiedUploadsDirectory");

    // Perform the remapping.
    std::string resultFilePath =
        FileUtils::resolvePathsInsideDependencyFileForRemote(
            testFilePath, modifiedUploadsDirectory.name());

    // Check the file has the expected contents.
    EXPECT_EQ(
        expectedResultContents,
        buildboxcommon::FileUtils::getFileContents(resultFilePath.c_str()));
}

TEST(ResolvePathsForUpload, absolutePathReplace)
{
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/remote/hello"}};

    const std::string testFileName = "world";
    const std::string testFileContents = "/local/hello/file.txt";
    const std::string expectedResultContents = "/remote/hello/file.txt";

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, relativePathReplace)
{
    RECC_PREFIX_REPLACEMENT = {{"local/hello", "remote/hello"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "local/hello/file.txt";
    const std::string expectedResultContents = "remote/hello/file.txt";

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, projectRootPathReplace)
{
    RECC_PROJECT_ROOT = "/project_root/";
    RECC_PREFIX_REPLACEMENT = {{"/project_root/hello", "/project_root"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "/project_root/hello/file.txt";
    const std::string expectedResultContents = "file.txt";

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, replaceOrder)
{
    RECC_PREFIX_REPLACEMENT = {{"/local", "/remote"},
                               {"/some_other_place", "/remote"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "/local/file.txt";
    const std::string expectedResultContents = "/remote/file.txt";

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, multiPathMultiLineReplace)
{
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/remote/hello"},
                               {"/local/stuff", "/remote/stuff"},
                               {"foo", "remote/foo"}};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "foo/file.txt foo/file.txt \\\n"
                                         "/local/hello/file.txt /lib/foo \\\n"
                                         "/local/stuff/hello/file.txt";
    const std::string expectedResultContents =
        "remote/foo/file.txt remote/foo/file.txt \\\n"
        "/remote/hello/file.txt /lib/foo \\\n"
        "/remote/stuff/hello/file.txt";

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, noRemappingProvided)
{
    RECC_PREFIX_REPLACEMENT = {};

    const std::string testFileName = "world.d";
    const std::string testFileContents = "/hello/file.txt";

    // If there is no RECC_PREFIX_REPLACEMENT there should be no change.
    const std::string &expectedResultContents = testFileContents;

    testExpectedUploadResolve(testFileName, testFileContents,
                              expectedResultContents);
}

TEST(ResolvePathsForUpload, noFileAtPath)
{
    RECC_PREFIX_REPLACEMENT = {{"/local/hello", "/hello"}};
    buildboxcommon::TemporaryDirectory testDirectory;

    // Performing the remapping on a non existant file shouldn't throw.
    EXPECT_NO_THROW(FileUtils::resolvePathsInsideDependencyFileForRemote(
        std::string(""), testDirectory.name()));
}

TEST(MakePathRelativeTest, ReturnNonAbsolutePathsUnmodified)
{
    EXPECT_EQ("", FileUtils::makePathRelative("", "/some/working/directory"));
    EXPECT_EQ("../a/relative/path",
              FileUtils::makePathRelative("../a/relative/path",
                                          "/some/working/directory"));
    EXPECT_EQ("test",
              FileUtils::makePathRelative("test", "/some/working/directory"));
    EXPECT_EQ("test/../path", FileUtils::makePathRelative(
                                  "test/../path", "/some/working/directory"));
    EXPECT_EQ("test/long/path",
              FileUtils::makePathRelative("test/long/path",
                                          "/some/working/directory"));
    EXPECT_EQ("some/path", FileUtils::makePathRelative(
                               "some/path", "/some/working/directory"));
    EXPECT_EQ("./some/path", FileUtils::makePathRelative(
                                 "./some/path", "/some/working/directory"));
    EXPECT_EQ("some/long/path/..",
              FileUtils::makePathRelative("some/long/path/..",
                                          "/some/working/directory"));
}

TEST(MakePathRelativeTest, DoNothingIfWorkingDirectoryNull)
{
    EXPECT_EQ("/test/directory/",
              FileUtils::makePathRelative("/test/directory/", ""));
    EXPECT_EQ("/test", FileUtils::makePathRelative("/test", ""));
}

TEST(MakePathRelativeTest, WorkingDirectoryIsPathPrefix)
{
    EXPECT_EQ("some/test/path",
              FileUtils::makePathRelative("/some/test/path", "/"));

    EXPECT_EQ("test/path",
              FileUtils::makePathRelative("/some/test/path", "/some"));
    EXPECT_EQ("test/path",
              FileUtils::makePathRelative("/some/test/path", "/some/"));

    EXPECT_EQ("path",
              FileUtils::makePathRelative("/some/test/path", "/some/test"));
    EXPECT_EQ("path",
              FileUtils::makePathRelative("/some/test/path", "/some/test/"));

    EXPECT_EQ("path/",
              FileUtils::makePathRelative("/some/test/path/", "/some/test"));
    EXPECT_EQ("path/",
              FileUtils::makePathRelative("/some/test/path/", "/some/test/"));
}

TEST(MakePathRelativeTest, PathEqualsWorkingDirectory)
{
    EXPECT_EQ(".", FileUtils::makePathRelative("/some/test/path",
                                               "/some/test/path"));
    EXPECT_EQ(".", FileUtils::makePathRelative("/some/test/path",
                                               "/some/test/path/"));
    EXPECT_EQ("./", FileUtils::makePathRelative("/some/test/path/",
                                                "/some/test/path"));
    EXPECT_EQ("./", FileUtils::makePathRelative("/some/test/path/",
                                                "/some/test/path/"));
}

TEST(MakePathRelativeTest, PathAlmostEqualsWorkingDirectory)
{
    EXPECT_EQ("../tests",
              FileUtils::makePathRelative("/some/tests", "/some/test"));
    EXPECT_EQ("../tests",
              FileUtils::makePathRelative("/some/tests", "/some/test/"));
    EXPECT_EQ("../tests/",
              FileUtils::makePathRelative("/some/tests/", "/some/test"));
    EXPECT_EQ("../tests/",
              FileUtils::makePathRelative("/some/tests/", "/some/test/"));
}

TEST(MakePathRelativeTest, PathIsParentOfWorkingDirectory)
{
    EXPECT_EQ("..", FileUtils::makePathRelative("/a/b/c", "/a/b/c/d"));
    EXPECT_EQ("..", FileUtils::makePathRelative("/a/b/c", "/a/b/c/d/"));
    EXPECT_EQ("../", FileUtils::makePathRelative("/a/b/c/", "/a/b/c/d"));
    EXPECT_EQ("../", FileUtils::makePathRelative("/a/b/c/", "/a/b/c/d/"));

    EXPECT_EQ("../../..", FileUtils::makePathRelative("/a", "/a/b/c/d"));
    EXPECT_EQ("../../..", FileUtils::makePathRelative("/a", "/a/b/c/d/"));
    EXPECT_EQ("../../../", FileUtils::makePathRelative("/a/", "/a/b/c/d"));
    EXPECT_EQ("../../../", FileUtils::makePathRelative("/a/", "/a/b/c/d/"));
}

TEST(MakePathRelativeTest, PathAdjacentToWorkingDirectory)
{
    EXPECT_EQ("../e", FileUtils::makePathRelative("/a/b/c/e", "/a/b/c/d"));
    EXPECT_EQ("../e", FileUtils::makePathRelative("/a/b/c/e", "/a/b/c/d/"));
    EXPECT_EQ("../e/", FileUtils::makePathRelative("/a/b/c/e/", "/a/b/c/d"));
    EXPECT_EQ("../e/", FileUtils::makePathRelative("/a/b/c/e/", "/a/b/c/d/"));

    EXPECT_EQ("../e/f/g",
              FileUtils::makePathRelative("/a/b/c/e/f/g", "/a/b/c/d"));
    EXPECT_EQ("../e/f/g",
              FileUtils::makePathRelative("/a/b/c/e/f/g", "/a/b/c/d/"));
    EXPECT_EQ("../e/f/g/",
              FileUtils::makePathRelative("/a/b/c/e/f/g/", "/a/b/c/d"));
    EXPECT_EQ("../e/f/g/",
              FileUtils::makePathRelative("/a/b/c/e/f/g/", "/a/b/c/d/"));

    EXPECT_EQ("../../e/f/g",
              FileUtils::makePathRelative("/a/b/e/f/g", "/a/b/c/d"));
    EXPECT_EQ("../../e/f/g",
              FileUtils::makePathRelative("/a/b/e/f/g", "/a/b/c/d/"));
    EXPECT_EQ("../../e/f/g/",
              FileUtils::makePathRelative("/a/b/e/f/g/", "/a/b/c/d"));
    EXPECT_EQ("../../e/f/g/",
              FileUtils::makePathRelative("/a/b/e/f/g/", "/a/b/c/d/"));
}
