// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/installer/util/copy_tree_work_item.h"

#include <windows.h>

#include <fstream>
#include <memory>

#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/threading/platform_thread.h"
#include "chrome/installer/util/work_item.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class CopyTreeWorkItemTest : public testing::Test {
 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
  }

  void TearDown() override { logging::CloseLogFile(); }

  // the path to temporary directory used to contain the test operations
  base::ScopedTempDir test_dir_;
  base::ScopedTempDir temp_dir_;
};

// Simple function to dump some text into a new file.
void CreateTextFile(const std::wstring& filename,
                    const std::wstring& contents) {
  std::ofstream file;
  file.open(filename.c_str());
  ASSERT_TRUE(file.is_open());
  file << contents;
  file.close();
}

// Simple function to read text from a file.
std::wstring ReadTextFile(const std::wstring& filename) {
  WCHAR contents[64];
  std::wifstream file;
  file.open(filename.c_str());
  EXPECT_TRUE(file.is_open());
  file.getline(contents, 64);
  file.close();
  return std::wstring(contents);
}

const wchar_t text_content_1[] = L"Gooooooooooooooooooooogle";
const wchar_t text_content_2[] = L"Overwrite Me";

}  // namespace

// Copy one file from source to destination.
TEST_F(CopyTreeWorkItemTest, CopyFile) {
  // Create source file
  base::FilePath file_name_from(test_dir_.GetPath());
  file_name_from = file_name_from.AppendASCII("File_From.txt");
  CreateTextFile(file_name_from.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from));

  // Create destination path
  base::FilePath dir_name_to(test_dir_.GetPath());
  dir_name_to = dir_name_to.AppendASCII("Copy_To_Subdir");
  base::CreateDirectory(dir_name_to);
  ASSERT_TRUE(base::PathExists(dir_name_to));

  base::FilePath file_name_to(dir_name_to);
  file_name_to = file_name_to.AppendASCII("File_To.txt");

  // test Do()
  std::unique_ptr<CopyTreeWorkItem> work_item(WorkItem::CreateCopyTreeWorkItem(
      file_name_from, file_name_to, temp_dir_.GetPath()));

  EXPECT_TRUE(work_item->Do());

  EXPECT_TRUE(base::PathExists(file_name_from));
  EXPECT_TRUE(base::PathExists(file_name_to));
  EXPECT_TRUE(base::ContentsEqual(file_name_from, file_name_to));

  // test rollback()
  work_item->Rollback();

  EXPECT_FALSE(base::PathExists(file_name_to));
  EXPECT_TRUE(base::PathExists(file_name_from));
}

// Copy one file, overwriting the existing one in destination.
TEST_F(CopyTreeWorkItemTest, CopyFileOverwrite) {
  // Create source file
  base::FilePath file_name_from(test_dir_.GetPath());
  file_name_from = file_name_from.AppendASCII("File_From.txt");
  CreateTextFile(file_name_from.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from));

  // Create destination file
  base::FilePath dir_name_to(test_dir_.GetPath());
  dir_name_to = dir_name_to.AppendASCII("Copy_To_Subdir");
  base::CreateDirectory(dir_name_to);
  ASSERT_TRUE(base::PathExists(dir_name_to));

  base::FilePath file_name_to(dir_name_to);
  file_name_to = file_name_to.AppendASCII("File_To.txt");
  CreateTextFile(file_name_to.value(), text_content_2);
  ASSERT_TRUE(base::PathExists(file_name_to));

  // test Do() with always overwrite.
  std::unique_ptr<CopyTreeWorkItem> work_item(WorkItem::CreateCopyTreeWorkItem(
      file_name_from, file_name_to, temp_dir_.GetPath()));

  EXPECT_TRUE(work_item->Do());

  EXPECT_TRUE(base::PathExists(file_name_from));
  EXPECT_TRUE(base::PathExists(file_name_to));
  EXPECT_EQ(0, ReadTextFile(file_name_from.value()).compare(text_content_1));
  EXPECT_EQ(0, ReadTextFile(file_name_to.value()).compare(text_content_1));

  // test rollback()
  work_item->Rollback();

  EXPECT_TRUE(base::PathExists(file_name_from));
  EXPECT_TRUE(base::PathExists(file_name_to));
  EXPECT_EQ(0, ReadTextFile(file_name_from.value()).compare(text_content_1));
  EXPECT_EQ(0, ReadTextFile(file_name_to.value()).compare(text_content_2));
}

