// Copyright (c) 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 <stdio.h>
#if defined(OS_POSIX)
#include <unistd.h>
#elif defined(OS_WIN)
#include <windows.h>
#endif

#define TELEMETRY 1

#include "base/command_line.h"
#include "base/environment.h"
#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/nacl/nacl_browsertest_util.h"
#include "components/nacl/browser/nacl_browser.h"
#include "components/nacl/common/nacl_switches.h"
#include "content/public/common/content_switches.h"

namespace {

#if defined(OS_WIN)
// crbug.com/98721
#  define MAYBE_SysconfNprocessorsOnln DISABLED_SysconfNprocessorsOnln
#else
#  define MAYBE_SysconfNprocessorsOnln SysconfNprocessorsOnln
#endif

NACL_BROWSER_TEST_F(NaClBrowserTest, SimpleLoad, {
  RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html"));
})

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNonSfiMode, MAYBE_NONSFI(Messaging)) {
  RunLoadTest(FILE_PATH_LITERAL("libc_free.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNonSfiMode, MAYBE_NONSFI(Irt)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("irt_test.html"));
}

NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatus0, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_exit_status_test.html?trigger=exit0&expected_exit=0"));
})

NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatus254, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_exit_status_test.html?trigger=exit254&expected_exit=254"));
})

NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatusNeg2, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_exit_status_test.html?trigger=exitneg2&expected_exit=254"));
})

#if defined(ADDRESS_SANITIZER)
#define Maybe_PPAPICore DISABLED_PPAPICore
#else
#define Maybe_PPAPICore PPAPICore
#endif
NACL_BROWSER_TEST_F(NaClBrowserTest, Maybe_PPAPICore, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_ppb_core.html"));
})

NACL_BROWSER_TEST_F(NaClBrowserTest, PPAPIPPBInstance, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_ppb_instance.html"));
})

NACL_BROWSER_TEST_F(NaClBrowserTest, PPAPIPPPInstance, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_ppp_instance.html"));
})

NACL_BROWSER_TEST_F(NaClBrowserTest, ProgressEvents, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_progress_events.html"));
})

// Note: currently not run on PNaCl because crash throttling causes the last few
// tests to fail for the wrong reasons.  Enabling this test would also require
// creating a new set of manifests because shared NaCl/PNaCl manifests are not
// allowed.  Also not run on GLibc because it's a large test that is at risk of
// causing timeouts.
// crbug/338444
#if defined(OS_WIN)
#define MAYBE_Bad DISABLED_Bad
#else
#define MAYBE_Bad Bad
#endif
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, MAYBE_Bad) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_bad.html"));
}

// partially_invalid.c does not have an ARM version of its asm.
#if !defined(__arm__)
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, BadNative) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_bad_native.html"));
}
#endif

#if defined(OS_WIN)
// crbug.com/98721
#  define MAYBE_Crash DISABLED_Crash
#elif defined(OS_LINUX)
// crbug.com/366334
#  define MAYBE_Crash DISABLED_Crash
#else
#  define MAYBE_Crash Crash
#endif
NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_Crash, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_crash.html"));
})

// PNaCl version does not work.
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, ManifestFile) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("pm_manifest_file_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestGLibc, MAYBE_GLIBC(ManifestFile)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("pm_manifest_file_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, PreInitManifestFile) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_pre_init_manifest_file_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestGLibc,
                       MAYBE_GLIBC(PreInitManifestFile)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_pre_init_manifest_file_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, IrtManifestFile) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("irt_manifest_file_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclNonSfi,
                       MAYBE_PNACL_NONSFI(IrtManifestFile)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("irt_manifest_file_test.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, IrtException) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("irt_exception_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclNonSfi,
                       MAYBE_PNACL_NONSFI(IrtException)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("irt_exception_test.html"));
}

NACL_BROWSER_TEST_F(NaClBrowserTest, Nameservice, {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("pm_nameservice_test.html"));
})

// Some versions of Visual Studio does not like preprocessor
// conditionals inside the argument of a macro, so we put the
// conditionals on a helper function.  We are already in an anonymous
// namespace, so the name of the helper is not visible in external
// scope.
#if defined(OS_POSIX)
base::FilePath::StringType NumberOfCoresAsFilePathString() {
  char string_rep[23];
  long nprocessors = sysconf(_SC_NPROCESSORS_ONLN);
#if TELEMETRY
  fprintf(stderr, "browser says nprocessors = %ld\n", nprocessors);
  fflush(NULL);
#endif
  snprintf(string_rep, sizeof string_rep, "%ld", nprocessors);
  return string_rep;
}
#elif defined(OS_WIN)
base::FilePath::StringType NumberOfCoresAsFilePathString() {
  wchar_t string_rep[23];
  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);
#if TELEMETRY
  fprintf(stderr, "browser says nprocessors = %d\n",
          system_info.dwNumberOfProcessors);
  fflush(NULL);
