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/watcher_client_win.h"
9 #include "base/base_switches.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/process/process_handle.h"
13 #include "base/process/kill.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/test/multiprocess_test.h"
17 #include "base/test/test_reg_util_win.h"
18 #include "base/win/scoped_handle.h"
19 #include "base/win/windows_version.h"
20 #include "components/browser_watcher/exit_code_watcher_win.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "testing/multiprocess_func_list.h"
24 namespace browser_watcher
{
28 // Command line switches used to communiate to the child test.
29 const char kParentPid
[] = "parent-pid";
30 const char kLeakHandle
[] = "leak-handle";
31 const char kNoLeakHandle
[] = "no-leak-handle";
33 bool IsValidParentProcessHandle(base::CommandLine
& cmd_line
,
34 const char* switch_name
) {
35 std::string str_handle
=
36 cmd_line
.GetSwitchValueASCII(switch_name
);
38 unsigned int_handle
= 0;
39 if (!base::StringToUint(str_handle
, &int_handle
))
43 if (!base::StringToInt(cmd_line
.GetSwitchValueASCII(kParentPid
),
48 base::ProcessHandle handle
=
49 reinterpret_cast<base::ProcessHandle
>(int_handle
);
50 // Verify that we can get the associated process id.
51 base::ProcessId parent_id
= base::GetProcId(handle
);
53 // Unable to get the parent pid - perhaps insufficient permissions.
57 // Make sure the handle grants SYNCHRONIZE by waiting on it.
58 DWORD err
= ::WaitForSingleObject(handle
, 0);
59 if (err
!= WAIT_OBJECT_0
&& err
!= WAIT_TIMEOUT
) {
60 // Unable to wait on the handle - perhaps insufficient permissions.
67 MULTIPROCESS_TEST_MAIN(VerifyParentHandle
) {
68 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
70 // Make sure we got a valid parent process handle from the watcher client.
71 if (!IsValidParentProcessHandle(*cmd_line
,
72 ExitCodeWatcher::kParenthHandleSwitch
)) {
73 LOG(ERROR
) << "Invalid or missing parent-handle.";
77 // If in the legacy mode, we expect this second handle will leak into the
78 // child process. This mainly serves to verify that the legacy mode is
80 if (cmd_line
->HasSwitch(kLeakHandle
) &&
81 !IsValidParentProcessHandle(*cmd_line
, kLeakHandle
)) {
82 LOG(ERROR
) << "Parent process handle unexpectedly didn't leak.";
86 // If not in the legacy mode, this second handle should not leak into the
88 if (cmd_line
->HasSwitch(kNoLeakHandle
) &&
89 IsValidParentProcessHandle(*cmd_line
, kLeakHandle
)) {
90 LOG(ERROR
) << "Parent process handle unexpectedly leaked.";
97 class BrowserWatcherClientTest
: public base::MultiProcessTest
{
99 virtual void SetUp() {
100 // Open an inheritable handle on our own process to test handle leakage.
101 self_
.Set(::OpenProcess(SYNCHRONIZE
| PROCESS_QUERY_INFORMATION
,
102 TRUE
, // Ineritable handle.
103 base::GetCurrentProcId()));
105 ASSERT_TRUE(self_
.IsValid());
113 // Get a base command line to launch back into this test fixture.
114 base::CommandLine
GetBaseCommandLine(HandlePolicy handle_policy
) {
115 base::CommandLine ret
= base::GetMultiProcessTestChildBaseCommandLine();
117 ret
.AppendSwitchASCII(switches::kTestChildProcess
, "VerifyParentHandle");
118 ret
.AppendSwitchASCII(kParentPid
,
119 base::StringPrintf("%d", base::GetCurrentProcId()));
121 switch (handle_policy
) {
123 ret
.AppendSwitchASCII(kLeakHandle
,
124 base::StringPrintf("%d", self_
.Get()));
128 ret
.AppendSwitchASCII(kNoLeakHandle
,
129 base::StringPrintf("%d", self_
.Get()));
133 ADD_FAILURE() << "Impossible handle_policy";
139 void AssertSuccessfulExitCode(base::ProcessHandle handle
) {
140 ASSERT_NE(base::kNullProcessHandle
, handle
);
142 // Duplicate the process handle to work around the fact that
143 // WaitForExitCode closes it(!!!).
144 base::ProcessHandle dupe
= NULL
;
145 ASSERT_TRUE(::DuplicateHandle(base::GetCurrentProcessHandle(),
147 base::GetCurrentProcessHandle(),
149 SYNCHRONIZE
| PROCESS_QUERY_INFORMATION
,
152 ASSERT_NE(base::kNullProcessHandle
, dupe
);
154 if (!base::WaitForExitCode(dupe
, &exit_code
)) {
155 base::CloseProcessHandle(dupe
);
156 FAIL() << "WaitForExitCode failed.";
158 ASSERT_EQ(0, exit_code
);
161 // Inheritable process handle used for testing.
162 base::win::ScopedHandle self_
;
167 // TODO(siggi): More testing - test WatcherClient base implementation.
169 TEST_F(BrowserWatcherClientTest
, LaunchWatcherSucceeds
) {
170 // We can only use the non-legacy launch method on Windows Vista or better.
171 if (base::win::GetVersion() < base::win::VERSION_VISTA
)
174 WatcherClient
client(GetBaseCommandLine(NO_LEAK_HANDLE
));
175 ASSERT_FALSE(client
.use_legacy_launch());
177 client
.LaunchWatcher();
179 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client
.process()));
182 TEST_F(BrowserWatcherClientTest
, LaunchWatcherLegacyModeSucceeds
) {
183 // Test the XP-compatible legacy launch mode. This is expected to leak
184 // a handle to the child process.
185 WatcherClient
client(GetBaseCommandLine(LEAK_HANDLE
));
187 // Use the legacy launch mode.
188 client
.set_use_legacy_launch(true);
190 client
.LaunchWatcher();
192 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client
.process()));
195 TEST_F(BrowserWatcherClientTest
, LegacyModeDetectedOnXP
) {
196 // This test only works on Windows XP.
197 if (base::win::GetVersion() > base::win::VERSION_XP
)
200 // Test that the client detects the need to use legacy launch mode, and that
201 // it works on Windows XP.
202 WatcherClient
client(GetBaseCommandLine(LEAK_HANDLE
));
203 ASSERT_TRUE(client
.use_legacy_launch());
205 client
.LaunchWatcher();
207 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client
.process()));
210 } // namespace browser_watcher