// Copy one file and without rollback. Verify all temporary files are deleted.
TEST_F(CopyTreeWorkItemTest, CopyFileAndCleanup) {
  // Create source file
  base::FilePath file_name_from(test_dir_.GetPath());
  file_name_from = file_name_from.AppendASCII("File_From.txt");
  CreateTextFile(file_name_from.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from));

  // Create destination file
  base::FilePath dir_name_to(test_dir_.GetPath());
  dir_name_to = dir_name_to.AppendASCII("Copy_To_Subdir");
  base::CreateDirectory(dir_name_to);
  ASSERT_TRUE(base::PathExists(dir_name_to));

  base::FilePath file_name_to(dir_name_to);
  file_name_to = file_name_to.AppendASCII("File_To.txt");
  CreateTextFile(file_name_to.value(), text_content_2);
  ASSERT_TRUE(base::PathExists(file_name_to));

  {
    // test Do().
    std::unique_ptr<CopyTreeWorkItem> work_item(
        WorkItem::CreateCopyTreeWorkItem(file_name_from, file_name_to,
                                         temp_dir_.GetPath()));

    EXPECT_TRUE(work_item->Do());

    EXPECT_TRUE(base::PathExists(file_name_from));
    EXPECT_TRUE(base::PathExists(file_name_to));
    EXPECT_EQ(0, ReadTextFile(file_name_from.value()).compare(text_content_1));
    EXPECT_EQ(0, ReadTextFile(file_name_to.value()).compare(text_content_1));
  }
}

// Copy one file, with the existing one in destination being used with always
// overwrite. This destination-file-in-use should be moved to backup location
// after Do() and moved back after Rollback().
TEST_F(CopyTreeWorkItemTest, CopyFileInUse) {
  // Create source file
  base::FilePath file_name_from(test_dir_.GetPath());
  file_name_from = file_name_from.AppendASCII("File_From");
  CreateTextFile(file_name_from.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from));

  // Create an executable in destination path by copying ourself to it.
  wchar_t exe_full_path_str[MAX_PATH];
  ::GetModuleFileName(nullptr, exe_full_path_str, MAX_PATH);
  base::FilePath exe_full_path(exe_full_path_str);

  base::FilePath dir_name_to(test_dir_.GetPath());
  dir_name_to = dir_name_to.AppendASCII("Copy_To_Subdir");
  base::CreateDirectory(dir_name_to);
  ASSERT_TRUE(base::PathExists(dir_name_to));

  base::FilePath file_name_to(dir_name_to);
  file_name_to = file_name_to.AppendASCII("File_To");
  base::CopyFile(exe_full_path, file_name_to);
  ASSERT_TRUE(base::PathExists(file_name_to));

  VLOG(1) << "copy ourself from " << exe_full_path.value() << " to "
          << file_name_to.value();

  // Run the executable in destination path
  STARTUPINFOW si = {sizeof(si)};
  PROCESS_INFORMATION pi = {0};
  ASSERT_TRUE(::CreateProcess(
      nullptr, const_cast<wchar_t*>(file_name_to.value().c_str()), nullptr,
      nullptr, FALSE, CREATE_NO_WINDOW | CREATE_SUSPENDED, nullptr, nullptr,
      &si, &pi));

  // test Do().
  std::unique_ptr<CopyTreeWorkItem> work_item(WorkItem::CreateCopyTreeWorkItem(
      file_name_from, file_name_to, temp_dir_.GetPath()));

  EXPECT_TRUE(work_item->Do());

  EXPECT_TRUE(base::PathExists(file_name_from));
  EXPECT_TRUE(base::PathExists(file_name_to));
  EXPECT_EQ(0, ReadTextFile(file_name_from.value()).compare(text_content_1));
  EXPECT_EQ(0, ReadTextFile(file_name_to.value()).compare(text_content_1));

  // test rollback()
  work_item->Rollback();

  EXPECT_TRUE(base::PathExists(file_name_from));
  EXPECT_TRUE(base::PathExists(file_name_to));
  EXPECT_EQ(0, ReadTextFile(file_name_from.value()).compare(text_content_1));
  EXPECT_TRUE(base::ContentsEqual(exe_full_path, file_name_to));

  TerminateProcess(pi.hProcess, 0);
  // make sure the handle is closed.
  EXPECT_TRUE(WaitForSingleObject(pi.hProcess, 10000) == WAIT_OBJECT_0);
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
}

