Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(61)

Unified Diff: chrome/browser/process_singleton_win_unittest.cc

Issue 981223004: Add test for ProcessSingleton hung rendezvous case. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address Scott's remaining comment. Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/process_singleton_win.cc ('k') | chrome/chrome_tests_unit.gypi » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/process_singleton_win_unittest.cc
diff --git a/chrome/browser/process_singleton_win_unittest.cc b/chrome/browser/process_singleton_win_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..51e8186eba2ab52de05d07db817c1d4b43579c83
--- /dev/null
+++ b/chrome/browser/process_singleton_win_unittest.cc
@@ -0,0 +1,318 @@
+// 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 "chrome/browser/process_singleton.h"
+
+#include <windows.h>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/multiprocess_test.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/wrapped_window_proc.h"
+#include "chrome/browser/chrome_process_finder_win.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/common/result_codes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace {
+
+const char kReadyEventNameFlag[] = "ready_event_name";
+const char kContinueEventNameFlag[] = "continue_event_name";
+const char kCreateWindowFlag[] = "create_window";
+const int kErrorResultCode = 0x345;
+
+bool NotificationCallback(const base::CommandLine& command_line,
+ const base::FilePath& current_directory) {
+ // This is never called in this test, but would signal that the singleton
+ // notification was successfully handled.
+ NOTREACHED();
+ return true;
+}
+
+// The ProcessSingleton kills hung browsers with no visible windows without user
+// interaction. If a hung browser has visible UI, however, it asks the user
+// first.
+// This class is the very minimal implementation to create a visible window
+// in the hung test process to allow testing the latter path.
+class ScopedVisibleWindow {
+ public:
+ ScopedVisibleWindow() : class_(0), window_(NULL) {}
+ ~ScopedVisibleWindow() {
+ if (window_)
+ ::DestroyWindow(window_);
+ if (class_)
+ ::UnregisterClass(reinterpret_cast<LPCWSTR>(class_), NULL);
+ }
+
+ bool Create() {
+ WNDCLASSEX wnd_cls = {0};
+ base::win::InitializeWindowClass(
+ L"ProcessSingletonTest", base::win::WrappedWindowProc<::DefWindowProc>,
+ 0, // style
+ 0, // class_extra
+ 0, // window_extra
+ NULL, // cursor
+ NULL, // background
+ L"", // menu_name
+ NULL, // large_icon
+ NULL, // small_icon
+ &wnd_cls);
+
+ class_ = ::RegisterClassEx(&wnd_cls);
+ if (!class_)
+ return false;
+ window_ = ::CreateWindow(reinterpret_cast<LPCWSTR>(class_), 0, WS_POPUP, 0,
+ 0, 0, 0, 0, 0, NULL, 0);
+ if (!window_)
+ return false;
+ ::ShowWindow(window_, SW_SHOW);
+
+ DCHECK(window_);
+ return true;
+ }
+
+ private:
+ ATOM class_;
+ HWND window_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedVisibleWindow);
+};
+
+MULTIPROCESS_TEST_MAIN(ProcessSingletonTestProcessMain) {
+ base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+ base::FilePath user_data_dir =
+ cmd_line->GetSwitchValuePath(switches::kUserDataDir);
+ if (user_data_dir.empty())
+ return kErrorResultCode;
+
+ base::string16 ready_event_name =
+ cmd_line->GetSwitchValueNative(kReadyEventNameFlag);
+
+ base::win::ScopedHandle ready_event(
+ ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str()));
+ if (!ready_event.IsValid())
+ return kErrorResultCode;
+
+ base::string16 continue_event_name =
+ cmd_line->GetSwitchValueNative(kContinueEventNameFlag);
+
+ base::win::ScopedHandle continue_event(
+ ::OpenEvent(SYNCHRONIZE, FALSE, continue_event_name.c_str()));
+ if (!continue_event.IsValid())
+ return kErrorResultCode;
+
+ ScopedVisibleWindow visible_window;
+ if (cmd_line->HasSwitch(kCreateWindowFlag)) {
+ if (!visible_window.Create())
+ return kErrorResultCode;
+ }
+
+ // Instantiate the process singleton.
+ ProcessSingleton process_singleton(user_data_dir,
+ base::Bind(&NotificationCallback));
+
+ if (!process_singleton.Create())
+ return kErrorResultCode;
+
+ // Signal ready and block for the continue event.
+ if (!::SetEvent(ready_event.Get()))
+ return kErrorResultCode;
+
+ if (::WaitForSingleObject(continue_event.Get(), INFINITE) != WAIT_OBJECT_0)
+ return kErrorResultCode;
+
+ return 0;
+}
+
+// This fixture is for testing the Windows platform-specific failure modes
+// of rendezvous, specifically the ones where the singleton-owning process
+// is hung.
+class ProcessSingletonTest : public base::MultiProcessTest {
+ protected:
+ enum WindowOption { WITH_WINDOW, NO_WINDOW };
+
+ ProcessSingletonTest()
+ : window_option_(NO_WINDOW), should_kill_called_(false) {}
+
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(base::MultiProcessTest::SetUp());
+
+ // Drop the process finder notification timeout to one second for testing.
+ old_notification_timeout_ = chrome::SetNotificationTimeoutForTesting(
+ base::TimeDelta::FromSeconds(1));
+ }
+
+ void TearDown() override {
+ chrome::SetNotificationTimeoutForTesting(old_notification_timeout_);
+
+ if (browser_victim_.IsValid()) {
+ EXPECT_TRUE(::SetEvent(continue_event_.Get()));
+ int exit_code = 0;
+ EXPECT_TRUE(browser_victim_.WaitForExit(&exit_code));
+ }
+
+ base::MultiProcessTest::TearDown();
+ }
+
+ void LaunchHungBrowserProcess(WindowOption window_option) {
+ // Create a unique user data dir to rendezvous on.
+ ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
+
+ // Create the named "ready" event, this is unique to our process.
+ ready_event_name_ =
+ base::StringPrintf(L"ready-event-%d", base::GetCurrentProcId());
+ base::win::ScopedHandle ready_event(
+ ::CreateEvent(NULL, TRUE, FALSE, ready_event_name_.c_str()));
+ ASSERT_TRUE(ready_event.IsValid());
+
+ // Create the named "continue" event, this is unique to our process.
+ continue_event_name_ =
+ base::StringPrintf(L"continue-event-%d", base::GetCurrentProcId());
+ continue_event_.Set(
+ ::CreateEvent(NULL, TRUE, FALSE, continue_event_name_.c_str()));
+ ASSERT_TRUE(continue_event_.IsValid());
+
+ window_option_ = window_option;
+
+ base::LaunchOptions options;
+ options.start_hidden = true;
+ browser_victim_ =
+ SpawnChildWithOptions("ProcessSingletonTestProcessMain", options);
+
+ // Wait for the ready event (or process exit).
+ HANDLE handles[] = {ready_event.Get(), browser_victim_.Handle()};
+ // The wait should always return because either |ready_event| is signaled or
+ // |browser_victim_| died unexpectedly or exited on error.
+ DWORD result =
+ ::WaitForMultipleObjects(arraysize(handles), handles, FALSE, INFINITE);
+ ASSERT_EQ(WAIT_OBJECT_0, result);
+ }
+
+ base::CommandLine MakeCmdLine(const std::string& procname) override {
+ base::CommandLine cmd_line = base::MultiProcessTest::MakeCmdLine(procname);
+
+ cmd_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_.path());
+ cmd_line.AppendSwitchNative(kReadyEventNameFlag, ready_event_name_);
+ cmd_line.AppendSwitchNative(kContinueEventNameFlag, continue_event_name_);
+ if (window_option_ == WITH_WINDOW)
+ cmd_line.AppendSwitch(kCreateWindowFlag);
+
+ return cmd_line;
+ }
+
+ void PrepareTest(WindowOption window_option, bool allow_kill) {
+ ASSERT_NO_FATAL_FAILURE(LaunchHungBrowserProcess(window_option));
+
+ // The ready event has been signalled - the process singleton is held by
+ // the hung sub process.
+ test_singleton_.reset(new ProcessSingleton(
+ user_data_dir(), base::Bind(&NotificationCallback)));
+
+ test_singleton_->OverrideShouldKillRemoteProcessCallbackForTesting(
+ base::Bind(&ProcessSingletonTest::MockShouldKillRemoteProcess,
+ base::Unretained(this), allow_kill));
+ }
+
+ base::Process* browser_victim() { return &browser_victim_; }
+ const base::FilePath& user_data_dir() const { return user_data_dir_.path(); }
+ ProcessSingleton* test_singleton() const { return test_singleton_.get(); }
+ bool should_kill_called() const { return should_kill_called_; }
+
+ private:
+ bool MockShouldKillRemoteProcess(bool allow_kill) {
+ should_kill_called_ = true;
+ return allow_kill;
+ }
+
+ base::string16 ready_event_name_;
+ base::string16 continue_event_name_;
+
+ WindowOption window_option_;
+ base::ScopedTempDir user_data_dir_;
+ base::Process browser_victim_;
+ base::win::ScopedHandle continue_event_;
+
+ scoped_ptr<ProcessSingleton> test_singleton_;
+
+ base::TimeDelta old_notification_timeout_;
+ bool should_kill_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessSingletonTest);
+};
+
+} // namespace
+
+TEST_F(ProcessSingletonTest, KillsHungBrowserWithNoWindows) {
+ ASSERT_NO_FATAL_FAILURE(PrepareTest(NO_WINDOW, false));
+
+ // As the hung browser has no visible window, it'll be killed without
+ // user interaction.
+ ProcessSingleton::NotifyResult notify_result =
+ test_singleton()->NotifyOtherProcessOrCreate();
+ ASSERT_EQ(ProcessSingleton::PROFILE_IN_USE, notify_result);
+
+ // The should-kill callback should not have been called, as the "browser" does
+ // not have visible window.
+ EXPECT_FALSE(should_kill_called());
+
+ // Verify that the hung browser has beem terminated with the
+ // RESULT_CODE_HUNG exit code.
+ int exit_code = 0;
+ EXPECT_TRUE(
+ browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
+ EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
+}
+
+TEST_F(ProcessSingletonTest, DoesntKillWithoutUserPermission) {
+ ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, false));
+
+ // As the hung browser has a visible window, this should query the user
+ // before killing the hung process.
+ ProcessSingleton::NotifyResult notify_result =
+ test_singleton()->NotifyOtherProcessOrCreate();
+ ASSERT_EQ(ProcessSingleton::PROCESS_NOTIFIED, notify_result);
+
+ // The should-kill callback should have been called, as the "browser" has a
+ // visible window.
+ EXPECT_TRUE(should_kill_called());
+
+ // Make sure the process hasn't been killed.
+ int exit_code = 0;
+ EXPECT_FALSE(
+ browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
+}
+
+TEST_F(ProcessSingletonTest, KillWithUserPermission) {
+ ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, true));
+
+ // As the hung browser has a visible window, this should query the user
+ // before killing the hung process.
+ ProcessSingleton::NotifyResult notify_result =
+ test_singleton()->NotifyOtherProcessOrCreate();
+ ASSERT_EQ(ProcessSingleton::PROFILE_IN_USE, notify_result);
+
+ // The should-kill callback should have been called, as the "browser" has a
+ // visible window.
+ EXPECT_TRUE(should_kill_called());
+
+ // Verify that the hung browser has beem terminated with the
+ // RESULT_CODE_HUNG exit code.
+ int exit_code = 0;
+ EXPECT_TRUE(
+ browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
+ EXPECT_EQ(content::RESULT_CODE_HUNG, exit_code);
+}
« no previous file with comments | « chrome/browser/process_singleton_win.cc ('k') | chrome/chrome_tests_unit.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698