// Copyright (c) 2010 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/process_util.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/service/service_process_control.h"
#include "chrome/browser/service/service_process_control_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/service_process_util.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"

class ServiceProcessControlBrowserTest
    : public InProcessBrowserTest {
 public:
  ServiceProcessControlBrowserTest()
      : service_process_handle_(base::kNullProcessHandle) {
  }
  ~ServiceProcessControlBrowserTest() {
    base::CloseProcessHandle(service_process_handle_);
    service_process_handle_ = base::kNullProcessHandle;
    // Delete all instances of ServiceProcessControl.
    ServiceProcessControlManager::GetInstance()->Shutdown();
  }

#if defined(OS_MACOSX)
  virtual void TearDown() {
    // ForceServiceProcessShutdown removes the process from launchd on Mac.
    ForceServiceProcessShutdown("", 0);
  }
#endif  // OS_MACOSX

 protected:
  void LaunchServiceProcessControl() {
    ServiceProcessControl* process =
        ServiceProcessControlManager::GetInstance()->GetProcessControl(
            browser()->profile());
    process_ = process;

    // Launch the process asynchronously.
    process->Launch(
        NewRunnableMethod(
            this,
            &ServiceProcessControlBrowserTest::ProcessControlLaunched),
        NewRunnableMethod(
            this,
            &ServiceProcessControlBrowserTest::ProcessControlLaunchFailed));

    // Then run the message loop to keep things running.
    ui_test_utils::RunMessageLoop();
  }

  // Send a remoting host status request and wait reply from the service.
  void SendRequestAndWait() {
    process()->GetCloudPrintProxyStatus(NewCallback(
        this, &ServiceProcessControlBrowserTest::CloudPrintStatusCallback));
    ui_test_utils::RunMessageLoop();
  }

  void CloudPrintStatusCallback(
      bool enabled, std::string email) {
    MessageLoop::current()->Quit();
  }

  void Disconnect() {
    // This will delete all instances of ServiceProcessControl and close the IPC
    // connections.
    ServiceProcessControlManager::GetInstance()->Shutdown();
    process_ = NULL;
  }

  void WaitForShutdown() {
    EXPECT_TRUE(base::WaitForSingleProcess(
        service_process_handle_,
        TestTimeouts::wait_for_terminate_timeout_ms()));
  }

  void ProcessControlLaunched() {
    base::ProcessId service_pid;
    EXPECT_TRUE(GetServiceProcessData(NULL, &service_pid));
    EXPECT_NE(static_cast<base::ProcessId>(0), service_pid);
    EXPECT_TRUE(base::OpenProcessHandleWithAccess(
        service_pid,
        base::kProcessAccessWaitForTermination,
        &service_process_handle_));
    // Quit the current message. Post a QuitTask instead of just calling Quit()
    // because this can get invoked in the context of a Launch() call and we
    // may not be in Run() yet.
    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
  }

  void ProcessControlLaunchFailed() {
    ADD_FAILURE();
    // Quit the current message.
    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
  }

  ServiceProcessControl* process() { return process_; }

 private:
  ServiceProcessControl* process_;
  base::ProcessHandle service_process_handle_;
};

// They way that the IPC is implemented only works on windows. This has to
// change when we implement a different scheme for IPC.
// Times out flakily, http://crbug.com/70076.
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest,
                       DISABLED_LaunchAndIPC) {
  LaunchServiceProcessControl();

  // Make sure we are connected to the service process.
  EXPECT_TRUE(process()->is_connected());
  SendRequestAndWait();

  // And then shutdown the service process.
  EXPECT_TRUE(process()->Shutdown());
}

// This tests the case when a service process is launched when browser
// starts but we try to launch it again in the remoting setup dialog.
// Crashes on mac. http://crbug.com/75518
#if defined(OS_MACOSX)
#define MAYBE_LaunchTwice DISABLED_LaunchTwice
#else
#define MAYBE_LaunchTwice LaunchTwice
#endif
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest, MAYBE_LaunchTwice) {
  // Launch the service process the first time.
  LaunchServiceProcessControl();

  // Make sure we are connected to the service process.
  EXPECT_TRUE(process()->is_connected());
  SendRequestAndWait();

  // Launch the service process again.
  LaunchServiceProcessControl();
  EXPECT_TRUE(process()->is_connected());
  SendRequestAndWait();

  // And then shutdown the service process.
  EXPECT_TRUE(process()->Shutdown());
}

