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/string_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "base/test/multiprocess_test.h"
13 #include "base/test/test_reg_util_win.h"
14 #include "base/threading/platform_thread.h"
15 #include "base/time/time.h"
16 #include "base/win/scoped_handle.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "testing/multiprocess_func_list.h"
20 namespace browser_watcher
{
24 const wchar_t kRegistryPath
[] = L
"Software\\BrowserWatcherTest";
26 MULTIPROCESS_TEST_MAIN(Sleeper
) {
27 // Sleep forever - the test harness will kill this process to give it an
29 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(INFINITE
));
33 class ScopedSleeperProcess
{
35 ScopedSleeperProcess() :
36 process_(base::kNullProcessHandle
),
37 process_id_(base::kNullProcessId
),
41 ~ScopedSleeperProcess() {
42 if (process_
!= base::kNullProcessHandle
) {
43 base::KillProcess(process_
, -1, true);
44 base::CloseProcessHandle(process_
);
49 ASSERT_EQ(base::kNullProcessHandle
, process_
);
51 base::CommandLine
cmd_line(base::GetMultiProcessTestChildBaseCommandLine());
52 base::LaunchOptions options
;
53 options
.start_hidden
= true;
54 process_
= base::SpawnMultiProcessTestChild("Sleeper", cmd_line
, options
);
55 process_id_
= base::GetProcId(process_
);
56 ASSERT_NE(base::kNullProcessHandle
, process_
);
59 void Kill(int exit_code
, bool wait
) {
60 ASSERT_NE(process_
, base::kNullProcessHandle
);
61 ASSERT_FALSE(is_killed_
);
62 ASSERT_TRUE(base::KillProcess(process_
, exit_code
, wait
));
66 void GetNewHandle(base::ProcessHandle
* output
) {
67 ASSERT_NE(process_
, base::kNullProcessHandle
);
69 ASSERT_TRUE(DuplicateHandle(::GetCurrentProcess(),
71 ::GetCurrentProcess(),
75 DUPLICATE_SAME_ACCESS
));
78 base::ProcessHandle
process() const { return process_
; }
79 base::ProcessId
process_id() const { return process_id_
; }
82 base::ProcessHandle process_
;
83 base::ProcessId process_id_
;
87 class BrowserWatcherTest
: public testing::Test
{
89 typedef testing::Test Super
;
91 static const int kExitCode
= 0xCAFEBABE;
93 BrowserWatcherTest() :
94 cmd_line_(base::CommandLine::NO_PROGRAM
),
95 process_(base::kNullProcessHandle
) {
98 virtual void SetUp() override
{
101 override_manager_
.OverrideRegistry(HKEY_CURRENT_USER
);
104 virtual void TearDown() override
{
105 if (process_
!= base::kNullProcessHandle
) {
106 base::CloseProcessHandle(process_
);
107 process_
= base::kNullProcessHandle
;
113 void OpenSelfWithAccess(uint32 access
) {
114 ASSERT_EQ(base::kNullProcessHandle
, process_
);
115 ASSERT_TRUE(base::OpenProcessHandleWithAccess(
116 base::GetCurrentProcId(), access
, &process_
));
119 void VerifyWroteExitCode(base::ProcessId proc_id
, int exit_code
) {
120 base::win::RegistryValueIterator
it(
121 HKEY_CURRENT_USER
, kRegistryPath
);
123 ASSERT_EQ(1, it
.ValueCount());
124 base::win::RegKey
key(HKEY_CURRENT_USER
,
128 // The value name should encode the process id at the start.
129 EXPECT_TRUE(StartsWith(it
.Name(),
130 base::StringPrintf(L
"%d-", proc_id
),
133 ASSERT_EQ(ERROR_SUCCESS
, key
.ReadValueDW(it
.Name(), &value
));
134 ASSERT_EQ(exit_code
, value
);
138 base::CommandLine cmd_line_
;
139 base::ProcessHandle process_
;
140 registry_util::RegistryOverrideManager override_manager_
;
145 TEST_F(BrowserWatcherTest
, ExitCodeWatcherInvalidCmdLineFailsInit
) {
146 ExitCodeWatcher
watcher(kRegistryPath
);
148 // An empty command line should fail.
149 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
151 // A non-numeric parent-handle argument should fail.
152 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
, "asdf");
153 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
156 TEST_F(BrowserWatcherTest
, ExitCodeWatcherInvalidHandleFailsInit
) {
157 ExitCodeWatcher
watcher(kRegistryPath
);
159 // A waitable event has a non process-handle.
160 base::WaitableEvent
event(false, false);
162 // A non-process handle should fail.
163 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
164 base::StringPrintf("%d", event
.handle()));
165 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
168 TEST_F(BrowserWatcherTest
, ExitCodeWatcherNoAccessHandleFailsInit
) {
169 ExitCodeWatcher
watcher(kRegistryPath
);
171 // Open a SYNCHRONIZE-only handle to this process.
172 ASSERT_NO_FATAL_FAILURE(OpenSelfWithAccess(SYNCHRONIZE
));
174 // A process handle with insufficient access should fail.
175 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
176 base::StringPrintf("%d", process_
));
177 EXPECT_FALSE(watcher
.ParseArguments(cmd_line_
));
180 TEST_F(BrowserWatcherTest
, ExitCodeWatcherSucceedsInit
) {
181 ExitCodeWatcher
watcher(kRegistryPath
);
183 // Open a handle to this process with sufficient access for the watcher.
184 ASSERT_NO_FATAL_FAILURE(
185 OpenSelfWithAccess(SYNCHRONIZE
| PROCESS_QUERY_INFORMATION
));
187 // A process handle with sufficient access should succeed init.
188 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
189 base::StringPrintf("%d", process_
));
190 EXPECT_TRUE(watcher
.ParseArguments(cmd_line_
));
192 ASSERT_EQ(process_
, watcher
.process());
194 // The watcher takes ownership of the handle, make sure it's not
196 process_
= base::kNullProcessHandle
;
199 TEST_F(BrowserWatcherTest
, ExitCodeWatcherOnExitedProcess
) {
200 ScopedSleeperProcess sleeper
;
201 ASSERT_NO_FATAL_FAILURE(sleeper
.Launch());
203 // Create a new handle to the sleeper process. This handle will leak in
204 // the case this test fails. A ScopedHandle cannot be used here, as the
205 // ownership would momentarily be held by two of them, which is disallowed.
206 base::ProcessHandle sleeper_handle
;
207 sleeper
.GetNewHandle(&sleeper_handle
);
209 ExitCodeWatcher
watcher(kRegistryPath
);
211 cmd_line_
.AppendSwitchASCII(ExitCodeWatcher::kParenthHandleSwitch
,
212 base::StringPrintf("%d", sleeper_handle
));
213 EXPECT_TRUE(watcher
.ParseArguments(cmd_line_
));
214 ASSERT_EQ(sleeper_handle
, watcher
.process());
216 // Verify that the watcher wrote a sentinel for the process.
217 VerifyWroteExitCode(sleeper
.process_id(), STILL_ACTIVE
);
219 // Kill the sleeper, and make sure it's exited before we continue.
220 ASSERT_NO_FATAL_FAILURE(sleeper
.Kill(kExitCode
, true));
222 watcher
.WaitForExit();
224 VerifyWroteExitCode(sleeper
.process_id(), kExitCode
);
227 } // namespace browser_watcher