// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/browser_watcher/window_hang_monitor_win.h" #include #include "base/base_switches.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/location.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/process/launch.h" #include "base/process/process.h" #include "base/process/process_handle.h" #include "base/run_loop.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/test/multiprocess_test.h" #include "base/threading/thread.h" #include "base/win/message_window.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" namespace browser_watcher { namespace { // Simulates a process that never opens a window. MULTIPROCESS_TEST_MAIN(NoWindowChild) { ::Sleep(INFINITE); return 0; } // Manages a WindowHangMonitor that lives on a background thread. class HangMonitorThread { public: // Instantiates the background thread. HangMonitorThread() : event_received_(false, false), event_(WindowHangMonitor::WINDOW_NOT_FOUND), thread_("HangMonitorThread") {} ~HangMonitorThread() { if (hang_monitor_.get()) DestroyWatcher(); } // Starts the background thread and the monitor to observe the window named // |window_name| in |process|. Blocks until the monitor has been initialized. bool Start(base::Process process, const base::string16& window_name) { if (!thread_.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_UI, 0))) { return false; } base::WaitableEvent complete(false, false); if (!thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&HangMonitorThread::StartupOnThread, base::Unretained(this), window_name, base::Passed(process.Pass()), base::Unretained(&complete)))) { return false; } complete.Wait(); return true; } // Returns true if a window event is detected within |timeout|. bool TimedWaitForEvent(base::TimeDelta timeout) { return event_received_.TimedWait(timeout); } // Blocks indefinitely for a window event and returns it. WindowHangMonitor::WindowEvent WaitForEvent() { event_received_.Wait(); return event_; } // Destroys the monitor and stops the background thread. Blocks until the // operation completes. void DestroyWatcher() { thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&HangMonitorThread::ShutdownOnThread, base::Unretained(this))); // This will block until the above-posted task completes. thread_.Stop(); } private: // Invoked when the monitor signals an event. Unblocks a call to // TimedWaitForEvent or WaitForEvent. void EventCallback(WindowHangMonitor::WindowEvent event) { if (event_received_.IsSignaled()) ADD_FAILURE() << "Multiple calls to EventCallback."; event_ = event; event_received_.Signal(); } // Initializes the WindowHangMonitor to observe the window named |window_name| // in |process|. Signals |complete| when done. void StartupOnThread(const base::string16& window_name, base::Process process, base::WaitableEvent* complete) { hang_monitor_.reset(new WindowHangMonitor( base::TimeDelta::FromMilliseconds(100), base::TimeDelta::FromMilliseconds(100), base::Bind(&HangMonitorThread::EventCallback, base::Unretained(this)))); hang_monitor_->Initialize(process.Pass(), window_name); complete->Signal(); } // Destroys the WindowHangMonitor. void ShutdownOnThread() { hang_monitor_.reset(); } // The detected event. Invalid if |event_received_| has not been signaled. WindowHangMonitor::WindowEvent event_; // Indicates that |event_| has been assigned in response to a callback from // the WindowHangMonitor. base::WaitableEvent event_received_; // The WindowHangMonitor under test. scoped_ptr hang_monitor_; // The background thread. base::Thread thread_; DISALLOW_COPY_AND_ASSIGN(HangMonitorThread); }; class WindowHangMonitorTest : public testing::Test { public: WindowHangMonitorTest() : ping_event_(false, false), pings_(0), window_thread_("WindowHangMonitorTest window_thread") {} void SetUp() override { // Pick a window name unique to this process. window_name_ = base::StringPrintf(L"WindowHanMonitorTest-%d", base::GetCurrentProcId()); ASSERT_TRUE(window_thread_.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_UI, 0))); } void TearDown() override { DeleteMessageWindow(); window_thread_.Stop(); } void CreateMessageWindow() { bool succeeded = false; base::WaitableEvent created(true, false); ASSERT_TRUE(window_thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&WindowHangMonitorTest::CreateMessageWindowInWorkerThread, base::Unretained(this), window_name_, &succeeded, &created))); created.Wait(); ASSERT_TRUE(succeeded); } void DeleteMessageWindow() { base::WaitableEvent deleted(true, false); window_thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&WindowHangMonitorTest::DeleteMessageWindowInWorkerThread, base::Unretained(this), &deleted)); deleted.Wait(); } void WaitForPing() { while (true) { { base::AutoLock auto_lock(ping_lock_); if (pings_) { ping_event_.Reset(); --pings_; return; } } ping_event_.Wait(); } } HangMonitorThread& monitor_thread() { return monitor_thread_; } const base::win::MessageWindow* message_window() const { return message_window_.get(); } const base::string16& window_name() const { return window_name_; } base::Thread* window_thread() { return &window_thread_; } private: bool MessageCallback(UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { EXPECT_EQ(window_thread_.message_loop(), base::MessageLoop::current()); if (message == WM_NULL) { base::AutoLock auto_lock(ping_lock_); ++pings_; ping_event_.Signal(); } return false; // Pass through to DefWindowProc. } void CreateMessageWindowInWorkerThread(const base::string16& name, bool* success, base::WaitableEvent* created) { message_window_.reset(new base::win::MessageWindow); *success = message_window_->CreateNamed( base::Bind(&WindowHangMonitorTest::MessageCallback, base::Unretained(this)), name); created->Signal(); } void DeleteMessageWindowInWorkerThread(base::WaitableEvent* deleted) { message_window_.reset(); if (deleted) deleted->Signal(); } HangMonitorThread monitor_thread_; scoped_ptr message_window_; base::string16 window_name_; base::Lock ping_lock_; base::WaitableEvent ping_event_; size_t pings_; base::Thread window_thread_; DISALLOW_COPY_AND_ASSIGN(WindowHangMonitorTest); }; } // namespace TEST_F(WindowHangMonitorTest, NoWindow) { base::CommandLine child_command_line = base::GetMultiProcessTestChildBaseCommandLine(); child_command_line.AppendSwitchASCII(switches::kTestChildProcess, "NoWindowChild"); base::Process process = base::LaunchProcess(child_command_line, base::LaunchOptions()); ASSERT_TRUE(process.IsValid()); base::ScopedClosureRunner terminate_process_runner( base::Bind(base::IgnoreResult(&base::Process::Terminate), base::Unretained(&process), 1, true)); monitor_thread().Start(process.Duplicate(), window_name()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); terminate_process_runner.Reset(); ASSERT_EQ(WindowHangMonitor::WINDOW_NOT_FOUND, monitor_thread().WaitForEvent()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); } TEST_F(WindowHangMonitorTest, WindowBeforeWatcher) { CreateMessageWindow(); monitor_thread().Start(base::Process::Current(), window_name()); WaitForPing(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); } TEST_F(WindowHangMonitorTest, WindowBeforeDestroy) { CreateMessageWindow(); monitor_thread().Start(base::Process::Current(), window_name()); WaitForPing(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); monitor_thread().DestroyWatcher(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta())); } TEST_F(WindowHangMonitorTest, NoWindowBeforeDestroy) { monitor_thread().Start(base::Process::Current(), window_name()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); monitor_thread().DestroyWatcher(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent(base::TimeDelta())); } TEST_F(WindowHangMonitorTest, WatcherBeforeWindow) { monitor_thread().Start(base::Process::Current(), window_name()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); CreateMessageWindow(); WaitForPing(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); } TEST_F(WindowHangMonitorTest, DetectsWindowDisappearance) { CreateMessageWindow(); monitor_thread().Start(base::Process::Current(), window_name()); WaitForPing(); DeleteMessageWindow(); ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED, monitor_thread().WaitForEvent()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); } TEST_F(WindowHangMonitorTest, DetectsWindowNameChange) { // This test changes the title of the message window as a proxy for what // happens if the window handle is reused for a different purpose. The latter // is impossible to test in a deterministic fashion. CreateMessageWindow(); monitor_thread().Start(base::Process::Current(), window_name()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); ASSERT_TRUE(::SetWindowText(message_window()->hwnd(), L"Gonsky")); ASSERT_EQ(WindowHangMonitor::WINDOW_VANISHED, monitor_thread().WaitForEvent()); } TEST_F(WindowHangMonitorTest, DetectsWindowHang) { CreateMessageWindow(); monitor_thread().Start(base::Process::Current(), window_name()); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); // Block the worker thread. base::WaitableEvent hang(true, false); window_thread()->message_loop_proxy()->PostTask( FROM_HERE, base::Bind(&base::WaitableEvent::Wait, base::Unretained(&hang))); EXPECT_EQ(WindowHangMonitor::WINDOW_HUNG, monitor_thread().WaitForEvent()); // Unblock the worker thread. hang.Signal(); ASSERT_FALSE(monitor_thread().TimedWaitForEvent( base::TimeDelta::FromMilliseconds(150))); } } // namespace browser_watcher