1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// Copyright (c) 2014 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 "components/browser_watcher/exit_code_watcher_win.h"
#include <stdint.h>
#include <utility>
#include "base/command_line.h"
#include "base/process/process.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_reg_util_win.h"
#include "base/threading/platform_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 browser_watcher {
namespace {
const base::char16 kRegistryPath[] = L"Software\\ExitCodeWatcherTest";
MULTIPROCESS_TEST_MAIN(Sleeper) {
// Sleep forever - the test harness will kill this process to give it an
// exit code.
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(INFINITE));
return 1;
}
class ScopedSleeperProcess {
public:
ScopedSleeperProcess() : is_killed_(false) {
}
~ScopedSleeperProcess() {
if (process_.IsValid()) {
process_.Terminate(-1, false);
EXPECT_TRUE(process_.WaitForExit(nullptr));
}
}
void Launch() {
ASSERT_FALSE(process_.IsValid());
base::CommandLine cmd_line(base::GetMultiProcessTestChildBaseCommandLine());
base::LaunchOptions options;
options.start_hidden = true;
process_ = base::SpawnMultiProcessTestChild("Sleeper", cmd_line, options);
ASSERT_TRUE(process_.IsValid());
}
void Kill(int exit_code, bool wait) {
ASSERT_TRUE(process_.IsValid());
ASSERT_FALSE(is_killed_);
process_.Terminate(exit_code, false);
int seen_exit_code = 0;
EXPECT_TRUE(process_.WaitForExit(&seen_exit_code));
EXPECT_EQ(exit_code, seen_exit_code);
is_killed_ = true;
}
const base::Process& process() const { return process_; }
private:
base::Process process_;
bool is_killed_;
};
class ExitCodeWatcherTest : public testing::Test {
public:
typedef testing::Test Super;
static const int kExitCode = 0xCAFEBABE;
ExitCodeWatcherTest() : cmd_line_(base::CommandLine::NO_PROGRAM) {}
void SetUp() override {
Super::SetUp();
override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
}
base::Process OpenSelfWithAccess(uint32_t access) {
return base::Process::OpenWithAccess(base::GetCurrentProcId(), access);
}
void VerifyWroteExitCode(base::ProcessId proc_id, int exit_code) {
base::win::RegistryValueIterator it(
HKEY_CURRENT_USER, kRegistryPath);
ASSERT_EQ(1u, it.ValueCount());
base::win::RegKey key(HKEY_CURRENT_USER,
kRegistryPath,
KEY_QUERY_VALUE);
// The value name should encode the process id at the start.
EXPECT_TRUE(base::StartsWith(
it.Name(),
base::StringPrintf(L"%d-", proc_id),
base::CompareCase::SENSITIVE));
DWORD value = 0;
ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(it.Name(), &value));
ASSERT_EQ(exit_code, static_cast<int>(value));
}
protected:
base::CommandLine cmd_line_;
registry_util::RegistryOverrideManager override_manager_;
};
} // namespace
TEST_F(ExitCodeWatcherTest, ExitCodeWatcherInvalidHandleFailsInit) {
ExitCodeWatcher watcher(kRegistryPath);
// A waitable event has a non process-handle.
base::Process event(::CreateEvent(NULL, false, false, NULL));
// A non-process handle should fail.
EXPECT_FALSE(watcher.Initialize(std::move(event)));
}
TEST_F(ExitCodeWatcherTest, ExitCodeWatcherNoAccessHandleFailsInit) {
ExitCodeWatcher watcher(kRegistryPath);
// Open a SYNCHRONIZE-only handle to this process.
base::Process self = OpenSelfWithAccess(SYNCHRONIZE);
ASSERT_TRUE(self.IsValid());
// A process handle with insufficient access should fail.
EXPECT_FALSE(watcher.Initialize(std::move(self)));
}
TEST_F(ExitCodeWatcherTest, ExitCodeWatcherSucceedsInit) {
ExitCodeWatcher watcher(kRegistryPath);
// Open a handle to this process with sufficient access for the watcher.
base::Process self =
OpenSelfWithAccess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION);
ASSERT_TRUE(self.IsValid());
// A process handle with sufficient access should succeed init.
EXPECT_TRUE(watcher.Initialize(std::move(self)));
}
TEST_F(ExitCodeWatcherTest, ExitCodeWatcherOnExitedProcess) {
ScopedSleeperProcess sleeper;
ASSERT_NO_FATAL_FAILURE(sleeper.Launch());
ExitCodeWatcher watcher(kRegistryPath);
EXPECT_TRUE(watcher.Initialize(sleeper.process().Duplicate()));
// Verify that the watcher wrote a sentinel for the process.
VerifyWroteExitCode(sleeper.process().Pid(), STILL_ACTIVE);
// Kill the sleeper, and make sure it's exited before we continue.
ASSERT_NO_FATAL_FAILURE(sleeper.Kill(kExitCode, true));
watcher.WaitForExit();
EXPECT_EQ(kExitCode, watcher.exit_code());
VerifyWroteExitCode(sleeper.process().Pid(), kExitCode);
}
} // namespace browser_watcher
|