//===- unittests/Basic/FileSystemTest.cpp ---------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "../BuildSystem/TempDir.h"

#include "llbuild/Basic/FileSystem.h"
#include "llbuild/Basic/LLVM.h"
#include "llbuild/Basic/PlatformUtility.h"
#include "llbuild/Basic/Stat.h"

#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"

#include "gtest/gtest.h"

using namespace llbuild;
using namespace llbuild::basic;

namespace {

TEST(FileSystemTest, basic) {
  // Check basic sanity of the local filesystem object.
  auto fs = createLocalFileSystem();

  // Write a temp file.
  SmallString<256> tempPath;
  llvm::sys::fs::createTemporaryFile("FileSystemTests", "txt", tempPath);
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(tempPath.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  auto missingFileInfo = fs->getFileInfo("/does/not/exists");
  EXPECT_TRUE(missingFileInfo.isMissing());
  
  auto ourFileInfo = fs->getFileInfo(tempPath.str());
  EXPECT_FALSE(ourFileInfo.isMissing());

  auto missingFileContents = fs->getFileContents("/does/not/exist");
  EXPECT_EQ(missingFileContents.get(), nullptr);

  auto ourFileContents = fs->getFileContents(tempPath.str());
  EXPECT_EQ(ourFileContents->getBuffer().str(), "Hello, world!");

  // Remote the temporary file.
  auto ec = llvm::sys::fs::remove(tempPath.str());
  EXPECT_FALSE(ec);
}

TEST(FileSystemTest, testRecursiveRemoval) {
  TmpDir rootTempDir(__func__);

  SmallString<256> tempDir { rootTempDir.str() };
  llvm::sys::path::append(tempDir, "root");
  sys::mkdir(tempDir.c_str());

  SmallString<256> file{ tempDir.str() };
  llvm::sys::path::append(file, "test.txt");
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(file.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  SmallString<256> dir{ tempDir.str() };
  llvm::sys::path::append(dir, "subdir");
  sys::mkdir(dir.c_str());

  llvm::sys::path::append(dir, "file_in_subdir.txt");
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(dir.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  auto fs = createLocalFileSystem();
  bool result = fs->remove(tempDir.c_str());
  EXPECT_TRUE(result);

  sys::StatStruct statbuf;
  EXPECT_EQ(-1, sys::stat(tempDir.c_str(), &statbuf));
  EXPECT_EQ(ENOENT, errno);
}

TEST(FileSystemTest, testRecursiveRemovalDoesNotFollowSymlinks) {
  TmpDir rootTempDir(__func__);

  SmallString<256> file{ rootTempDir.str() };
  llvm::sys::path::append(file, "test.txt");
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(file.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  SmallString<256> otherDir{ rootTempDir.str() };
  llvm::sys::path::append(otherDir, "other_dir");
  sys::mkdir(otherDir.c_str());

  SmallString<256> otherFile{ otherDir.str() };
  llvm::sys::path::append(otherFile, "test.txt");
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(otherFile.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  SmallString<256> tempDir { rootTempDir.str() };
  llvm::sys::path::append(tempDir, "root");
  sys::mkdir(tempDir.c_str());

  SmallString<256> linkPath { tempDir.str() };
  llvm::sys::path::append(linkPath, "link.txt");
  int res = sys::symlink(file.c_str(), linkPath.c_str());
  EXPECT_EQ(res, 0);

  SmallString<256> directoryLinkPath { tempDir.str() };
  llvm::sys::path::append(directoryLinkPath, "link_to_other_dir");
  res = sys::symlink(otherDir.c_str(), directoryLinkPath.c_str());
  EXPECT_EQ(res, 0);

  auto fs = createLocalFileSystem();
  bool result = fs->remove(tempDir.c_str());
  EXPECT_TRUE(result);

  sys::StatStruct  statbuf;
  EXPECT_EQ(-1, sys::stat(tempDir.c_str(), &statbuf));
  EXPECT_EQ(ENOENT, errno);
  // Verify that the symlink target still exists.
  EXPECT_EQ(0, sys::stat(file.c_str(), &statbuf));
  // Verify that we did not delete the symlinked directories contents.
  EXPECT_EQ(0, sys::stat(otherFile.c_str(), &statbuf));
}

TEST(DeviceAgnosticFileSystemTest, basic) {
  // Check basic sanity of the local filesystem object.
  auto fs = DeviceAgnosticFileSystem::from(createLocalFileSystem());

  // Write a temp file.
  SmallString<256> tempPath;
  llvm::sys::fs::createTemporaryFile("FileSystemTests", "txt", tempPath);
  {
    std::error_code ec;
    llvm::raw_fd_ostream os(tempPath.str(), ec, llvm::sys::fs::F_Text);
    EXPECT_FALSE(ec);
    os << "Hello, world!";
    os.close();
  }

  auto missingFileInfo = fs->getFileInfo("/does/not/exists");
  EXPECT_TRUE(missingFileInfo.isMissing());

  auto ourFileInfo = fs->getFileInfo(tempPath.str());
  EXPECT_FALSE(ourFileInfo.isMissing());

  EXPECT_EQ(ourFileInfo.device, 0ull);
  EXPECT_EQ(ourFileInfo.inode, 0ull);

  auto missingFileContents = fs->getFileContents("/does/not/exist");
  EXPECT_EQ(missingFileContents.get(), nullptr);

  auto ourFileContents = fs->getFileContents(tempPath.str());
  EXPECT_EQ(ourFileContents->getBuffer().str(), "Hello, world!");
  // Remote the temporary file.
  auto ec = llvm::sys::fs::remove(tempPath.str());
  EXPECT_FALSE(ec);
}

}
