/*
 * Copyright 2019 Bloomberg Finance LP
 *
 * 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 <buildboxcommon_temporarydirectory.h>
#include <buildboxcommon_temporaryfile.h>
#include <buildboxrun_hosttools_changedirectoryguard.h>

#include <filesystem>
#include <gtest/gtest.h>
#include <limits.h>
#include <system_error>

using namespace buildboxcommon;
using namespace buildboxrun;
using namespace hosttools;

std::string getRealPath(const std::string &s)
try {
    return std::filesystem::canonical(s).string();
}
catch (const std::filesystem::filesystem_error &) {
    return s;
}

// Small helper to get the current directory
std::string getDirectory()
{
    char buff[PATH_MAX];
    if (getcwd(buff, sizeof(buff)) == NULL) {
        throw "getcwd failed";
    }
    // get the absolute path to resolve
    // any symlinks.
    return getRealPath(std::string(buff));
}

// Test to make sure that the Guard changes into
// and back out of the directory as expected.
TEST(ChangeDirectoryGuardTests, TestBasicUsage)
{
    TemporaryDirectory tempdir = TemporaryDirectory();
    const std::string tmpdirname = tempdir.name();
    const std::string currentDir = getDirectory();
    ASSERT_NE(currentDir, tmpdirname);
    {
        ChangeDirectoryGuard g(tmpdirname);
        const std::string newDir = getDirectory();
        ASSERT_EQ(newDir, getRealPath(tmpdirname));
    }
    std::string dirAfter = getDirectory();
    ASSERT_NE(dirAfter, tmpdirname);
    ASSERT_EQ(dirAfter, currentDir);
}

// Test that errors from chdir throw exceptions. EXPECT_THROW
// isn't used because we want to check the errno of the exception as well
TEST(ChangeDirectoryGuardTests, NonExistantDirectoryThrows)
{
    try {
        ChangeDirectoryGuard g("/foo/bar/baz");
        FAIL() << "ChangeDirectoryGuard should have thrown with a "
                  "non-existant directory";
    }
    catch (const std::system_error &e) {
        EXPECT_EQ(e.code(), std::error_code(ENOENT, std::system_category()));
    }
    catch (...) {
        FAIL() << "Expected a std::system_error, but "
               << std::current_exception << "Was found instead!";
    }
}

TEST(ChangeDirectoryGuardTests, EmptyPathThrows)
{
    try {
        ChangeDirectoryGuard g("");
        FAIL() << "ChangeDirectoryGuard should have thrown when constructed "
                  "with an empty string";
    }
    catch (const std::system_error &e) {
        EXPECT_EQ(e.code(), std::error_code(ENOENT, std::system_category()));
    }
    catch (...) {
        FAIL() << "Expected a std::system_error, but "
               << std::current_exception << "Was found instead!";
    }
}

TEST(ChangeDirectoryGuardTests, DirIsAFileFails)
{
    TemporaryFile tempfile = TemporaryFile();
    try {
        ChangeDirectoryGuard g(tempfile.name());
        FAIL() << "ChangeDirectoryGuard should have thrown when constructed "
                  "with a file instead of a directory";
    }
    catch (const std::system_error &e) {
        EXPECT_EQ(e.code(), std::error_code(ENOTDIR, std::system_category()));
    }
    catch (...) {
        FAIL() << "Expected a std::system_error, but "
               << std::current_exception << "Was found instead!";
    }
}
