// 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 "tests/gtest/include/gtest/gtest.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),
    retval_(0) {
  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
}

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());
}

// 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 {
#if defined(OS_WIN)
  settings.multi_threaded_message_loop =
      command_line_->HasSwitch(client::switches::kMultiThreadedMessageLoop);
#endif

  if (!settings.multi_threaded_message_loop) {
    settings.external_message_pump =
        command_line_->HasSwitch(client::switches::kExternalMessagePump);
  }

  CefString(&settings.cache_path) =
      command_line_->GetSwitchValue(client::switches::kCachePath);

  // 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;
}

// static
bool CefTestSuite::GetCachePath(std::string& path) const {
  if (command_line_->HasSwitch(client::switches::kCachePath)) {
    // Set the cache_path value.
    path = command_line_->GetSwitchValue(client::switches::kCachePath);
    return true;
  }

  return false;
}

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(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
#endif

#if defined(OS_LINUX) && defined(USE_AURA)
  // 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) && defined(USE_AURA)

  // 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)
 }

 void CefTestSuite::Shutdown() {
 }
