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 "components/browser_watcher/window_hang_monitor_win.h"
9 #include "base/base_switches.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/callback.h"
13 #include "base/callback_helpers.h"
14 #include "base/command_line.h"
15 #include "base/location.h"
16 #include "base/macros.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/process/launch.h"
20 #include "base/process/process.h"
21 #include "base/process/process_handle.h"
22 #include "base/run_loop.h"
23 #include "base/strings/string16.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/synchronization/waitable_event.h"
28 #include "base/test/multiprocess_test.h"
29 #include "base/threading/thread.h"
30 #include "base/win/message_window.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "testing/multiprocess_func_list.h"
34 namespace browser_watcher
{
38 // Simulates a process that never opens a window.
39 MULTIPROCESS_TEST_MAIN(NoWindowChild
) {
44 // Manages a WindowHangMonitor that lives on a background thread.
45 class HangMonitorThread
{
47 // Instantiates the background thread.
49 : event_(WindowHangMonitor::WINDOW_NOT_FOUND
),
50 event_received_(false, false),
51 thread_("HangMonitorThread") {}
53 ~HangMonitorThread() {
54 if (hang_monitor_
.get())
58 // Starts the background thread and the monitor to observe the window named
59 // |window_name| in |process|. Blocks until the monitor has been initialized.
60 bool Start(base::Process process
, const base::string16
& window_name
) {
61 if (!thread_
.StartWithOptions(
62 base::Thread::Options(base::MessageLoop::TYPE_UI
, 0))) {
66 base::WaitableEvent
complete(false, false);
67 if (!thread_
.task_runner()->PostTask(
68 FROM_HERE
, base::Bind(&HangMonitorThread::StartupOnThread
,
69 base::Unretained(this), window_name
,
70 base::Passed(process
.Pass()),
71 base::Unretained(&complete
)))) {
80 // Returns true if a window event is detected within |timeout|.
81 bool TimedWaitForEvent(base::TimeDelta timeout
) {
82 return event_received_
.TimedWait(timeout
);
85 // Blocks indefinitely for a window event and returns it.
86 WindowHangMonitor::WindowEvent
WaitForEvent() {
87 event_received_
.Wait();
91 // Destroys the monitor and stops the background thread. Blocks until the
92 // operation completes.
93 void DestroyWatcher() {
94 thread_
.task_runner()->PostTask(
95 FROM_HERE
, base::Bind(&HangMonitorThread::ShutdownOnThread
,
96 base::Unretained(this)));
97 // This will block until the above-posted task completes.
102 // Invoked when the monitor signals an event. Unblocks a call to
103 // TimedWaitForEvent or WaitForEvent.
104 void EventCallback(WindowHangMonitor::WindowEvent event
) {
105 if (event_received_
.IsSignaled())
106 ADD_FAILURE() << "Multiple calls to EventCallback.";
108 event_received_
.Signal();
111 // Initializes the WindowHangMonitor to observe the window named |window_name|
112 // in |process|. Signals |complete| when done.
113 void StartupOnThread(const base::string16
& window_name
,
114 base::Process process
,
115 base::WaitableEvent
* complete
) {
116 hang_monitor_
.reset(new WindowHangMonitor(
117 base::TimeDelta::FromMilliseconds(100),
118 base::TimeDelta::FromMilliseconds(100),
119 base::Bind(&HangMonitorThread::EventCallback
, base::Unretained(this))));
120 hang_monitor_
->Initialize(process
.Pass(), window_name
);
124 // Destroys the WindowHangMonitor.
125 void ShutdownOnThread() { hang_monitor_
.reset(); }
127 // The detected event. Invalid if |event_received_| has not been signaled.
128 WindowHangMonitor::WindowEvent event_
;
129 // Indicates that |event_| has been assigned in response to a callback from
130 // the WindowHangMonitor.
131 base::WaitableEvent event_received_
;
132 // The WindowHangMonitor under test.
133 scoped_ptr
<WindowHangMonitor
> hang_monitor_
;
134 // The background thread.
135 base::Thread thread_
;
137 DISALLOW_COPY_AND_ASSIGN(HangMonitorThread
);
140 class WindowHangMonitorTest
: public testing::Test
{
142 WindowHangMonitorTest()
143 : ping_event_(false, false),
145 window_thread_("WindowHangMonitorTest window_thread") {}
147 void SetUp() override
{
148 // Pick a window name unique to this process.
149 window_name_
= base::StringPrintf(L
"WindowHanMonitorTest-%d",
150 base::GetCurrentProcId());
151 ASSERT_TRUE(window_thread_
.StartWithOptions(
152 base::Thread::Options(base::MessageLoop::TYPE_UI
, 0)));
155 void TearDown() override
{
156 DeleteMessageWindow();
157 window_thread_
.Stop();
160 void CreateMessageWindow() {
161 bool succeeded
= false;
162 base::WaitableEvent
created(true, false);
163 ASSERT_TRUE(window_thread_
.task_runner()->PostTask(
165 base::Bind(&WindowHangMonitorTest::CreateMessageWindowInWorkerThread
,
166 base::Unretained(this), window_name_
, &succeeded
,
169 ASSERT_TRUE(succeeded
);
172 void DeleteMessageWindow() {
173 base::WaitableEvent
deleted(true, false);
174 window_thread_
.task_runner()->PostTask(
176 base::Bind(&WindowHangMonitorTest::DeleteMessageWindowInWorkerThread
,
177 base::Unretained(this), &deleted
));
184 base::AutoLock
auto_lock(ping_lock_
);
195 HangMonitorThread
& monitor_thread() { return monitor_thread_
; }
197 const base::win::MessageWindow
* message_window() const {
198 return message_window_
.get();
201 const base::string16
& window_name() const { return window_name_
; }
203 base::Thread
* window_thread() { return &window_thread_
; }
206 bool MessageCallback(UINT message
,
210 EXPECT_EQ(window_thread_
.message_loop(), base::MessageLoop::current());
211 if (message
== WM_NULL
) {
212 base::AutoLock
auto_lock(ping_lock_
);
214 ping_event_
.Signal();
217 return false; // Pass through to DefWindowProc.
220 void CreateMessageWindowInWorkerThread(const base::string16
& name
,
222 base::WaitableEvent
* created
) {
223 message_window_
.reset(new base::win::MessageWindow
);
224 *success
= message_window_
->CreateNamed(
225 base::Bind(&WindowHangMonitorTest::MessageCallback
,
226 base::Unretained(this)),
231 void DeleteMessageWindowInWorkerThread(base::WaitableEvent
* deleted
) {
232 message_window_
.reset();
237 HangMonitorThread monitor_thread_
;
238 scoped_ptr
<base::win::MessageWindow
> message_window_
;
239 base::string16 window_name_
;
240 base::Lock ping_lock_
;
241 base::WaitableEvent ping_event_
;
243 base::Thread window_thread_
;
245 DISALLOW_COPY_AND_ASSIGN(WindowHangMonitorTest
);
250 TEST_F(WindowHangMonitorTest
, NoWindow
) {
251 base::CommandLine child_command_line
=
252 base::GetMultiProcessTestChildBaseCommandLine();
253 child_command_line
.AppendSwitchASCII(switches::kTestChildProcess
,
255 base::Process process
=
256 base::LaunchProcess(child_command_line
, base::LaunchOptions());
257 ASSERT_TRUE(process
.IsValid());
259 base::ScopedClosureRunner
terminate_process_runner(
260 base::Bind(base::IgnoreResult(&base::Process::Terminate
),
261 base::Unretained(&process
), 1, true));
263 monitor_thread().Start(process
.Duplicate(), window_name());
265 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
266 base::TimeDelta::FromMilliseconds(150)));
268 terminate_process_runner
.Reset();
270 ASSERT_EQ(WindowHangMonitor::WINDOW_NOT_FOUND
,
271 monitor_thread().WaitForEvent());
273 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
274 base::TimeDelta::FromMilliseconds(150)));
277 TEST_F(WindowHangMonitorTest
, WindowBeforeWatcher
) {
278 CreateMessageWindow();
280 monitor_thread().Start(base::Process::Current(), window_name());
284 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
285 base::TimeDelta::FromMilliseconds(150)));
288 TEST_F(WindowHangMonitorTest
, WindowBeforeDestroy
) {
289 CreateMessageWindow();
291 monitor_thread().Start(base::Process::Current(), window_name());
295 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
296 base::TimeDelta::FromMilliseconds(150)));
298 monitor_thread().DestroyWatcher();
300 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta()));
303 TEST_F(WindowHangMonitorTest
, NoWindowBeforeDestroy
) {
304 monitor_thread().Start(base::Process::Current(), window_name());
306 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
307 base::TimeDelta::FromMilliseconds(150)));
308 monitor_thread().DestroyWatcher();
310 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta()));
313 TEST_F(WindowHangMonitorTest
, WatcherBeforeWindow
) {
314 monitor_thread().Start(base::Process::Current(), window_name());
316 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
317 base::TimeDelta::FromMilliseconds(150)));
319 CreateMessageWindow();
323 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
324 base::TimeDelta::FromMilliseconds(150)));
327 TEST_F(WindowHangMonitorTest
, DetectsWindowDisappearance
) {
328 CreateMessageWindow();
330 monitor_thread().Start(base::Process::Current(), window_name());
334 DeleteMessageWindow();
336 ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED
,
337 monitor_thread().WaitForEvent());
339 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
340 base::TimeDelta::FromMilliseconds(150)));
343 TEST_F(WindowHangMonitorTest
, DetectsWindowNameChange
) {
344 // This test changes the title of the message window as a proxy for what
345 // happens if the window handle is reused for a different purpose. The latter
346 // is impossible to test in a deterministic fashion.
347 CreateMessageWindow();
349 monitor_thread().Start(base::Process::Current(), window_name());
351 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
352 base::TimeDelta::FromMilliseconds(150)));
354 ASSERT_TRUE(::SetWindowText(message_window()->hwnd(), L
"Gonsky"));
356 ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED
,
357 monitor_thread().WaitForEvent());
360 TEST_F(WindowHangMonitorTest
, DetectsWindowHang
) {
361 CreateMessageWindow();
363 monitor_thread().Start(base::Process::Current(), window_name());
365 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
366 base::TimeDelta::FromMilliseconds(150)));
368 // Block the worker thread.
369 base::WaitableEvent
hang(true, false);
371 window_thread()->task_runner()->PostTask(
373 base::Bind(&base::WaitableEvent::Wait
, base::Unretained(&hang
)));
375 EXPECT_EQ(WindowHangMonitor::WINDOW_HUNG
,
376 monitor_thread().WaitForEvent());
378 // Unblock the worker thread.
381 ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
382 base::TimeDelta::FromMilliseconds(150)));
385 } // namespace browser_watcher