// Copyright 2016 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 "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/utility_process_host.h"
#include "content/browser/utility_process_host_client.h"
#include "content/public/browser/browser_child_process_observer.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/common/bind_interface_helpers.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_service.mojom.h"

#if defined(OS_MACOSX) || defined(OS_LINUX)
#include <sys/wait.h>
#endif

#if defined(OS_WIN)
#include <windows.h>
#endif  // OS_WIN

namespace content {

namespace {

const char kTestProcessName[] = "test_process";

}  // namespace

class UtilityProcessHostBrowserTest : public BrowserChildProcessObserver,
                                      public ContentBrowserTest {
 public:
  void RunUtilityProcess(bool elevated, bool crash) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserChildProcessObserver::Add(this);
    has_launched = false;
    has_crashed = false;
    base::RunLoop run_loop;
    done_closure_ =
        base::BindOnce(&UtilityProcessHostBrowserTest::DoneRunning,
                       base::Unretained(this), run_loop.QuitClosure(), crash);
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::BindOnce(
            &UtilityProcessHostBrowserTest::RunUtilityProcessOnIOThread,
            base::Unretained(this), elevated, crash));
    run_loop.Run();
  }

 protected:
  void DoneRunning(base::OnceClosure quit_closure, bool expect_crashed) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserChildProcessObserver::Remove(this);
    EXPECT_EQ(true, has_launched);
    EXPECT_EQ(expect_crashed, has_crashed);
    std::move(quit_closure).Run();
  }

  void RunUtilityProcessOnIOThread(bool elevated, bool crash) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    UtilityProcessHost* host =
        new UtilityProcessHost(/*client=*/nullptr,
                               /*client_task_runner=*/nullptr);
    host->SetName(base::ASCIIToUTF16("TestProcess"));
    host->SetMetricsName(kTestProcessName);
#if defined(OS_WIN)
    if (elevated)
      host->SetSandboxType(service_manager::SandboxType::
                               SANDBOX_TYPE_NO_SANDBOX_AND_ELEVATED_PRIVILEGES);
#endif
    EXPECT_TRUE(host->Start());

    BindInterface(host, &service_);
    if (crash) {
      service_->DoCrashImmediately(
          base::BindOnce(&UtilityProcessHostBrowserTest::OnSomethingOnIOThread,
                         base::Unretained(this), crash));
    } else {
      service_->DoSomething(
          base::BindOnce(&UtilityProcessHostBrowserTest::OnSomethingOnIOThread,
                         base::Unretained(this), crash));
    }
  }

  void ResetServiceOnIOThread() {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    service_.reset();
  }

  void OnSomethingOnIOThread(bool expect_crash) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    // If service crashes then this never gets called.
    ASSERT_EQ(false, expect_crash);
    ResetServiceOnIOThread();
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            std::move(done_closure_));
  }

  mojom::TestServicePtr service_;
  base::OnceClosure done_closure_;

  // Access on UI thread.
  bool has_launched;
  bool has_crashed;

 private:
  // content::BrowserChildProcessObserver implementation:
  void BrowserChildProcessKilled(
      const ChildProcessData& data,
      const ChildProcessTerminationInfo& info) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_ANDROID)
    // Android does not send crash notifications but sends kills. See comment in
    // browser_child_process_observer.h.
    BrowserChildProcessCrashed(data, info);
#else
    FAIL() << "Killed notifications should only happen on Android.";
#endif
  }

  void BrowserChildProcessCrashed(
      const ChildProcessData& data,
      const ChildProcessTerminationInfo& info) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    EXPECT_EQ(true, has_launched);
#if defined(OS_WIN)
    EXPECT_EQ(EXCEPTION_BREAKPOINT, DWORD{info.exit_code});
#elif defined(OS_MACOSX) || defined(OS_LINUX)
    EXPECT_TRUE(WIFSIGNALED(info.exit_code));
    EXPECT_EQ(SIGTRAP, WTERMSIG(info.exit_code));
#endif
    EXPECT_EQ(kTestProcessName, data.metrics_name);
    EXPECT_EQ(false, has_crashed);
    has_crashed = true;
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::BindOnce(&UtilityProcessHostBrowserTest::ResetServiceOnIOThread,
                       base::Unretained(this)));
    std::move(done_closure_).Run();
  }

  void BrowserChildProcessLaunchedAndConnected(
      const ChildProcessData& data) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // Multiple child processes might be launched, check just for ours.
    if (data.metrics_name == kTestProcessName) {
      EXPECT_EQ(false, has_launched);
      has_launched = true;
    }
  }
};

IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchProcess) {
  RunUtilityProcess(false, false);
}

IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchProcessAndCrash) {
  RunUtilityProcess(false, true);
}

#if defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchElevatedProcess) {
  RunUtilityProcess(true, false);
}

// Disabled because currently this causes a WER dialog to appear.
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
                       LaunchElevatedProcessAndCrash_DISABLED) {
  RunUtilityProcess(true, true);
}
#endif

}  // namespace content
