1 // Copyright 2014 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 "chrome/browser/process_singleton.h"
17 #include "base/bind.h"
18 #include "base/command_line.h"
19 #include "base/file_util.h"
20 #include "base/files/file_path.h"
21 #include "base/files/scoped_temp_dir.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/posix/eintr_wrapper.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/synchronization/waitable_event.h"
26 #include "base/test/test_timeouts.h"
27 #include "base/test/thread_test_helper.h"
28 #include "base/threading/thread.h"
29 #include "chrome/common/chrome_constants.h"
30 #include "content/public/test/test_browser_thread.h"
31 #include "net/base/net_util.h"
32 #include "testing/gtest/include/gtest/gtest.h"
34 using content::BrowserThread
;
38 class ProcessSingletonPosixTest
: public testing::Test
{
40 // A ProcessSingleton exposing some protected methods for testing.
41 class TestableProcessSingleton
: public ProcessSingleton
{
43 explicit TestableProcessSingleton(const base::FilePath
& user_data_dir
)
46 base::Bind(&TestableProcessSingleton::NotificationCallback
,
47 base::Unretained(this))) {}
50 std::vector
<CommandLine::StringVector
> callback_command_lines_
;
52 using ProcessSingleton::NotifyOtherProcessWithTimeout
;
53 using ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate
;
54 using ProcessSingleton::OverrideCurrentPidForTesting
;
55 using ProcessSingleton::OverrideKillCallbackForTesting
;
58 bool NotificationCallback(const CommandLine
& command_line
,
59 const base::FilePath
& current_directory
) {
60 callback_command_lines_
.push_back(command_line
.argv());
65 ProcessSingletonPosixTest()
67 io_thread_(BrowserThread::IO
),
68 wait_event_(true, false),
69 signal_event_(true, false),
70 process_singleton_on_thread_(NULL
) {
71 io_thread_
.StartIOThread();
74 virtual void SetUp() {
75 testing::Test::SetUp();
77 ProcessSingleton::DisablePromptForTesting();
78 // Put the lock in a temporary directory. Doesn't need to be a
79 // full profile to test this code.
80 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
81 // Use a long directory name to ensure that the socket isn't opened through
83 user_data_path_
= temp_dir_
.path().Append(
84 std::string(sizeof(sockaddr_un::sun_path
), 'a'));
85 ASSERT_TRUE(CreateDirectory(user_data_path_
));
87 lock_path_
= user_data_path_
.Append(chrome::kSingletonLockFilename
);
88 socket_path_
= user_data_path_
.Append(chrome::kSingletonSocketFilename
);
89 cookie_path_
= user_data_path_
.Append(chrome::kSingletonCookieFilename
);
92 virtual void TearDown() {
93 scoped_refptr
<base::ThreadTestHelper
> io_helper(new base::ThreadTestHelper(
94 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO
).get()));
95 ASSERT_TRUE(io_helper
->Run());
97 // Destruct the ProcessSingleton object before the IO thread so that its
98 // internals are destructed properly.
99 if (process_singleton_on_thread_
) {
100 worker_thread_
->message_loop()->PostTask(
102 base::Bind(&ProcessSingletonPosixTest::DestructProcessSingleton
,
103 base::Unretained(this)));
105 scoped_refptr
<base::ThreadTestHelper
> helper(new base::ThreadTestHelper(
106 worker_thread_
->message_loop_proxy().get()));
107 ASSERT_TRUE(helper
->Run());
111 testing::Test::TearDown();
114 void CreateProcessSingletonOnThread() {
115 ASSERT_EQ(NULL
, worker_thread_
.get());
116 worker_thread_
.reset(new base::Thread("BlockingThread"));
117 worker_thread_
->Start();
119 worker_thread_
->message_loop()->PostTask(
121 base::Bind(&ProcessSingletonPosixTest::
122 CreateProcessSingletonInternal
,
123 base::Unretained(this)));
125 scoped_refptr
<base::ThreadTestHelper
> helper(
126 new base::ThreadTestHelper(worker_thread_
->message_loop_proxy().get()));
127 ASSERT_TRUE(helper
->Run());
130 TestableProcessSingleton
* CreateProcessSingleton() {
131 return new TestableProcessSingleton(user_data_path_
);
136 ASSERT_EQ(0, lstat(lock_path_
.value().c_str(), &statbuf
));
137 ASSERT_TRUE(S_ISLNK(statbuf
.st_mode
));
139 ssize_t len
= readlink(lock_path_
.value().c_str(), buf
, PATH_MAX
);
142 ASSERT_EQ(0, lstat(socket_path_
.value().c_str(), &statbuf
));
143 ASSERT_TRUE(S_ISLNK(statbuf
.st_mode
));
145 len
= readlink(socket_path_
.value().c_str(), buf
, PATH_MAX
);
147 base::FilePath socket_target_path
= base::FilePath(std::string(buf
, len
));
149 ASSERT_EQ(0, lstat(socket_target_path
.value().c_str(), &statbuf
));
150 ASSERT_TRUE(S_ISSOCK(statbuf
.st_mode
));
152 len
= readlink(cookie_path_
.value().c_str(), buf
, PATH_MAX
);
154 std::string
cookie(buf
, len
);
156 base::FilePath remote_cookie_path
= socket_target_path
.DirName().
157 Append(chrome::kSingletonCookieFilename
);
158 len
= readlink(remote_cookie_path
.value().c_str(), buf
, PATH_MAX
);
160 EXPECT_EQ(cookie
, std::string(buf
, len
));
163 ProcessSingleton::NotifyResult
NotifyOtherProcess(
165 base::TimeDelta timeout
) {
166 scoped_ptr
<TestableProcessSingleton
> process_singleton(
167 CreateProcessSingleton());
168 CommandLine
command_line(CommandLine::ForCurrentProcess()->GetProgram());
169 command_line
.AppendArg("about:blank");
171 process_singleton
->OverrideCurrentPidForTesting(
172 base::GetCurrentProcId() + 1);
173 process_singleton
->OverrideKillCallbackForTesting(
174 base::Bind(&ProcessSingletonPosixTest::KillCallback
,
175 base::Unretained(this)));
178 return process_singleton
->NotifyOtherProcessWithTimeout(
179 command_line
, timeout
.InSeconds(), true);
182 // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
183 ProcessSingleton::NotifyResult
NotifyOtherProcessOrCreate(
184 const std::string
& url
,
185 base::TimeDelta timeout
) {
186 scoped_ptr
<TestableProcessSingleton
> process_singleton(
187 CreateProcessSingleton());
188 CommandLine
command_line(CommandLine::ForCurrentProcess()->GetProgram());
189 command_line
.AppendArg(url
);
190 return process_singleton
->NotifyOtherProcessWithTimeoutOrCreate(
191 command_line
, timeout
.InSeconds());
194 void CheckNotified() {
195 ASSERT_TRUE(process_singleton_on_thread_
!= NULL
);
196 ASSERT_EQ(1u, process_singleton_on_thread_
->callback_command_lines_
.size());
199 i
< process_singleton_on_thread_
->callback_command_lines_
[0].size();
201 if (process_singleton_on_thread_
->callback_command_lines_
[0][i
] ==
208 ASSERT_EQ(0, kill_callbacks_
);
211 void BlockWorkerThread() {
212 worker_thread_
->message_loop()->PostTask(
214 base::Bind(&ProcessSingletonPosixTest::BlockThread
,
215 base::Unretained(this)));
218 void UnblockWorkerThread() {
219 wait_event_
.Signal(); // Unblock the worker thread for shutdown.
220 signal_event_
.Wait(); // Ensure thread unblocks before continuing.
225 signal_event_
.Signal();
228 base::FilePath user_data_path_
;
229 base::FilePath lock_path_
;
230 base::FilePath socket_path_
;
231 base::FilePath cookie_path_
;
235 void CreateProcessSingletonInternal() {
236 ASSERT_TRUE(!process_singleton_on_thread_
);
237 process_singleton_on_thread_
= CreateProcessSingleton();
238 ASSERT_EQ(ProcessSingleton::PROCESS_NONE
,
239 process_singleton_on_thread_
->NotifyOtherProcessOrCreate());
242 void DestructProcessSingleton() {
243 ASSERT_TRUE(process_singleton_on_thread_
);
244 delete process_singleton_on_thread_
;
247 void KillCallback(int pid
) {
251 content::TestBrowserThread io_thread_
;
252 base::ScopedTempDir temp_dir_
;
253 base::WaitableEvent wait_event_
;
254 base::WaitableEvent signal_event_
;
256 scoped_ptr
<base::Thread
> worker_thread_
;
257 TestableProcessSingleton
* process_singleton_on_thread_
;
262 // Test if the socket file and symbol link created by ProcessSingletonPosix
264 // If this test flakes, use http://crbug.com/74554.
265 TEST_F(ProcessSingletonPosixTest
, CheckSocketFile
) {
266 CreateProcessSingletonOnThread();
270 // TODO(james.su@gmail.com): port following tests to Windows.
271 // Test success case of NotifyOtherProcess().
272 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessSuccess
) {
273 CreateProcessSingletonOnThread();
274 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED
,
275 NotifyOtherProcess(true, TestTimeouts::action_timeout()));
279 // Test failure case of NotifyOtherProcess().
280 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessFailure
) {
281 CreateProcessSingletonOnThread();
284 EXPECT_EQ(ProcessSingleton::PROCESS_NONE
,
285 NotifyOtherProcess(true, TestTimeouts::action_timeout()));
287 ASSERT_EQ(1, kill_callbacks_
);
288 UnblockWorkerThread();
291 // Test that we don't kill ourselves by accident if a lockfile with the same pid
293 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessNoSuicide
) {
294 CreateProcessSingletonOnThread();
295 // Replace lockfile with one containing our own pid.
296 EXPECT_EQ(0, unlink(lock_path_
.value().c_str()));
297 std::string symlink_content
= base::StringPrintf(
299 net::GetHostName().c_str(),
301 base::GetCurrentProcId());
302 EXPECT_EQ(0, symlink(symlink_content
.c_str(), lock_path_
.value().c_str()));
304 // Remove socket so that we will not be able to notify the existing browser.
305 EXPECT_EQ(0, unlink(socket_path_
.value().c_str()));
307 EXPECT_EQ(ProcessSingleton::PROCESS_NONE
,
308 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
309 // If we've gotten to this point without killing ourself, the test succeeded.
312 // Test that we can still notify a process on the same host even after the
314 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessHostChanged
) {
315 CreateProcessSingletonOnThread();
316 EXPECT_EQ(0, unlink(lock_path_
.value().c_str()));
317 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_
.value().c_str()));
319 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED
,
320 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
324 // Test that we fail when lock says process is on another host and we can't
325 // notify it over the socket.
326 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessDifferingHost
) {
327 CreateProcessSingletonOnThread();
331 EXPECT_EQ(0, unlink(lock_path_
.value().c_str()));
332 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_
.value().c_str()));
334 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE
,
335 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
337 ASSERT_EQ(0, unlink(lock_path_
.value().c_str()));
339 UnblockWorkerThread();
342 // Test that we fail when lock says process is on another host and we can't
343 // notify it over the socket.
344 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessOrCreate_DifferingHost
) {
345 CreateProcessSingletonOnThread();
349 EXPECT_EQ(0, unlink(lock_path_
.value().c_str()));
350 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_
.value().c_str()));
352 std::string
url("about:blank");
353 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE
,
354 NotifyOtherProcessOrCreate(url
, TestTimeouts::action_timeout()));
356 ASSERT_EQ(0, unlink(lock_path_
.value().c_str()));
358 UnblockWorkerThread();
361 // Test that Create fails when another browser is using the profile directory.
362 TEST_F(ProcessSingletonPosixTest
, CreateFailsWithExistingBrowser
) {
363 CreateProcessSingletonOnThread();
365 scoped_ptr
<TestableProcessSingleton
> process_singleton(
366 CreateProcessSingleton());
367 process_singleton
->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
368 EXPECT_FALSE(process_singleton
->Create());
371 // Test that Create fails when another browser is using the profile directory
372 // but with the old socket location.
373 TEST_F(ProcessSingletonPosixTest
, CreateChecksCompatibilitySocket
) {
374 CreateProcessSingletonOnThread();
375 scoped_ptr
<TestableProcessSingleton
> process_singleton(
376 CreateProcessSingleton());
377 process_singleton
->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
379 // Do some surgery so as to look like the old configuration.
381 ssize_t len
= readlink(socket_path_
.value().c_str(), buf
, sizeof(buf
));
383 base::FilePath socket_target_path
= base::FilePath(std::string(buf
, len
));
384 ASSERT_EQ(0, unlink(socket_path_
.value().c_str()));
385 ASSERT_EQ(0, rename(socket_target_path
.value().c_str(),
386 socket_path_
.value().c_str()));
387 ASSERT_EQ(0, unlink(cookie_path_
.value().c_str()));
389 EXPECT_FALSE(process_singleton
->Create());
392 // Test that we fail when lock says process is on another host and we can't
393 // notify it over the socket before of a bad cookie.
394 TEST_F(ProcessSingletonPosixTest
, NotifyOtherProcessOrCreate_BadCookie
) {
395 CreateProcessSingletonOnThread();
396 // Change the cookie.
397 EXPECT_EQ(0, unlink(cookie_path_
.value().c_str()));
398 EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_
.value().c_str()));
400 // Also change the hostname, so the remote does not retry.
401 EXPECT_EQ(0, unlink(lock_path_
.value().c_str()));
402 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_
.value().c_str()));
404 std::string
url("about:blank");
405 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE
,
406 NotifyOtherProcessOrCreate(url
, TestTimeouts::action_timeout()));
409 #if defined(OS_MACOSX)
410 // Test that if there is an existing lock file, and we could not flock()
412 TEST_F(ProcessSingletonPosixTest
, CreateRespectsOldMacLock
) {
413 scoped_ptr
<TestableProcessSingleton
> process_singleton(
414 CreateProcessSingleton());
415 base::ScopedFD
lock_fd(HANDLE_EINTR(
416 open(lock_path_
.value().c_str(), O_RDWR
| O_CREAT
| O_EXLOCK
, 0644)));
417 ASSERT_TRUE(lock_fd
.is_valid());
418 EXPECT_FALSE(process_singleton
->Create());
419 base::File::Info info
;
420 EXPECT_TRUE(base::GetFileInfo(lock_path_
, &info
));
421 EXPECT_FALSE(info
.is_directory
);
422 EXPECT_FALSE(info
.is_symbolic_link
);
425 // Test that if there is an existing lock file, and it's not locked, we replace
427 TEST_F(ProcessSingletonPosixTest
, CreateReplacesOldMacLock
) {
428 scoped_ptr
<TestableProcessSingleton
> process_singleton(
429 CreateProcessSingleton());
430 EXPECT_EQ(0, base::WriteFile(lock_path_
, "", 0));
431 EXPECT_TRUE(process_singleton
->Create());
434 #endif // defined(OS_MACOSX)