1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/browser_watcher/exit_code_watcher_win.h"
7 #include "base/command_line.h"
8 #include "base/process/kill.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/test/multiprocess_test.h"
14 #include "base/test/test_reg_util_win.h"
15 #include "base/threading/platform_thread.h"
16 #include "base/time/time.h"
17 #include "base/win/scoped_handle.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/multiprocess_func_list.h"
21 namespace browser_watcher
{
25 const base::char16 kRegistryPath
[] = L
"Software\\BrowserWatcherTest";
27 MULTIPROCESS_TEST_MAIN(Sleeper
) {
28 // Sleep forever - the test harness will kill this process to give it an
30 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(INFINITE
));
34 class ScopedSleeperProcess
{
36 ScopedSleeperProcess() :
37 process_(base::kNullProcessHandle
),
38 process_id_(base::kNullProcessId
),
42 ~ScopedSleeperProcess() {
43 if (process_
!= base::kNullProcessHandle
) {
44 base::KillProcess(process_
, -1, true);
45 base::CloseProcessHandle(process_
);
50 ASSERT_EQ(base::kNullProcessHandle
, process_
);
52 base::CommandLine
cmd_line(base::GetMultiProcessTestChildBaseCommandLine());
53 base::LaunchOptions options
;
54 options
.start_hidden
= true;
55 process_
= base::SpawnMultiProcessTestChild("Sleeper", cmd_line
, options
);
56 process_id_
= base::GetProcId(process_
);
57 ASSERT_NE(base::kNullProcessHandle
, process_
);
60 void Kill(int exit_code
, bool wait
) {
61 ASSERT_NE(process_
, base::kNullProcessHandle
);
62 ASSERT_FALSE(is_killed_
);
63 ASSERT_TRUE(base::KillProcess(process_
, exit_code
, wait
));
67 void GetNewHandle(base::ProcessHandle
* output
) {
68 ASSERT_NE(process_
, base::kNullProcessHandle
);
70 ASSERT_TRUE(DuplicateHandle(::GetCurrentProcess(),
72 ::GetCurrentProcess(),
76 DUPLICATE_SAME_ACCESS
));
79 base::ProcessHandle
process() const { return process_
; }
80 base::ProcessId
process_id() const { return process_id_
; }
83 base::ProcessHandle process_
;
84 base::ProcessId process_id_
;
88 class BrowserWatcherTest
: public testing::Test
{
90 typedef testing::Test Super
;
92 static const int kExitCode
= 0xCAFEBABE;
94 BrowserWatcherTest() :
95 cmd_line_(base::CommandLine::NO_PROGRAM
),
96 process_(base::kNullProcessHandle
) {
99 virtual void SetUp() override
{
102 override_manager_
.OverrideRegistry(HKEY_CURRENT_USER
);
105 virtual void TearDown() override
{
106 if (process_
!= base::kNullProcessHandle
) {
107 base::CloseProcessHandle(process_
);
108 process_
= base::kNullProcessHandle
;
114 void OpenSelfWithAccess(uint32 access
) {
115 ASSERT_EQ(base::kNullProcessHandle
, process_
);
116 ASSERT_TRUE(base::OpenProcessHandleWithAccess(
117 base::GetCurrentProcId(), access
, &process_
));
120 void VerifyWroteExitCode(base::ProcessId proc_id
, int exit_code
) {
121 base::win::RegistryValueIterator
it(
122 HKEY_CURRENT_USER
, kRegistryPath
);
124 ASSERT_EQ(1, it
.ValueCount());
125 base::win::RegKey
key(HKEY_CURRENT_USER
,
129 // The value name should encode the process id at the start.
130 EXPECT_TRUE(StartsWith(it
.Name(),
131 base::StringPrintf(L
"%d-", proc_id
),
134 ASSERT_EQ(ERROR_SUCCESS
, key
.ReadValueDW(it
.Name(), &value
));
135 ASSERT_EQ(exit_code
, value
);
139 base::CommandLine cmd_line_
;
140 base::ProcessHandle process_
;
141 registry_util::RegistryOverrideManager override_manager_
;
146 TEST_F(BrowserWatcherTest
, ExitCodeWatcherInvalidCmdLineFailsInit
) {
147 ExitCodeWatcher
watcher(kRegistryPath
);
149 // An empty command line should fail.
150 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
152 // A non-numeric parent-handle argument should fail.
153 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
, "asdf");
154 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
157 TEST_F(BrowserWatcherTest
, ExitCodeWatcherInvalidHandleFailsInit
) {
158 ExitCodeWatcher
watcher(kRegistryPath
);
160 // A waitable event has a non process-handle.
161 base::WaitableEvent
event(false, false);
163 // A non-process handle should fail.
164 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
165 base::StringPrintf("%d", event
.handle()));
166 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
169 TEST_F(BrowserWatcherTest
, ExitCodeWatcherNoAccessHandleFailsInit
) {
170 ExitCodeWatcher
watcher(kRegistryPath
);
172 // Open a SYNCHRONIZE-only handle to this process.
173 ASSERT_NO_FATAL_FAILURE(OpenSelfWithAccess(SYNCHRONIZE
));
175 // A process handle with insufficient access should fail.
176 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
177 base::StringPrintf("%d", process_
));
178 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
181 TEST_F(BrowserWatcherTest
, ExitCodeWatcherSucceedsInit
) {
182 ExitCodeWatcher
watcher(kRegistryPath
);
184 // Open a handle to this process with sufficient access for the watcher.
185 ASSERT_NO_FATAL_FAILURE(
186 OpenSelfWithAccess(SYNCHRONIZE
| PROCESS_QUERY_INFORMATION
));
188 // A process handle with sufficient access should succeed init.
189 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
190 base::StringPrintf("%d", process_
));
191 EXPECT_TRUE(watcher
.ParseArguments(cmd_line_
));
193 ASSERT_EQ(process_
, watcher
.process());
195 // The watcher takes ownership of the handle, make sure it's not
197 process_
= base::kNullProcessHandle
;
200 TEST_F(BrowserWatcherTest
, ExitCodeWatcherOnExitedProcess
) {
201 ScopedSleeperProcess sleeper
;
202 ASSERT_NO_FATAL_FAILURE(sleeper
.Launch());
204 // Create a new handle to the sleeper process. This handle will leak in
205 // the case this test fails. A ScopedHandle cannot be used here, as the
206 // ownership would momentarily be held by two of them, which is disallowed.
207 base::ProcessHandle sleeper_handle
;
208 sleeper
.GetNewHandle(&sleeper_handle
);
210 ExitCodeWatcher
watcher(kRegistryPath
);
212 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
213 base::StringPrintf("%d", sleeper_handle
));
214 EXPECT_TRUE(watcher
.ParseArguments(cmd_line_
));
215 ASSERT_EQ(sleeper_handle
, watcher
.process());
217 // Verify that the watcher wrote a sentinel for the process.
218 VerifyWroteExitCode(sleeper
.process_id(), STILL_ACTIVE
);
220 // Kill the sleeper, and make sure it's exited before we continue.
221 ASSERT_NO_FATAL_FAILURE(sleeper
.Kill(kExitCode
, true));
223 watcher
.WaitForExit();
225 VerifyWroteExitCode(sleeper
.process_id(), kExitCode
);
228 } // namespace browser_watcher