#endif
  _snwprintf_s(string_rep, sizeof string_rep / sizeof string_rep[0], _TRUNCATE,
               L"%u", system_info.dwNumberOfProcessors);
  return string_rep;
}
#endif

#if TELEMETRY
static void PathTelemetry(base::FilePath::StringType const &path) {
# if defined(OS_WIN)
    fwprintf(stderr, L"path = %s\n", path.c_str());
# else
    fprintf(stderr, "path = %s\n", path.c_str());
# endif
    fflush(NULL);
}
#else
static void PathTelemetry(base::FilePath::StringType const &path) {
  (void) path;
}
#endif

NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_SysconfNprocessorsOnln, {
    base::FilePath::StringType path =
      FILE_PATH_LITERAL("sysconf_nprocessors_onln_test.html?cpu_count=");
    path = path + NumberOfCoresAsFilePathString();
    PathTelemetry(path);
    RunNaClIntegrationTest(path);
})

IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CrossOriginCORS) {
  RunLoadTest(FILE_PATH_LITERAL("cross_origin/cors.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CrossOriginFail) {
  RunLoadTest(FILE_PATH_LITERAL("cross_origin/fail.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, SameOriginCookie) {
  RunLoadTest(FILE_PATH_LITERAL("cross_origin/same_origin_cookie.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CORSNoCookie) {
  RunLoadTest(FILE_PATH_LITERAL("cross_origin/cors_no_cookie.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, RelativeManifest) {
  RunLoadTest(FILE_PATH_LITERAL("manifest/relative_manifest.html"));
}

// Test with the NaCl debug flag turned on.
class NaClBrowserTestPnaclDebug : public NaClBrowserTestPnacl {
 public:
  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    NaClBrowserTestPnacl::SetUpCommandLine(command_line);
    // Turn on debugging to influence the PNaCl URL loaded
    command_line->AppendSwitch(switches::kEnableNaClDebug);
    // On windows, the debug stub requires --no-sandbox:
    // crbug.com/265624
#if defined(OS_WIN)
    command_line->AppendSwitch(switches::kNoSandbox);
#endif
  }

  // On some platforms this test does not work.
  bool TestIsBroken() {
    // TODO(jvoung): Make this test work on Windows 32-bit. When --no-sandbox
    // is used, the required 1GB sandbox address space is not reserved.
    // (see note in chrome/browser/nacl_host/test/nacl_gdb_browsertest.cc)
#if defined(OS_WIN)
    if (base::win::OSInfo::GetInstance()->wow64_status() ==
        base::win::OSInfo::WOW64_DISABLED &&
        base::win::OSInfo::GetInstance()->architecture() ==
        base::win::OSInfo::X86_ARCHITECTURE) {
      return true;
    }
#endif
    return false;
  }

  void StartTestScript(base::ProcessHandle* test_process,
                       int debug_stub_port) {
    // We call a python script that speaks to the debug stub, and
    // lets the app continue, so that the load progress event completes.
    CommandLine cmd(base::FilePath(FILE_PATH_LITERAL("python")));
    base::FilePath script;
    PathService::Get(base::DIR_SOURCE_ROOT, &script);
    script = script.AppendASCII(
        "chrome/browser/nacl_host/test/debug_stub_browser_tests.py");
    cmd.AppendArgPath(script);
    cmd.AppendArg(base::IntToString(debug_stub_port));
    cmd.AppendArg("continue");
    LOG(INFO) << cmd.GetCommandLineString();
    base::LaunchProcess(cmd, base::LaunchOptions(), test_process);
  }

  void RunWithTestDebugger(const base::FilePath::StringType& test_url) {
    base::ProcessHandle test_script;
    scoped_ptr<base::Environment> env(base::Environment::Create());
    nacl::NaClBrowser::GetInstance()->SetGdbDebugStubPortListener(
        base::Bind(&NaClBrowserTestPnaclDebug::StartTestScript,
                   base::Unretained(this), &test_script));
    // Turn on debug stub logging.
    env->SetVar("NACLVERBOSITY", "1");
    RunLoadTest(test_url);
    env->UnSetVar("NACLVERBOSITY");
    nacl::NaClBrowser::GetInstance()->ClearGdbDebugStubPortListener();
    int exit_code;
    LOG(INFO) << "Waiting for script to exit (which waits for embed to die).";
    base::WaitForExitCode(test_script, &exit_code);
    EXPECT_EQ(0, exit_code);
  }
};

// Test with the NaCl debug flag turned on, but mask off every URL
// so that nothing is actually debugged.
class NaClBrowserTestPnaclDebugMasked : public NaClBrowserTestPnaclDebug {
 public:
  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    NaClBrowserTestPnaclDebug::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(switches::kNaClDebugMask,
                                    "!<all_urls>");
  }
};

// The tests which actually start a debug session must use the debug stub
// to continue the app startup. However, NaCl on windows can't open the
// debug stub socket in the browser process as needed by the test.
// See http://crbug.com/157312.
#if defined(OS_WIN)
#define MAYBE_PnaclDebugURLFlagAndURL DISABLED_PnaclDebugURLFlagAndURL
#define MAYBE_PnaclDebugURLFlagNoURL DISABLED_PnaclDebugURLFlagNoURL
#else
#define MAYBE_PnaclDebugURLFlagAndURL PnaclDebugURLFlagAndURL
#define MAYBE_PnaclDebugURLFlagNoURL PnaclDebugURLFlagNoURL
#endif
IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclDebug,
                       MAYBE_PnaclDebugURLFlagAndURL) {
  RunWithTestDebugger(FILE_PATH_LITERAL(
      "pnacl_debug_url.html?nmf_file=pnacl_has_debug.nmf"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclDebug,
                       MAYBE_PnaclDebugURLFlagNoURL) {
  RunWithTestDebugger(FILE_PATH_LITERAL(
      "pnacl_debug_url.html?nmf_file=pnacl_no_debug.nmf"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclDebugURLFlagOff)) {
  RunLoadTest(FILE_PATH_LITERAL(
      "pnacl_debug_url.html?nmf_file=pnacl_has_debug_flag_off.nmf"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclDebugMasked,
                       MAYBE_PNACL(PnaclDebugURLFlagMaskedOff)) {
  if (TestIsBroken()) {
    return;
  }
  // If the mask excludes debugging, it's as if the flag was off.
  RunLoadTest(FILE_PATH_LITERAL(
      "pnacl_debug_url.html?nmf_file=pnacl_has_debug_flag_off.nmf"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclErrorHandling)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL("pnacl_error_handling.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclNMFOptionsO0)) {
  RunLoadTest(FILE_PATH_LITERAL("pnacl_options.html?use_nmf=o_0"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclNMFOptionsO2)) {
  RunLoadTest(FILE_PATH_LITERAL("pnacl_options.html?use_nmf=o_2"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclNMFOptionsOlarge)) {
  RunLoadTest(FILE_PATH_LITERAL("pnacl_options.html?use_nmf=o_large"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclDyncodeSyscallDisabled)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pnacl_dyncode_syscall_disabled.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl,
                       MAYBE_PNACL(PnaclExceptionHandlingDisabled)) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pnacl_exception_handling_disabled.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl, PnaclMimeType) {
  RunLoadTest(FILE_PATH_LITERAL("pnacl_mime_type.html"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclDisabled, PnaclMimeType) {
  RunLoadTest(FILE_PATH_LITERAL("pnacl_mime_type.html"));
}

class NaClBrowserTestNewlibStdoutPM : public NaClBrowserTestNewlib {
 public:
  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
    // Env needs to be set early because nacl_helper is spawned before the test
    // body on Linux.
    scoped_ptr<base::Environment> env(base::Environment::Create());
    env->SetVar("NACL_EXE_STDOUT", "DEBUG_ONLY:dev://postmessage");
    NaClBrowserTestNewlib::SetUpInProcessBrowserTestFixture();
  }
};

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStdoutPM, RedirectFg0) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stdout&thread=fg&delay_us=0"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStdoutPM, RedirectBg0) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stdout&thread=bg&delay_us=0"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStdoutPM, RedirectFg1) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stdout&thread=fg&delay_us=1000000"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStdoutPM, RedirectBg1) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stdout&thread=bg&delay_us=1000000"));
}

class NaClBrowserTestNewlibStderrPM : public NaClBrowserTestNewlib {
 public:
  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
    // Env needs to be set early because nacl_helper is spawned before the test
    // body on Linux.
    scoped_ptr<base::Environment> env(base::Environment::Create());
    env->SetVar("NACL_EXE_STDERR", "DEBUG_ONLY:dev://postmessage");
    NaClBrowserTestNewlib::SetUpInProcessBrowserTestFixture();
  }
};

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStderrPM, RedirectFg0) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stderr&thread=fg&delay_us=0"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStderrPM, RedirectBg0) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stderr&thread=bg&delay_us=0"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStderrPM, RedirectFg1) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stderr&thread=fg&delay_us=1000000"));
}

IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibStderrPM, RedirectBg1) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "pm_redir_test.html?stream=stderr&thread=bg&delay_us=1000000"));
}

// TODO(ncbray) support glibc and PNaCl
#if defined(OS_MACOSX)
// crbug.com/375894
#define MAYBE_MimeHandler DISABLED_MimeHandler
#else
#define MAYBE_MimeHandler MimeHandler
#endif
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlibExtension, MAYBE_MimeHandler) {
  RunNaClIntegrationTest(FILE_PATH_LITERAL(
      "ppapi_extension_mime_handler.html"));
}

}  // namespace