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

Side by Side 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 Gab's remaining comments. 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 unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/process_singleton.h"
6
7 #include <windows.h>
8
9 #include "base/bind.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"
30
31 namespace {
32
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;
37
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.
42 NOTREACHED();
43 return true;
44 }
45
46 HWND CreateVisibleWindow() {
47 WNDCLASSEX wnd_cls = {};
48 base::win::InitializeWindowClass(
49 L"ProcessSingletonTest", base::win::WrappedWindowProc<::DefWindowProc>,
50 0, // style
51 0, // class_extra
52 0, // window_extra
53 NULL, // cursor
54 NULL, // background
55 L"", // menu_name
56 NULL, // large_icon
57 NULL, // small_icon
58 &wnd_cls);
59
60 ATOM clazz = ::RegisterClassEx(&wnd_cls);
61
62 // Create a visible popup window to trigger user interaction in the
63 // process singleton.
64 HWND window = ::CreateWindow(reinterpret_cast<LPCWSTR>(clazz), 0, WS_POPUP, 0,
sky 2015/03/11 19:15:51 Where is the window destroyed? Alos, why do you ne
Sigurður Ásgeirsson 2015/03/11 20:15:31 This is testing specific behavior of the ProcessSi
sky 2015/03/11 20:19:27 How about a good comment then? The one on 62 doesn
65 0, 0, 0, 0, 0, NULL, 0);
66 if (window)
67 ::ShowWindow(window, SW_SHOW);
68
69 return window;
70 }
71
72 MULTIPROCESS_TEST_MAIN(ProcessSingletonTestProcessMain) {
73 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
74 base::FilePath user_data_dir =
75 cmd_line->GetSwitchValuePath(switches::kUserDataDir);
76 if (user_data_dir.empty())
77 return kErrorResultCode;
78
79 base::string16 ready_event_name =
80 cmd_line->GetSwitchValueNative(kReadyEventNameFlag);
81
82 base::win::ScopedHandle ready_event(
83 ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str()));
84 if (!ready_event.IsValid())
85 return kErrorResultCode;
86
87 base::string16 continue_event_name =
88 cmd_line->GetSwitchValueNative(kContinueEventNameFlag);
89
90 base::win::ScopedHandle continue_event(
91 ::OpenEvent(SYNCHRONIZE, FALSE, continue_event_name.c_str()));
92 if (!continue_event.IsValid())
93 return kErrorResultCode;
94
95 if (cmd_line->HasSwitch(kCreateWindowFlag)) {
96 HWND window = CreateVisibleWindow();
97 if (!window)
98 return kErrorResultCode;
99 }
100
101 // Instantiate the process singleton.
102 ProcessSingleton process_singleton(user_data_dir,
103 base::Bind(&NotificationCallback));
104
105 if (!process_singleton.Create())
106 return kErrorResultCode;
107
108 // Signal ready and block for the continue event.
109 if (!::SetEvent(ready_event.Get()))
110 return kErrorResultCode;
111
112 if (::WaitForSingleObject(continue_event.Get(), INFINITE) != WAIT_OBJECT_0)
113 return kErrorResultCode;
114
115 return 0;
116 }
117
118 // This fixture is for testing the Windows platform-specific failure modes
119 // of rendezvous, specifically the ones where the singleton-owning process
120 // is hung.
121 class ProcessSingletonTest : public base::MultiProcessTest {
122 protected:
123 enum WindowOption { WITH_WINDOW, NO_WINDOW };
124
125 ProcessSingletonTest()
126 : window_option_(NO_WINDOW), should_kill_called_(false) {}
127
128 void SetUp() override {
129 ASSERT_NO_FATAL_FAILURE(base::MultiProcessTest::SetUp());
130
131 // Drop the process finder notification timeout to one second for testing.
132 old_notification_timeout_ =
133 chrome::SetNotificationTimeoutInSecondsForTesting(1);
134 }
135
136 void TearDown() override {
137 chrome::SetNotificationTimeoutInSecondsForTesting(
138 old_notification_timeout_);
139
140 if (browser_victim_.IsValid()) {
141 EXPECT_TRUE(::SetEvent(continue_event_.Get()));
142 int exit_code = 0;
143 EXPECT_TRUE(browser_victim_.WaitForExit(&exit_code));
144 }
145
146 base::MultiProcessTest::TearDown();
147 }
148
149 void LaunchHungBrowserProcess(WindowOption window_option) {
150 // Create a unique user data dir to rendezvous on.
151 ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
152
153 // Create the named "ready" event, this is unique to our process.
154 ready_event_name_ =
155 base::StringPrintf(L"ready-event-%d", base::GetCurrentProcId());
156 base::win::ScopedHandle ready_event(
157 ::CreateEvent(NULL, TRUE, FALSE, ready_event_name_.c_str()));
158 ASSERT_TRUE(ready_event.IsValid());
159
160 // Create the named "continue" event, this is unique to our process.
161 continue_event_name_ =
162 base::StringPrintf(L"continue-event-%d", base::GetCurrentProcId());
163 continue_event_.Set(
164 ::CreateEvent(NULL, TRUE, FALSE, continue_event_name_.c_str()));
165 ASSERT_TRUE(continue_event_.IsValid());
166
167 window_option_ = window_option;
168
169 base::LaunchOptions options;
170 options.start_hidden = true;
171 browser_victim_ =
172 SpawnChildWithOptions("ProcessSingletonTestProcessMain", options);
173
174 // Wait for the ready event (or process exit).
175 HANDLE handles[] = {ready_event.Get(), browser_victim_.Handle()};
176 // The wait should always return because either |ready_event| is signaled or
177 // |browser_victim_| died unexpectedly or exited on error.
178 DWORD result =
179 ::WaitForMultipleObjects(arraysize(handles), handles, FALSE, INFINITE);
180 ASSERT_EQ(result, WAIT_OBJECT_0);
sky 2015/03/11 19:15:51 Isn't WAIT_OBJECT_0 the expected value? In general
Sigurður Ásgeirsson 2015/03/11 20:15:31 Yes, I've been using the macros consistently this
181 }
182
183 base::CommandLine MakeCmdLine(const std::string& procname) override {
184 base::CommandLine cmd_line = base::MultiProcessTest::MakeCmdLine(procname);
185
186 cmd_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_.path());
187 cmd_line.AppendSwitchNative(kReadyEventNameFlag, ready_event_name_);
188 cmd_line.AppendSwitchNative(kContinueEventNameFlag, continue_event_name_);
189 if (window_option_ == WITH_WINDOW)
190 cmd_line.AppendSwitch(kCreateWindowFlag);
191
192 return cmd_line;
193 }
194
195 void PrepareTest(WindowOption window_option, bool allow_kill) {
196 ASSERT_NO_FATAL_FAILURE(LaunchHungBrowserProcess(window_option));
197
198 // The ready event has been signalled - the process singleton is held by
199 // the hung sub process.
200 test_singleton_.reset(new ProcessSingleton(
201 user_data_dir(), base::Bind(&NotificationCallback)));
202
203 test_singleton_->OverrideShouldKillRemoteProcessCallbackForTesting(
204 base::Bind(&ProcessSingletonTest::MockShouldKillRemoteProcess,
205 base::Unretained(this), allow_kill));
206 }
207
208 base::Process* browser_victim() { return &browser_victim_; }
209 const base::FilePath& user_data_dir() const { return user_data_dir_.path(); }
210 ProcessSingleton* test_singleton() const { return test_singleton_.get(); }
211 bool should_kill_called() const { return should_kill_called_; }
212
213 private:
214 bool MockShouldKillRemoteProcess(bool allow_kill) {
215 should_kill_called_ = true;
216 return allow_kill;
217 }
218
219 base::string16 ready_event_name_;
220 base::string16 continue_event_name_;
221
222 WindowOption window_option_;
223 base::ScopedTempDir user_data_dir_;
224 base::Process browser_victim_;
225 base::win::ScopedHandle continue_event_;
226
227 scoped_ptr<ProcessSingleton> test_singleton_;
228
229 int old_notification_timeout_;
230 bool should_kill_called_;
231
232 DISALLOW_COPY_AND_ASSIGN(ProcessSingletonTest);
233 };
234
235 } // namespace
236
237 TEST_F(ProcessSingletonTest, KillsHungBrowserWithNoWindows) {
238 ASSERT_NO_FATAL_FAILURE(PrepareTest(NO_WINDOW, false));
239
240 // As the hung browser has no visible window, it'll be killed without
241 // user interaction.
242 ProcessSingleton::NotifyResult notify_result =
243 test_singleton()->NotifyOtherProcessOrCreate();
244 ASSERT_EQ(notify_result, ProcessSingleton::PROFILE_IN_USE);
245
246 // The should-kill callback should not have been called, as the "browser" does
247 // not have visible window.
248 EXPECT_FALSE(should_kill_called());
249
250 // Verify that the hung browser has beem terminated with the
251 // RESULT_CODE_HUNG exit code.
252 int exit_code = 0;
253 EXPECT_TRUE(
254 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
255 EXPECT_EQ(exit_code, content::RESULT_CODE_HUNG);
256 }
257
258 TEST_F(ProcessSingletonTest, DoesntKillWithoutUserPermission) {
259 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, false));
260
261 // As the hung browser has a visible window, this should query the user
262 // before killing the hung process.
263 ProcessSingleton::NotifyResult notify_result =
264 test_singleton()->NotifyOtherProcessOrCreate();
265 ASSERT_EQ(notify_result, ProcessSingleton::PROCESS_NOTIFIED);
266
267 // The should-kill callback should have been called, as the "browser" has a
268 // visible window.
269 EXPECT_TRUE(should_kill_called());
270
271 // Make sure the process hasn't been killed.
272 int exit_code = 0;
273 EXPECT_FALSE(
274 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
275 }
276
277 TEST_F(ProcessSingletonTest, KillWithUserPermission) {
278 ASSERT_NO_FATAL_FAILURE(PrepareTest(WITH_WINDOW, true));
279
280 // As the hung browser has a visible window, this should query the user
281 // before killing the hung process.
282 ProcessSingleton::NotifyResult notify_result =
283 test_singleton()->NotifyOtherProcessOrCreate();
284 ASSERT_EQ(notify_result, ProcessSingleton::PROFILE_IN_USE);
285
286 // The should-kill callback should have been called, as the "browser" has a
287 // visible window.
288 EXPECT_TRUE(should_kill_called());
289
290 // Verify that the hung browser has beem terminated with the
291 // RESULT_CODE_HUNG exit code.
292 int exit_code = 0;
293 EXPECT_TRUE(
294 browser_victim()->WaitForExitWithTimeout(base::TimeDelta(), &exit_code));
295 EXPECT_EQ(exit_code, content::RESULT_CODE_HUNG);
296 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698