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

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