static void DecrementUntilZero(int* count) {
  (*count)--;
  if (!(*count))
    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
}

// Invoke multiple Launch calls in succession and ensure that all the tasks
// get invoked.
// Crashes on mac. http://crbug.com/75518
#if defined(OS_MACOSX)
#define MAYBE_MultipleLaunchTasks DISABLED_MultipleLaunchTasks
#else
#define MAYBE_MultipleLaunchTasks MultipleLaunchTasks
#endif
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest,
                       MAYBE_MultipleLaunchTasks) {
  ServiceProcessControl* process =
      ServiceProcessControlManager::GetInstance()->GetProcessControl(
          browser()->profile());
  int launch_count = 5;
  for (int i = 0; i < launch_count; i++) {
    // Launch the process asynchronously.
    process->Launch(
        NewRunnableFunction(&DecrementUntilZero, &launch_count),
        new MessageLoop::QuitTask());
  }
  // Then run the message loop to keep things running.
  ui_test_utils::RunMessageLoop();
  EXPECT_EQ(0, launch_count);
  // And then shutdown the service process.
  EXPECT_TRUE(process->Shutdown());
}

// Make sure using the same task for success and failure tasks works.
// Crashes on mac. http://crbug.com/75518
#if defined(OS_MACOSX)
#define MAYBE_SameLaunchTask DISABLED_SameLaunchTask
#else
#define MAYBE_SameLaunchTask SameLaunchTask
#endif
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest, MAYBE_SameLaunchTask) {
  ServiceProcessControl* process =
      ServiceProcessControlManager::GetInstance()->GetProcessControl(
          browser()->profile());
  int launch_count = 5;
  for (int i = 0; i < launch_count; i++) {
    // Launch the process asynchronously.
    Task * task = NewRunnableFunction(&DecrementUntilZero, &launch_count);
    process->Launch(task, task);
  }
  // Then run the message loop to keep things running.
  ui_test_utils::RunMessageLoop();
  EXPECT_EQ(0, launch_count);
  // And then shutdown the service process.
  EXPECT_TRUE(process->Shutdown());
}

// Tests whether disconnecting from the service IPC causes the service process
// to die.
// Crashes on mac. http://crbug.com/75518
#if defined(OS_MACOSX)
#define MAYBE_DieOnDisconnect DISABLED_DieOnDisconnect
#else
#define MAYBE_DieOnDisconnect DieOnDisconnect
#endif
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest,
                       MAYBE_DieOnDisconnect) {
  // Launch the service process.
  LaunchServiceProcessControl();
  // Make sure we are connected to the service process.
  EXPECT_TRUE(process()->is_connected());
  Disconnect();
  WaitForShutdown();
}

//http://code.google.com/p/chromium/issues/detail?id=70793
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest,
                       DISABLED_ForceShutdown) {
  // Launch the service process.
  LaunchServiceProcessControl();
  // Make sure we are connected to the service process.
  EXPECT_TRUE(process()->is_connected());
  base::ProcessId service_pid;
  EXPECT_TRUE(GetServiceProcessData(NULL, &service_pid));
  EXPECT_NE(static_cast<base::ProcessId>(0), service_pid);
  chrome::VersionInfo version_info;
  ForceServiceProcessShutdown(version_info.Version(), service_pid);
  WaitForShutdown();
}

// Crashes on mac. http://crbug.com/75518
#if defined(OS_MACOSX)
#define MAYBE_CheckPid DISABLED_CheckPid
#else
#define MAYBE_CheckPid CheckPid
#endif
IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest, MAYBE_CheckPid) {
  base::ProcessId service_pid;
  EXPECT_FALSE(GetServiceProcessData(NULL, &service_pid));
  // Launch the service process.
  LaunchServiceProcessControl();
  EXPECT_TRUE(GetServiceProcessData(NULL, &service_pid));
  EXPECT_NE(static_cast<base::ProcessId>(0), service_pid);
}

DISABLE_RUNNABLE_METHOD_REFCOUNT(ServiceProcessControlBrowserTest);