// Copyright 2016 The Chromium Embedded Framework Authors. Postions copyright
// 2012 The Chromium Authors. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.

#include "tests/ceftests/test_suite.h"

#include "include/base/cef_logging.h"
#include "include/cef_file_util.h"
#include "include/test/cef_test_helpers.h"
#include "include/wrapper/cef_scoped_temp_dir.h"
#include "tests/gtest/include/gtest/gtest.h"
#include "tests/gtest/teamcity/include/teamcity_gtest.h"
#include "tests/shared/browser/resource_util.h"
#include "tests/shared/common/client_switches.h"

namespace {

CefTestSuite* g_test_suite = nullptr;

#if defined(OS_WIN)

// From base/process/launch_win.cc.
void RouteStdioToConsole(bool create_console_if_not_found) {
  // Don't change anything if stdout or stderr already point to a
  // valid stream.
  //
  // If we are running under Buildbot or under Cygwin's default
  // terminal (mintty), stderr and stderr will be pipe handles.  In
  // that case, we don't want to open CONOUT$, because its output
  // likely does not go anywhere.
  //
  // We don't use GetStdHandle() to check stdout/stderr here because
  // it can return dangling IDs of handles that were never inherited
  // by this process.  These IDs could have been reused by the time
  // this function is called.  The CRT checks the validity of
  // stdout/stderr on startup (before the handle IDs can be reused).
  // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was
  // invalid.
  if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) {
    return;
  }

  if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
    unsigned int result = GetLastError();
    // Was probably already attached.
    if (result == ERROR_ACCESS_DENIED) {
      return;
    }
    // Don't bother creating a new console for each child process if the
    // parent process is invalid (eg: crashed).
    if (result == ERROR_GEN_FAILURE) {
      return;
    }
    if (create_console_if_not_found) {
      // Make a new console if attaching to parent fails with any other error.
      // It should be ERROR_INVALID_HANDLE at this point, which means the
      // browser was likely not started from a console.
      AllocConsole();
    } else {
      return;
    }
  }

  // Arbitrary byte count to use when buffering output lines.  More
  // means potential waste, less means more risk of interleaved
  // log-lines in output.
  enum { kOutputBufferSize = 64 * 1024 };

  if (freopen("CONOUT$", "w", stdout)) {
    setvbuf(stdout, nullptr, _IOLBF, kOutputBufferSize);
    // Overwrite FD 1 for the benefit of any code that uses this FD
    // directly.  This is safe because the CRT allocates FDs 0, 1 and
    // 2 at startup even if they don't have valid underlying Windows
    // handles.  This means we won't be overwriting an FD created by
    // _open() after startup.
    _dup2(_fileno(stdout), 1);
  }
  if (freopen("CONOUT$", "w", stderr)) {
    setvbuf(stderr, nullptr, _IOLBF, kOutputBufferSize);
    _dup2(_fileno(stderr), 2);
  }

  // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog.
  std::ios::sync_with_stdio();
}

#endif  // defined(OS_WIN)

}  // namespace

CefTestSuite::CefTestSuite(int argc, char** argv)
    : argc_(argc), argv_(argc, argv) {
  g_test_suite = this;

  // Keep a representation of the original command-line.
  command_line_ = CefCommandLine::CreateCommandLine();
#if defined(OS_WIN)
  command_line_->InitFromString(::GetCommandLineW());
#else
  command_line_->InitFromArgv(argc, argv);
#endif

  if (!command_line_->HasSwitch("type")) {
    // Initialize in the main process only.
    root_cache_path_ =
        command_line_->GetSwitchValue(client::switches::kCachePath);
    if (root_cache_path_.empty()) {
      CefScopedTempDir temp_dir;
      CHECK(temp_dir.CreateUniqueTempDir());
      root_cache_path_ = temp_dir.Take();
      RegisterTempDirectory(root_cache_path_);
    }
  }
}

CefTestSuite::~CefTestSuite() {
  g_test_suite = nullptr;
}

// static
CefTestSuite* CefTestSuite::GetInstance() {
  return g_test_suite;
}

void CefTestSuite::InitMainProcess() {
  PreInitialize();

  // This will modify |argc_| and |argv_|.
  testing::InitGoogleTest(&argc_, argv_.array());

  if (jetbrains::teamcity::underTeamcity()) {
    auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
    listeners.Append(
        new jetbrains::teamcity::TeamcityGoogleTestEventListener());
  }
}

// Don't add additional code to this method. Instead add it to Initialize().
int CefTestSuite::Run() {
  Initialize();
  retval_ = RUN_ALL_TESTS();
  Shutdown();
  return retval_;
}

void CefTestSuite::GetSettings(CefSettings& settings) const {
  CefString(&settings.cache_path) = root_cache_path_;
  CefString(&settings.root_cache_path) = root_cache_path_;

  // Always expose the V8 gc() function to give tests finer-grained control over
  // memory management.
  std::string javascript_flags = "--expose-gc";
  // Value of kJavascriptFlags switch.
  std::string other_javascript_flags =
      command_line_->GetSwitchValue("js-flags");
  if (!other_javascript_flags.empty()) {
    javascript_flags += " " + other_javascript_flags;
  }
  CefString(&settings.javascript_flags) = javascript_flags;

  // Necessary for V8Test.OnUncaughtException tests.
  settings.uncaught_exception_stack_size = 10;

  // Necessary for the OSRTest tests.
  settings.windowless_rendering_enabled = true;

  // For Accept-Language test
  CefString(&settings.accept_language_list) = CEF_SETTINGS_ACCEPT_LANGUAGE;
}

void CefTestSuite::RegisterTempDirectory(const CefString& directory) {
  base::AutoLock lock_scope(temp_directories_lock_);
  temp_directories_.push_back(directory);
}

void CefTestSuite::DeleteTempDirectories() {
  base::AutoLock lock_scope(temp_directories_lock_);
  for (const auto& temp_directory : temp_directories_) {
    CefDeleteFile(temp_directory, true);
  }
  temp_directories_.clear();
}

void CefTestSuite::PreInitialize() {
#if defined(OS_WIN)
  testing::GTEST_FLAG(catch_exceptions) = false;

  // Enable termination on heap corruption.
  // Ignore the result code. Supported starting with XP SP3 and Vista.
  HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
#endif

#if defined(OS_LINUX)
  // When calling native char conversion functions (e.g wrctomb) we need to
  // have the locale set. In the absence of such a call the "C" locale is the
  // default. In the gtk code (below) gtk_init() implicitly sets a locale.
  setlocale(LC_ALL, "");
#endif  // defined(OS_LINUX)

  // Don't add additional code to this function. Instead add it to Initialize().
}

void CefTestSuite::Initialize() {
#if defined(OS_WIN)
  RouteStdioToConsole(true);
#endif  // defined(OS_WIN)

#if !defined(CEF_TESTS_IN_SRC_DIRECTORY)
  // Configure the directory that contains test data resources.
  std::string resource_dir;
  bool result = client::GetResourceDir(resource_dir);
  CHECK(result && !resource_dir.empty());
  CefSetDataDirectoryForTests(resource_dir);
#endif  // !defined(CEF_TESTS_IN_SRC_DIRECTORY)
}

void CefTestSuite::Shutdown() {}
