| Index: chrome/app/chrome_watcher_client_unittest_win.cc
|
| diff --git a/chrome/app/chrome_watcher_client_unittest_win.cc b/chrome/app/chrome_watcher_client_unittest_win.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d2cc96bb6d9893ddeb62825581376861290d47db
|
| --- /dev/null
|
| +++ b/chrome/app/chrome_watcher_client_unittest_win.cc
|
| @@ -0,0 +1,271 @@
|
| +// Copyright 2015 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 "chrome/app/chrome_watcher_client_win.h"
|
| +
|
| +#include <windows.h>
|
| +#include <string>
|
| +#include "base/base_switches.h"
|
| +#include "base/bind.h"
|
| +#include "base/command_line.h"
|
| +#include "base/logging.h"
|
| +#include "base/process/process_handle.h"
|
| +#include "base/strings/string16.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| +#include "base/synchronization/waitable_event.h"
|
| +#include "base/test/multiprocess_test.h"
|
| +#include "base/threading/simple_thread.h"
|
| +#include "base/time/time.h"
|
| +#include "base/win/scoped_handle.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "testing/multiprocess_func_list.h"
|
| +
|
| +namespace {
|
| +
|
| +const char kParentHandle[] = "parent-handle";
|
| +const char kEventHandle[] = "event-handle";
|
| +const char kNamedEventSuffix[] = "named-event-suffix";
|
| +
|
| +const base::char16 kExitEventBaseName[] = L"ChromeWatcherClientTestExitEvent_";
|
| +const base::char16 kInitializeEventBaseName[] =
|
| + L"ChromeWatcherClientTestInitializeEvent_";
|
| +
|
| +base::win::ScopedHandle InterpretHandleSwitch(base::CommandLine& cmd_line,
|
| + const char* switch_name) {
|
| + std::string str_handle =
|
| + cmd_line.GetSwitchValueASCII(switch_name);
|
| + if (str_handle.empty()) {
|
| + LOG(ERROR) << "Switch " << switch_name << " unexpectedly absent.";
|
| + return base::win::ScopedHandle();
|
| + }
|
| +
|
| + unsigned int_handle = 0;
|
| + if (!base::StringToUint(str_handle, &int_handle)) {
|
| + LOG(ERROR) << "Switch " << switch_name << " has invalid value "
|
| + << str_handle;
|
| + return base::win::ScopedHandle();
|
| + }
|
| +
|
| + return base::win::ScopedHandle(
|
| + reinterpret_cast<base::ProcessHandle>(int_handle));
|
| +}
|
| +
|
| +// Simulates a Chrome watcher process. Exits when the global exit event is
|
| +// signaled. Signals the "on initialized" event (passed on the command-line)
|
| +// when the global initialization event is signaled.
|
| +MULTIPROCESS_TEST_MAIN(ChromeWatcherClientTestProcess) {
|
| + base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
|
| +
|
| + base::string16 named_event_suffix =
|
| + base::ASCIIToUTF16(cmd_line->GetSwitchValueASCII(kNamedEventSuffix));
|
| + if (named_event_suffix.empty()) {
|
| + LOG(ERROR) << "Switch " << kNamedEventSuffix << " unexpectedly absent.";
|
| + return 1;
|
| + }
|
| +
|
| + base::win::ScopedHandle exit_event(::CreateEvent(
|
| + NULL, FALSE, FALSE, (kExitEventBaseName + named_event_suffix).c_str()));
|
| + if (!exit_event.IsValid()) {
|
| + LOG(ERROR) << "Failed to create event named "
|
| + << kExitEventBaseName + named_event_suffix;
|
| + return 1;
|
| + }
|
| +
|
| + base::win::ScopedHandle initialize_event(
|
| + ::CreateEvent(NULL, FALSE, FALSE,
|
| + (kInitializeEventBaseName + named_event_suffix).c_str()));
|
| + if (!initialize_event.IsValid()) {
|
| + LOG(ERROR) << "Failed to create event named "
|
| + << kInitializeEventBaseName + named_event_suffix;
|
| + return 1;
|
| + }
|
| +
|
| + base::win::ScopedHandle parent_process(
|
| + InterpretHandleSwitch(*cmd_line, kParentHandle));
|
| + if (!parent_process.IsValid())
|
| + return 1;
|
| +
|
| + base::win::ScopedHandle on_initialized_event(
|
| + InterpretHandleSwitch(*cmd_line, kEventHandle));
|
| + if (!on_initialized_event.IsValid())
|
| + return 1;
|
| +
|
| + while (true) {
|
| + // We loop as a convenient way to continue waiting for the exit_event after
|
| + // the initialize_event is signaled. We expect to get initialize_event zero
|
| + // or one times before exit_event, never more.
|
| + HANDLE handles[] = {exit_event.Get(), initialize_event.Get()};
|
| + DWORD result =
|
| + ::WaitForMultipleObjects(arraysize(handles), handles, FALSE, INFINITE);
|
| + switch (result) {
|
| + case WAIT_OBJECT_0:
|
| + // exit_event
|
| + return 0;
|
| + case WAIT_OBJECT_0 + 1:
|
| + // initialize_event
|
| + ::SetEvent(on_initialized_event.Get());
|
| + break;
|
| + case WAIT_FAILED:
|
| + PLOG(ERROR) << "Unexpected failure in WaitForMultipleObjects.";
|
| + return 1;
|
| + default:
|
| + NOTREACHED() << "Unexpected result from WaitForMultipleObjects: "
|
| + << result;
|
| + return 1;
|
| + }
|
| + }
|
| +}
|
| +
|
| +// Implements a thread to launch the ChromeWatcherClient and block on
|
| +// EnsureInitialized. Provides various helpers to interact with the
|
| +// ChromeWatcherClient.
|
| +class ChromeWatcherClientThread : public base::SimpleThread {
|
| + public:
|
| + ChromeWatcherClientThread()
|
| + : client_(base::Bind(&ChromeWatcherClientThread::GenerateCommandLine,
|
| + base::Unretained(this))),
|
| + complete_(false, false),
|
| + result_(false),
|
| + SimpleThread("ChromeWatcherClientTest thread") {}
|
| +
|
| + // Waits up to |timeout| for the call to EnsureInitialized to complete. If it
|
| + // does, sets |result| to the return value of EnsureInitialized and returns
|
| + // true. Otherwise returns false.
|
| + bool WaitForResultWithTimeout(const base::TimeDelta& timeout, bool* result) {
|
| + if (!complete_.TimedWait(timeout))
|
| + return false;
|
| + *result = result_;
|
| + return true;
|
| + }
|
| +
|
| + // Waits indefinitely for the call to WaitForInitialization to complete.
|
| + // Returns the return value of WaitForInitialization.
|
| + bool WaitForResult() {
|
| + complete_.Wait();
|
| + return result_;
|
| + }
|
| +
|
| + ChromeWatcherClient& client() { return client_; }
|
| +
|
| + base::string16 NamedEventSuffix() {
|
| + return base::UintToString16(base::GetCurrentProcId());
|
| + }
|
| +
|
| + // base::SimpleThread implementation.
|
| + void Run() override {
|
| + result_ = client_.LaunchWatcher();
|
| + if (result_)
|
| + result_ = client_.EnsureInitialized();
|
| + complete_.Signal();
|
| + }
|
| +
|
| + private:
|
| + // Returns a command line to launch back into ChromeWatcherClientTestProcess.
|
| + base::CommandLine GenerateCommandLine(HANDLE on_initialized_event,
|
| + HANDLE parent_handle) {
|
| + base::CommandLine ret = base::GetMultiProcessTestChildBaseCommandLine();
|
| + ret.AppendSwitchASCII(switches::kTestChildProcess,
|
| + "ChromeWatcherClientTestProcess");
|
| + ret.AppendSwitchASCII(kEventHandle,
|
| + base::UintToString(reinterpret_cast<unsigned int>(
|
| + on_initialized_event)));
|
| + ret.AppendSwitchASCII(
|
| + kParentHandle,
|
| + base::UintToString(reinterpret_cast<unsigned int>(parent_handle)));
|
| + ret.AppendSwitchASCII(kNamedEventSuffix,
|
| + base::UTF16ToASCII(NamedEventSuffix()));
|
| + return ret;
|
| + }
|
| +
|
| + // The instance under test.
|
| + ChromeWatcherClient client_;
|
| + // Signaled when WaitForInitialization returns.
|
| + base::WaitableEvent complete_;
|
| + // The return value of WaitForInitialization.
|
| + bool result_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ChromeWatcherClientThread);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class ChromeWatcherClientTest : public testing::Test {
|
| + protected:
|
| + // Sends a signal to the simulated watcher process to exit. Returns true if
|
| + // successful.
|
| + bool SignalExit() { return FALSE != ::SetEvent(exit_event_.Get()); }
|
| +
|
| + // Sends a signal to the simulated watcher process to signal its
|
| + // "initialization". Returns true if successful.
|
| + bool SignalInitialize() {
|
| + return FALSE != ::SetEvent(initialize_event_.Get());
|
| + }
|
| +
|
| + // The helper thread, which also provides access to the ChromeWatcherClient.
|
| + ChromeWatcherClientThread& thread() { return thread_; }
|
| +
|
| + // testing::Test implementation.
|
| + void SetUp() override {
|
| + exit_event_.Set(::CreateEvent(
|
| + NULL, FALSE, FALSE,
|
| + (kExitEventBaseName + thread_.NamedEventSuffix()).c_str()));
|
| + ASSERT_TRUE(exit_event_.IsValid());
|
| + initialize_event_.Set(::CreateEvent(
|
| + NULL, FALSE, FALSE,
|
| + (kInitializeEventBaseName + thread_.NamedEventSuffix()).c_str()));
|
| + ASSERT_TRUE(initialize_event_.IsValid());
|
| + }
|
| +
|
| + void TearDown() override {
|
| + if (thread_.client().launched()) {
|
| + SignalExit();
|
| + int exit_code = 0;
|
| + thread_.client().WaitForExit(&exit_code);
|
| + }
|
| + thread_.Join();
|
| + }
|
| +
|
| + private:
|
| + // Used to launch and block on the Chrome watcher process in a background
|
| + // thread.
|
| + ChromeWatcherClientThread thread_;
|
| + // Used to signal the Chrome watcher process to exit.
|
| + base::win::ScopedHandle exit_event_;
|
| + // Used to signal the Chrome watcher process to signal its own
|
| + // initialization..
|
| + base::win::ScopedHandle initialize_event_;
|
| +};
|
| +
|
| +TEST_F(ChromeWatcherClientTest, SuccessTest) {
|
| + thread().Start();
|
| + bool result = false;
|
| + // Give a broken implementation a chance to exit unexpectedly.
|
| + ASSERT_FALSE(thread().WaitForResultWithTimeout(
|
| + base::TimeDelta::FromMilliseconds(100), &result));
|
| + ASSERT_TRUE(SignalInitialize());
|
| + ASSERT_TRUE(thread().WaitForResult());
|
| + // The watcher should still be running. Give a broken implementation a chance
|
| + // to exit unexpectedly, then signal it to exit.
|
| + int exit_code = 0;
|
| + ASSERT_FALSE(thread().client().WaitForExitWithTimeout(
|
| + base::TimeDelta::FromMilliseconds(100), &exit_code));
|
| + SignalExit();
|
| + ASSERT_TRUE(thread().client().WaitForExit(&exit_code));
|
| + ASSERT_EQ(0, exit_code);
|
| +}
|
| +
|
| +TEST_F(ChromeWatcherClientTest, FailureTest) {
|
| + thread().Start();
|
| + bool result = false;
|
| + // Give a broken implementation a chance to exit unexpectedly.
|
| + ASSERT_FALSE(thread().WaitForResultWithTimeout(
|
| + base::TimeDelta::FromMilliseconds(100), &result));
|
| + ASSERT_TRUE(SignalExit());
|
| + ASSERT_FALSE(thread().WaitForResult());
|
| + int exit_code = 0;
|
| + ASSERT_TRUE(
|
| + thread().client().WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
|
| + ASSERT_EQ(0, exit_code);
|
| +}
|
|
|