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/bind.h"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "base/process/kill.h"
14 #include "base/process/process.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/test/multiprocess_test.h"
18 #include "base/test/test_reg_util_win.h"
19 #include "base/win/scoped_handle.h"
20 #include "base/win/windows_version.h"
21 #include "components/browser_watcher/exit_code_watcher_win.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "testing/multiprocess_func_list.h"
25 namespace browser_watcher
{
29 // Command line switches used to communiate to the child test.
30 const char kParentHandle
[] = "parent-handle";
31 const char kLeakHandle
[] = "leak-handle";
32 const char kNoLeakHandle
[] = "no-leak-handle";
34 bool IsValidParentProcessHandle(base::CommandLine
& cmd_line
,
35 const char* switch_name
) {
36 std::string str_handle
=
37 cmd_line
.GetSwitchValueASCII(switch_name
);
39 unsigned int_handle
= 0;
40 if (!base::StringToUint(str_handle
, &int_handle
))
43 base::ProcessHandle handle
=
44 reinterpret_cast<base::ProcessHandle
>(int_handle
);
45 // Verify that we can get the associated process id.
46 base::ProcessId parent_id
= base::GetProcId(handle
);
48 // Unable to get the parent pid - perhaps insufficient permissions.
52 // Make sure the handle grants SYNCHRONIZE by waiting on it.
53 DWORD err
= ::WaitForSingleObject(handle
, 0);
54 if (err
!= WAIT_OBJECT_0
&& err
!= WAIT_TIMEOUT
) {
55 // Unable to wait on the handle - perhaps insufficient permissions.
62 MULTIPROCESS_TEST_MAIN(VerifyParentHandle
) {
63 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
65 // Make sure we got a valid parent process handle from the watcher client.
66 if (!IsValidParentProcessHandle(*cmd_line
, kParentHandle
)) {
67 LOG(ERROR
) << "Invalid or missing parent-handle.";
71 // If in the legacy mode, we expect this second handle will leak into the
72 // child process. This mainly serves to verify that the legacy mode is
74 if (cmd_line
->HasSwitch(kLeakHandle
) &&
75 !IsValidParentProcessHandle(*cmd_line
, kLeakHandle
)) {
76 LOG(ERROR
) << "Parent process handle unexpectedly didn't leak.";
80 // If not in the legacy mode, this second handle should not leak into the
82 if (cmd_line
->HasSwitch(kNoLeakHandle
) &&
83 IsValidParentProcessHandle(*cmd_line
, kLeakHandle
)) {
84 LOG(ERROR
) << "Parent process handle unexpectedly leaked.";
91 class WatcherClientTest
: public base::MultiProcessTest
{
93 virtual void SetUp() {
94 // Open an inheritable handle on our own process to test handle leakage.
95 self_
.Set(::OpenProcess(SYNCHRONIZE
| PROCESS_QUERY_INFORMATION
,
96 TRUE
, // Ineritable handle.
97 base::GetCurrentProcId()));
99 ASSERT_TRUE(self_
.IsValid());
107 // Get a base command line to launch back into this test fixture.
108 base::CommandLine
GetBaseCommandLine(HandlePolicy handle_policy
,
109 HANDLE parent_handle
) {
110 base::CommandLine ret
= base::GetMultiProcessTestChildBaseCommandLine();
112 ret
.AppendSwitchASCII(switches::kTestChildProcess
, "VerifyParentHandle");
113 ret
.AppendSwitchASCII(kParentHandle
,
114 base::StringPrintf("%d", parent_handle
));
116 switch (handle_policy
) {
118 ret
.AppendSwitchASCII(kLeakHandle
,
119 base::StringPrintf("%d", self_
.Get()));
123 ret
.AppendSwitchASCII(kNoLeakHandle
,
124 base::StringPrintf("%d", self_
.Get()));
128 ADD_FAILURE() << "Impossible handle_policy";
134 WatcherClient::CommandLineGenerator
GetBaseCommandLineGenerator(
135 HandlePolicy handle_policy
) {
136 return base::Bind(&WatcherClientTest::GetBaseCommandLine
,
137 base::Unretained(this), handle_policy
);
140 void AssertSuccessfulExitCode(base::Process process
) {
141 ASSERT_TRUE(process
.IsValid());
143 if (!process
.WaitForExit(&exit_code
))
144 FAIL() << "Process::WaitForExit failed.";
145 ASSERT_EQ(0, exit_code
);
148 // Inheritable process handle used for testing.
149 base::win::ScopedHandle self_
;
154 // TODO(siggi): More testing - test WatcherClient base implementation.
156 TEST_F(WatcherClientTest
, LaunchWatcherSucceeds
) {
157 // We can only use the non-legacy launch method on Windows Vista or better.
158 if (base::win::GetVersion() < base::win::VERSION_VISTA
)
161 WatcherClient
client(GetBaseCommandLineGenerator(NO_LEAK_HANDLE
));
162 ASSERT_FALSE(client
.use_legacy_launch());
164 client
.LaunchWatcher();
166 ASSERT_NO_FATAL_FAILURE(
167 AssertSuccessfulExitCode(client
.process().Duplicate()));
170 TEST_F(WatcherClientTest
, LaunchWatcherLegacyModeSucceeds
) {
171 // Test the XP-compatible legacy launch mode. This is expected to leak
172 // a handle to the child process.
173 WatcherClient
client(GetBaseCommandLineGenerator(LEAK_HANDLE
));
175 // Use the legacy launch mode.
176 client
.set_use_legacy_launch(true);
178 client
.LaunchWatcher();
180 ASSERT_NO_FATAL_FAILURE(
181 AssertSuccessfulExitCode(client
.process().Duplicate()));
184 TEST_F(WatcherClientTest
, LegacyModeDetectedOnXP
) {
185 // This test only works on Windows XP.
186 if (base::win::GetVersion() > base::win::VERSION_XP
)
189 // Test that the client detects the need to use legacy launch mode, and that
190 // it works on Windows XP.
191 WatcherClient
client(GetBaseCommandLineGenerator(LEAK_HANDLE
));
192 ASSERT_TRUE(client
.use_legacy_launch());
194 client
.LaunchWatcher();
196 ASSERT_NO_FATAL_FAILURE(
197 AssertSuccessfulExitCode(client
.process().Duplicate()));
200 } // namespace browser_watcher