1 // Copyright 2015 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 "chrome/browser/process_singleton.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/files/file_path.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/process/launch.h"
17 #include "base/process/process.h"
18 #include "base/process/process_handle.h"
19 #include "base/strings/string16.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/test/multiprocess_test.h"
22 #include "base/win/scoped_handle.h"
23 #include "base/win/wrapped_window_proc.h"
24 #include "chrome/browser/chrome_process_finder_win.h"
25 #include "chrome/common/chrome_constants.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "content/public/common/result_codes.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "testing/multiprocess_func_list.h"
33 const char kReadyEventNameFlag
[] = "ready_event_name";
34 const char kContinueEventNameFlag
[] = "continue_event_name";
35 const char kCreateWindowFlag
[] = "create_window";
36 const int kErrorResultCode
= 0x345;
38 bool NotificationCallback(const base::CommandLine
& command_line
,
39 const base::FilePath
& current_directory
) {
40 // This is never called in this test, but would signal that the singleton
41 // notification was successfully handled.
46 // The ProcessSingleton kills hung browsers with no visible windows without user
47 // interaction. If a hung browser has visible UI, however, it asks the user
49 // This class is the very minimal implementation to create a visible window
50 // in the hung test process to allow testing the latter path.
51 class ScopedVisibleWindow
{
53 ScopedVisibleWindow() : class_(0), window_(NULL
) {}
54 ~ScopedVisibleWindow() {
56 ::DestroyWindow(window_
);
58 ::UnregisterClass(reinterpret_cast<LPCWSTR
>(class_
), NULL
);
62 WNDCLASSEX wnd_cls
= {0};
63 base::win::InitializeWindowClass(
64 L
"ProcessSingletonTest", base::win::WrappedWindowProc
<::DefWindowProc
>,
75 class_
= ::RegisterClassEx(&wnd_cls
);
78 window_
= ::CreateWindow(reinterpret_cast<LPCWSTR
>(class_
), 0, WS_POPUP
, 0,
79 0, 0, 0, 0, 0, NULL
, 0);
82 ::ShowWindow(window_
, SW_SHOW
);
92 DISALLOW_COPY_AND_ASSIGN(ScopedVisibleWindow
);
95 MULTIPROCESS_TEST_MAIN(ProcessSingletonTestProcessMain
) {
96 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
97 base::FilePath user_data_dir
=
98 cmd_line
->GetSwitchValuePath(switches::kUserDataDir
);
99 if (user_data_dir
.empty())
100 return kErrorResultCode
;
102 base::string16 ready_event_name
=
103 cmd_line
->GetSwitchValueNative(kReadyEventNameFlag
);
105 base::win::ScopedHandle
ready_event(
106 ::OpenEvent(EVENT_MODIFY_STATE
, FALSE
, ready_event_name
.c_str()));
107 if (!ready_event
.IsValid())
108 return kErrorResultCode
;
110 base::string16 continue_event_name
=
111 cmd_line
->GetSwitchValueNative(kContinueEventNameFlag
);
113 base::win::ScopedHandle
continue_event(
114 ::OpenEvent(SYNCHRONIZE
, FALSE
, continue_event_name
.c_str()));
115 if (!continue_event
.IsValid())
116 return kErrorResultCode
;
118 ScopedVisibleWindow visible_window
;
119 if (cmd_line
->HasSwitch(kCreateWindowFlag
)) {
120 if (!visible_window
.Create())
121 return kErrorResultCode
;
124 // Instantiate the process singleton.
125 ProcessSingleton
process_singleton(user_data_dir
,
126 base::Bind(&NotificationCallback
));
128 if (!process_singleton
.Create())
129 return kErrorResultCode
;
131 // Signal ready and block for the continue event.
132 if (!::SetEvent(ready_event
.Get()))
133 return kErrorResultCode
;
135 if (::WaitForSingleObject(continue_event
.Get(), INFINITE
) != WAIT_OBJECT_0
)
136 return kErrorResultCode
;
141 // This fixture is for testing the Windows platform-specific failure modes
142 // of rendezvous, specifically the ones where the singleton-owning process
144 class ProcessSingletonTest
: public base::MultiProcessTest
{
146 enum WindowOption
{ WITH_WINDOW
, NO_WINDOW
};
148 ProcessSingletonTest()
149 : window_option_(NO_WINDOW
), should_kill_called_(false) {}
151 void SetUp() override
{
152 ASSERT_NO_FATAL_FAILURE(base::MultiProcessTest::SetUp());
154 // Drop the process finder notification timeout to one second for testing.
155 old_notification_timeout_
= chrome::SetNotificationTimeoutForTesting(
156 base::TimeDelta::FromSeconds(1));
159 void TearDown() override
{
160 chrome::SetNotificationTimeoutForTesting(old_notification_timeout_
);
162 if (browser_victim_
.IsValid()) {
163 EXPECT_TRUE(::SetEvent(continue_event_
.Get()));
165 EXPECT_TRUE(browser_victim_
.WaitForExit(&exit_code
));
168 base::MultiProcessTest::TearDown();
171 void LaunchHungBrowserProcess(WindowOption window_option
) {
172 // Create a unique user data dir to rendezvous on.
173 ASSERT_TRUE(user_data_dir_
.CreateUniqueTempDir());
175 // Create the named "ready" event, this is unique to our process.
177 base::StringPrintf(L
"ready-event-%d", base::GetCurrentProcId());
178 base::win::ScopedHandle
ready_event(
179 ::CreateEvent(NULL
, TRUE
, FALSE
, ready_event_name_
.c_str()));
180 ASSERT_TRUE(ready_event
.IsValid());
182 // Create the named "continue" event, this is unique to our process.
183 continue_event_name_
=
184 base::StringPrintf(L
"continue-event-%d", base::GetCurrentProcId());
186 ::CreateEvent(NULL
, TRUE
, FALSE
, continue_event_name_
.c_str()));
187 ASSERT_TRUE(continue_event_
.IsValid());
189 window_option_
= window_option
;
191 base::LaunchOptions options
;
192 options
.start_hidden
= true;
194 SpawnChildWithOptions("ProcessSingletonTestProcessMain", options
);
196 // Wait for the ready event (or process exit).
197 HANDLE handles
[] = {ready_event
.Get(), browser_victim_
.Handle()};
198 // The wait should always return because either |ready_event| is signaled or
199 // |browser_victim_| died unexpectedly or exited on error.
201 ::WaitForMultipleObjects(arraysize(handles
), handles
, FALSE
, INFINITE
);
202 ASSERT_EQ(WAIT_OBJECT_0
, result
);
205 base::CommandLine
MakeCmdLine(const std::string
& procname
) override
{
206 base::CommandLine cmd_line
= base::MultiProcessTest::MakeCmdLine(procname
);
208 cmd_line
.AppendSwitchPath(switches::kUserDataDir
, user_data_dir_
.path());
209 cmd_line
.AppendSwitchNative(kReadyEventNameFlag
, ready_event_name_
);
210 cmd_line
.AppendSwitchNative(kContinueEventNameFlag
, continue_event_name_
);
211 if (window_option_
== WITH_WINDOW
)
212 cmd_line
.AppendSwitch(kCreateWindowFlag
);
217 void PrepareTest(WindowOption window_option
, bool allow_kill
) {
218 ASSERT_NO_FATAL_FAILURE(LaunchHungBrowserProcess(window_option
));
220 // The ready event has been signalled - the process singleton is held by
221 // the hung sub process.
222 test_singleton_
.reset(new ProcessSingleton(
223 user_data_dir(), base::Bind(&NotificationCallback
)));
225 test_singleton_
->OverrideShouldKillRemoteProcessCallbackForTesting(
226 base::Bind(&ProcessSingletonTest::MockShouldKillRemoteProcess
,
227 base::Unretained(this), allow_kill
));
230 base::Process
* browser_victim() { return &browser_victim_
; }
231 const base::FilePath
& user_data_dir() const { return user_data_dir_
.path(); }
232 ProcessSingleton
* test_singleton() const { return test_singleton_
.get(); }
233 bool should_kill_called() const { return should_kill_called_
; }
236 bool MockShouldKillRemoteProcess(bool allow_kill
) {
237 should_kill_called_
= true;
241 base::string16 ready_event_name_
;
242 base::string16 continue_event_name_
;
244 WindowOption window_option_
;
245 base::ScopedTempDir user_data_dir_
;
246 base::Process browser_victim_
;
247 base::win::ScopedHandle continue_event_
;
249 scoped_ptr
<ProcessSingleton
> test_singleton_
;
251 base::TimeDelta old_notification_timeout_
;
252 bool should_kill_called_
;
254 DISALLOW_COPY_AND_ASSIGN(ProcessSingletonTest
);
259 TEST_F(ProcessSingletonTest
, KillsHungBrowserWithNoWindows
) {
260 ASSERT_NO_FATAL_FAILURE(PrepareTest(NO_WINDOW
, false));
262 // As the hung browser has no visible window, it'll be killed without
264 ProcessSingleton::NotifyResult notify_result
=
265 test_singleton()->NotifyOtherProcessOrCreate();
266 ASSERT_EQ(ProcessSingleton::PROFILE_IN_USE
, notify_result
);
268 // The should-kill callback should not have been called, as the "browser" does
269 // not have visible window.
270 EXPECT_FALSE(should_kill_called());
272 // Verify that the hung browser has beem terminated with the
273 // RESULT_CODE_HUNG exit code.
276 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code
));
277 EXPECT_EQ(content::RESULT_CODE_HUNG
, exit_code
);
280 TEST_F(ProcessSingletonTest
, DoesntKillWithoutUserPermission
) {
281 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW
, false));
283 // As the hung browser has a visible window, this should query the user
284 // before killing the hung process.
285 ProcessSingleton::NotifyResult notify_result
=
286 test_singleton()->NotifyOtherProcessOrCreate();
287 ASSERT_EQ(ProcessSingleton::PROCESS_NOTIFIED
, notify_result
);
289 // The should-kill callback should have been called, as the "browser" has a
291 EXPECT_TRUE(should_kill_called());
293 // Make sure the process hasn't been killed.
296 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code
));
299 TEST_F(ProcessSingletonTest
, KillWithUserPermission
) {
300 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW
, true));
302 // As the hung browser has a visible window, this should query the user
303 // before killing the hung process.
304 ProcessSingleton::NotifyResult notify_result
=
305 test_singleton()->NotifyOtherProcessOrCreate();
306 ASSERT_EQ(ProcessSingleton::PROFILE_IN_USE
, notify_result
);
308 // The should-kill callback should have been called, as the "browser" has a
310 EXPECT_TRUE(should_kill_called());
312 // Verify that the hung browser has beem terminated with the
313 // RESULT_CODE_HUNG exit code.
316 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code
));
317 EXPECT_EQ(content::RESULT_CODE_HUNG
, exit_code
);