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

#include "swift/Basic/FileSystem.h"
#include "swift/Basic/LLVM.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"

#define ASSERT_NO_ERROR(x)                                                     \
  do if (std::error_code ASSERT_NO_ERROR_ec = x) {                             \
    llvm::errs() << #x ": did not return errc::success.\n"                     \
      << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n"                \
      << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n";            \
    FAIL();                                                                    \
  } while (0)

using namespace llvm::sys;
using namespace swift;

namespace {
TEST(FileSystem, MoveFileIfDifferentEmpty) {
  // Create unique temporary directory for these tests
  llvm::SmallString<128> dirPath;
  ASSERT_NO_ERROR(fs::createUniqueDirectory("FileSystem-test", dirPath));

  // Test 1: Move empty over nonexistent.
  llvm::SmallString<128> sourceFile = dirPath;
  path::append(sourceFile, "source.txt");
  {
    std::error_code error;
    llvm::raw_fd_ostream emptyOut(sourceFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
  }

  fs::UniqueID origID;
  ASSERT_NO_ERROR(fs::getUniqueID(sourceFile, origID));

  llvm::SmallString<128> destFile = dirPath;
  path::append(destFile, "dest.txt");
  ASSERT_FALSE(fs::exists(destFile));

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  fs::UniqueID destID;
  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(origID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));

  // Test 2: Move empty over empty.
  {
    std::error_code error;
    llvm::raw_fd_ostream emptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
  }

  fs::UniqueID newID;
  ASSERT_NO_ERROR(fs::getUniqueID(destFile, newID));
  ASSERT_NE(origID, newID);

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(newID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));
  origID = destID;

  // Test 3: Move empty over non-empty.
  {
    std::error_code error;
    llvm::raw_fd_ostream nonEmptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
    nonEmptyOut << "a";
  }

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, newID));
  ASSERT_NE(origID, newID);

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(origID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));

  // Test 4: Move empty over self.
  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, sourceFile));
  ASSERT_TRUE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(sourceFile, newID));
  ASSERT_EQ(origID, newID);

  // Clean up.
  ASSERT_NO_ERROR(fs::remove(sourceFile, false));
  ASSERT_NO_ERROR(fs::remove(dirPath, false));
}

TEST(FileSystem, MoveFileIfDifferentNonEmpty) {
  // Create unique temporary directory for these tests
  llvm::SmallString<128> dirPath;
  ASSERT_NO_ERROR(fs::createUniqueDirectory("FileSystem-test", dirPath));

  // Test 1: Move source over nonexistent.
  llvm::SmallString<128> sourceFile = dirPath;
  path::append(sourceFile, "source.txt");
  {
    std::error_code error;
    llvm::raw_fd_ostream sourceOut(sourceFile, error, fs::OF_None);
    sourceOut << "original";
    ASSERT_NO_ERROR(error);
  }

  fs::UniqueID origID;
  ASSERT_NO_ERROR(fs::getUniqueID(sourceFile, origID));

  llvm::SmallString<128> destFile = dirPath;
  path::append(destFile, "dest.txt");
  ASSERT_FALSE(fs::exists(destFile));

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  fs::UniqueID destID;
  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(origID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));

  // Test 2: Move source over empty.
  {
    std::error_code error;
    llvm::raw_fd_ostream emptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
  }

  fs::UniqueID newID;
  ASSERT_NO_ERROR(fs::getUniqueID(destFile, newID));
  ASSERT_NE(origID, newID);

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(origID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));

  // Test 3: Move source over non-empty-but-different.
  {
    std::error_code error;
    llvm::raw_fd_ostream nonEmptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
    nonEmptyOut << "different";
  }

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, newID));
  ASSERT_NE(origID, newID);

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(origID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));

  // Test 4: Move source over identical.
  {
    std::error_code error;
    llvm::raw_fd_ostream nonEmptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
    nonEmptyOut << "original";
  }

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, newID));
  ASSERT_NE(origID, newID);

  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, destFile));
  ASSERT_TRUE(fs::exists(destFile));
  ASSERT_FALSE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(destFile, destID));
  ASSERT_EQ(newID, destID);

  ASSERT_NO_ERROR(fs::rename(destFile, sourceFile));
  origID = newID;

  // Test 5: Move source over self.
  ASSERT_NO_ERROR(moveFileIfDifferent(sourceFile, sourceFile));
  ASSERT_TRUE(fs::exists(sourceFile));

  ASSERT_NO_ERROR(fs::getUniqueID(sourceFile, newID));
  ASSERT_EQ(origID, newID);

  // Clean up.
  ASSERT_NO_ERROR(fs::remove(sourceFile, false));
  ASSERT_NO_ERROR(fs::remove(dirPath, false));
}

TEST(FileSystem, MoveFileIfDifferentNonExistent) {
  // Create unique temporary directory for these tests
  llvm::SmallString<128> dirPath;
  ASSERT_NO_ERROR(fs::createUniqueDirectory("FileSystem-test", dirPath));

  llvm::SmallString<128> sourceFile = dirPath;
  path::append(sourceFile, "source.txt");
  llvm::SmallString<128> destFile = dirPath;
  path::append(destFile, "dest.txt");

  // Test 1: Nonexistent -> nonexistent.
  ASSERT_TRUE((bool)moveFileIfDifferent(sourceFile, destFile));

  {
    std::error_code error;
    llvm::raw_fd_ostream emptyOut(destFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
  }

  // Test 2: Nonexistent -> present.
  ASSERT_TRUE((bool)moveFileIfDifferent(sourceFile, destFile));

  // Clean up.
  ASSERT_NO_ERROR(fs::remove(destFile));
  ASSERT_NO_ERROR(fs::remove(dirPath));
}

TEST(FileSystem, MoveFileIfDifferentInvalid) {
  // Create unique temporary directory for these tests
  llvm::SmallString<128> dirPath;
  ASSERT_NO_ERROR(fs::createUniqueDirectory("FileSystem-test", dirPath));

  llvm::SmallString<128> sourceFile = dirPath;
  path::append(sourceFile, "source.txt");
  {
    std::error_code error;
    llvm::raw_fd_ostream emptyOut(sourceFile, error, fs::OF_None);
    ASSERT_NO_ERROR(error);
  }

  // Test 1: Move a file over its parent directory.
  ASSERT_TRUE((bool)moveFileIfDifferent(sourceFile, dirPath));

  // Test 2: Move a file into a nonexistent directory.
  llvm::SmallString<128> destFile = dirPath;
  path::append(destFile, "nonexistent", "dest.txt");
  ASSERT_TRUE((bool)moveFileIfDifferent(sourceFile, destFile));

  // Clean up.
  ASSERT_NO_ERROR(fs::remove(sourceFile, false));
  ASSERT_NO_ERROR(fs::remove(dirPath, false));
}
} // anonymous namespace