// Copy a tree from source to destination.
// Flaky, http://crbug.com/41245908.
TEST_F(CopyTreeWorkItemTest, DISABLED_CopyTree) {
  // Create source tree
  base::FilePath dir_name_from(test_dir_.GetPath());
  dir_name_from = dir_name_from.AppendASCII("from");
  base::CreateDirectory(dir_name_from);
  ASSERT_TRUE(base::PathExists(dir_name_from));

  base::FilePath dir_name_from_1(dir_name_from);
  dir_name_from_1 = dir_name_from_1.AppendASCII("1");
  base::CreateDirectory(dir_name_from_1);
  ASSERT_TRUE(base::PathExists(dir_name_from_1));

  base::FilePath dir_name_from_2(dir_name_from);
  dir_name_from_2 = dir_name_from_2.AppendASCII("2");
  base::CreateDirectory(dir_name_from_2);
  ASSERT_TRUE(base::PathExists(dir_name_from_2));

  base::FilePath file_name_from_1(dir_name_from_1);
  file_name_from_1 = file_name_from_1.AppendASCII("File_1.txt");
  CreateTextFile(file_name_from_1.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from_1));

  base::FilePath file_name_from_2(dir_name_from_2);
  file_name_from_2 = file_name_from_2.AppendASCII("File_2.txt");
  CreateTextFile(file_name_from_2.value(), text_content_1);
  ASSERT_TRUE(base::PathExists(file_name_from_2));

  base::FilePath dir_name_to(test_dir_.GetPath());
  dir_name_to = dir_name_to.AppendASCII("to");

  // test Do()
  {
    std::unique_ptr<CopyTreeWorkItem> work_item(
        WorkItem::CreateCopyTreeWorkItem(dir_name_from, dir_name_to,
                                         temp_dir_.GetPath()));

    EXPECT_TRUE(work_item->Do());
  }

  base::FilePath file_name_to_1(dir_name_to);
  file_name_to_1 = file_name_to_1.AppendASCII("1");
  file_name_to_1 = file_name_to_1.AppendASCII("File_1.txt");
  EXPECT_TRUE(base::PathExists(file_name_to_1));
  VLOG(1) << "compare " << file_name_from_1.value() << " and "
          << file_name_to_1.value();
  EXPECT_TRUE(base::ContentsEqual(file_name_from_1, file_name_to_1));

  base::FilePath file_name_to_2(dir_name_to);
  file_name_to_2 = file_name_to_2.AppendASCII("2");
  file_name_to_2 = file_name_to_2.AppendASCII("File_2.txt");
  EXPECT_TRUE(base::PathExists(file_name_to_2));
  VLOG(1) << "compare " << file_name_from_2.value() << " and "
          << file_name_to_2.value();
  EXPECT_TRUE(base::ContentsEqual(file_name_from_2, file_name_to_2));
}
