1 // Copyright (c) 2012 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"
15 #include "base/bind.h"
16 #include "base/command_line.h"
17 #include "base/files/file_path.h"
18 #include "base/files/scoped_temp_dir.h"
19 #include "base/message_loop/message_loop.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/synchronization/waitable_event.h"
22 #include "base/test/test_timeouts.h"
23 #include "base/test/thread_test_helper.h"
24 #include "base/threading/thread.h"
25 #include "chrome/common/chrome_constants.h"
26 #include "content/public/test/test_browser_thread.h"
27 #include "net/base/net_util.h"
28 #include "testing/gtest/include/gtest/gtest.h"
30 using content::BrowserThread;
34 bool NotificationCallback(const CommandLine& command_line,
35 const base::FilePath& current_directory) {
39 class ProcessSingletonLinuxTest : public testing::Test {
41 // A ProcessSingleton exposing some protected methods for testing.
42 class TestableProcessSingleton : public ProcessSingleton {
44 explicit TestableProcessSingleton(const base::FilePath& user_data_dir)
47 base::Bind(&TestableProcessSingleton::NotificationCallback,
48 base::Unretained(this))) {}
51 std::vector<CommandLine::StringVector> callback_command_lines_;
53 using ProcessSingleton::NotifyOtherProcessWithTimeout;
54 using ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate;
55 using ProcessSingleton::OverrideCurrentPidForTesting;
56 using ProcessSingleton::OverrideKillCallbackForTesting;
59 bool NotificationCallback(const CommandLine& command_line,
60 const base::FilePath& current_directory) {
61 callback_command_lines_.push_back(command_line.argv());
66 ProcessSingletonLinuxTest()
68 io_thread_(BrowserThread::IO),
69 wait_event_(true, false),
70 signal_event_(true, false),
71 process_singleton_on_thread_(NULL) {
72 io_thread_.StartIOThread();
75 virtual void SetUp() {
76 testing::Test::SetUp();
78 ProcessSingleton::DisablePromptForTesting();
79 // Put the lock in a temporary directory. Doesn't need to be a
80 // full profile to test this code.
81 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
82 lock_path_ = temp_dir_.path().Append(chrome::kSingletonLockFilename);
83 socket_path_ = temp_dir_.path().Append(chrome::kSingletonSocketFilename);
84 cookie_path_ = temp_dir_.path().Append(chrome::kSingletonCookieFilename);
87 virtual void TearDown() {
88 scoped_refptr<base::ThreadTestHelper> io_helper(new base::ThreadTestHelper(
89 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get()));
90 ASSERT_TRUE(io_helper->Run());
92 // Destruct the ProcessSingleton object before the IO thread so that its
93 // internals are destructed properly.
94 if (process_singleton_on_thread_) {
95 worker_thread_->message_loop()->PostTask(
97 base::Bind(&ProcessSingletonLinuxTest::DestructProcessSingleton,
98 base::Unretained(this)));
100 scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
101 worker_thread_->message_loop_proxy().get()));
102 ASSERT_TRUE(helper->Run());
106 testing::Test::TearDown();
109 void CreateProcessSingletonOnThread() {
110 ASSERT_EQ(NULL, worker_thread_.get());
111 worker_thread_.reset(new base::Thread("BlockingThread"));
112 worker_thread_->Start();
114 worker_thread_->message_loop()->PostTask(
116 base::Bind(&ProcessSingletonLinuxTest::
117 CreateProcessSingletonInternal,
118 base::Unretained(this)));
120 scoped_refptr<base::ThreadTestHelper> helper(
121 new base::ThreadTestHelper(worker_thread_->message_loop_proxy().get()));
122 ASSERT_TRUE(helper->Run());
125 TestableProcessSingleton* CreateProcessSingleton() {
126 return new TestableProcessSingleton(temp_dir_.path());
129 ProcessSingleton::NotifyResult NotifyOtherProcess(
131 base::TimeDelta timeout) {
132 scoped_ptr<TestableProcessSingleton> process_singleton(
133 CreateProcessSingleton());
134 CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
135 command_line.AppendArg("about:blank");
137 process_singleton->OverrideCurrentPidForTesting(
138 base::GetCurrentProcId() + 1);
139 process_singleton->OverrideKillCallbackForTesting(
140 base::Bind(&ProcessSingletonLinuxTest::KillCallback,
141 base::Unretained(this)));
144 return process_singleton->NotifyOtherProcessWithTimeout(
145 command_line, timeout.InSeconds(), true);
148 // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
149 ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
150 const std::string& url,
151 base::TimeDelta timeout) {
152 scoped_ptr<TestableProcessSingleton> process_singleton(
153 CreateProcessSingleton());
154 CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
155 command_line.AppendArg(url);
156 return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
157 command_line, timeout.InSeconds());
160 void CheckNotified() {
161 ASSERT_TRUE(process_singleton_on_thread_ != NULL);
162 ASSERT_EQ(1u, process_singleton_on_thread_->callback_command_lines_.size());
165 i < process_singleton_on_thread_->callback_command_lines_[0].size();
167 if (process_singleton_on_thread_->callback_command_lines_[0][i] ==
174 ASSERT_EQ(0, kill_callbacks_);
177 void BlockWorkerThread() {
178 worker_thread_->message_loop()->PostTask(
180 base::Bind(&ProcessSingletonLinuxTest::BlockThread,
181 base::Unretained(this)));
184 void UnblockWorkerThread() {
185 wait_event_.Signal(); // Unblock the worker thread for shutdown.
186 signal_event_.Wait(); // Ensure thread unblocks before continuing.
191 signal_event_.Signal();
194 base::FilePath lock_path_;
195 base::FilePath socket_path_;
196 base::FilePath cookie_path_;
200 void CreateProcessSingletonInternal() {
201 ASSERT_TRUE(!process_singleton_on_thread_);
202 process_singleton_on_thread_ = CreateProcessSingleton();
203 ASSERT_EQ(ProcessSingleton::PROCESS_NONE,
204 process_singleton_on_thread_->NotifyOtherProcessOrCreate());
207 void DestructProcessSingleton() {
208 ASSERT_TRUE(process_singleton_on_thread_);
209 delete process_singleton_on_thread_;
212 void KillCallback(int pid) {
216 content::TestBrowserThread io_thread_;
217 base::ScopedTempDir temp_dir_;
218 base::WaitableEvent wait_event_;
219 base::WaitableEvent signal_event_;
221 scoped_ptr<base::Thread> worker_thread_;
222 TestableProcessSingleton* process_singleton_on_thread_;
227 // Test if the socket file and symbol link created by ProcessSingletonLinux
229 // If this test flakes, use http://crbug.com/74554.
230 TEST_F(ProcessSingletonLinuxTest, CheckSocketFile) {
231 CreateProcessSingletonOnThread();
233 ASSERT_EQ(0, lstat(lock_path_.value().c_str(), &statbuf));
234 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
236 ssize_t len = readlink(lock_path_.value().c_str(), buf, PATH_MAX);
239 ASSERT_EQ(0, lstat(socket_path_.value().c_str(), &statbuf));
240 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
242 len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
244 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
246 ASSERT_EQ(0, lstat(socket_target_path.value().c_str(), &statbuf));
247 ASSERT_TRUE(S_ISSOCK(statbuf.st_mode));
249 len = readlink(cookie_path_.value().c_str(), buf, PATH_MAX);
251 std::string cookie(buf, len);
253 base::FilePath remote_cookie_path = socket_target_path.DirName().
254 Append(chrome::kSingletonCookieFilename);
255 len = readlink(remote_cookie_path.value().c_str(), buf, PATH_MAX);
257 EXPECT_EQ(cookie, std::string(buf, len));
260 // TODO(james.su@gmail.com): port following tests to Windows.
261 // Test success case of NotifyOtherProcess().
262 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessSuccess) {
263 CreateProcessSingletonOnThread();
264 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
265 NotifyOtherProcess(true, TestTimeouts::action_timeout()));
269 // Test failure case of NotifyOtherProcess().
270 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessFailure) {
271 CreateProcessSingletonOnThread();
274 EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
275 NotifyOtherProcess(true, TestTimeouts::action_timeout()));
277 ASSERT_EQ(1, kill_callbacks_);
278 UnblockWorkerThread();
281 // Test that we don't kill ourselves by accident if a lockfile with the same pid
283 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessNoSuicide) {
284 CreateProcessSingletonOnThread();
285 // Replace lockfile with one containing our own pid.
286 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
287 std::string symlink_content = base::StringPrintf(
289 net::GetHostName().c_str(),
291 base::GetCurrentProcId());
292 EXPECT_EQ(0, symlink(symlink_content.c_str(), lock_path_.value().c_str()));
294 // Remove socket so that we will not be able to notify the existing browser.
295 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
297 EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
298 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
299 // If we've gotten to this point without killing ourself, the test succeeded.
302 // Test that we can still notify a process on the same host even after the
304 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessHostChanged) {
305 CreateProcessSingletonOnThread();
306 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
307 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
309 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
310 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
314 // Test that we fail when lock says process is on another host and we can't
315 // notify it over the socket.
316 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessDifferingHost) {
317 CreateProcessSingletonOnThread();
321 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
322 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
324 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
325 NotifyOtherProcess(false, TestTimeouts::action_timeout()));
327 ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
329 UnblockWorkerThread();
332 // Test that we fail when lock says process is on another host and we can't
333 // notify it over the socket.
334 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_DifferingHost) {
335 CreateProcessSingletonOnThread();
339 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
340 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
342 std::string url("about:blank");
343 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
344 NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout()));
346 ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
348 UnblockWorkerThread();
351 // Test that Create fails when another browser is using the profile directory.
352 TEST_F(ProcessSingletonLinuxTest, CreateFailsWithExistingBrowser) {
353 CreateProcessSingletonOnThread();
355 scoped_ptr<TestableProcessSingleton> process_singleton(
356 CreateProcessSingleton());
357 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
358 EXPECT_FALSE(process_singleton->Create());
361 // Test that Create fails when another browser is using the profile directory
362 // but with the old socket location.
363 TEST_F(ProcessSingletonLinuxTest, CreateChecksCompatibilitySocket) {
364 CreateProcessSingletonOnThread();
365 scoped_ptr<TestableProcessSingleton> process_singleton(
366 CreateProcessSingleton());
367 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
369 // Do some surgery so as to look like the old configuration.
371 ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf));
373 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
374 ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
375 ASSERT_EQ(0, rename(socket_target_path.value().c_str(),
376 socket_path_.value().c_str()));
377 ASSERT_EQ(0, unlink(cookie_path_.value().c_str()));
379 EXPECT_FALSE(process_singleton->Create());
382 // Test that we fail when lock says process is on another host and we can't
383 // notify it over the socket before of a bad cookie.
384 TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_BadCookie) {
385 CreateProcessSingletonOnThread();
386 // Change the cookie.
387 EXPECT_EQ(0, unlink(cookie_path_.value().c_str()));
388 EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str()));
390 // Also change the hostname, so the remote does not retry.
391 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
392 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
394 std::string url("about:blank");
395 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
396 NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout()));