summaryrefslogtreecommitdiffstats
path: root/chrome/browser/process_singleton_linux_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/process_singleton_linux_unittest.cc')
-rw-r--r--chrome/browser/process_singleton_linux_unittest.cc370
1 files changed, 370 insertions, 0 deletions
diff --git a/chrome/browser/process_singleton_linux_unittest.cc b/chrome/browser/process_singleton_linux_unittest.cc
new file mode 100644
index 0000000..ede5915
--- /dev/null
+++ b/chrome/browser/process_singleton_linux_unittest.cc
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 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 <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <vector>
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "base/test/thread_test_helper.h"
+#include "base/threading/thread.h"
+#include "chrome/common/chrome_constants.h"
+#include "content/test/test_browser_thread.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+
+namespace {
+
+bool NotificationCallback(const CommandLine& command_line,
+ const FilePath& current_directory) {
+ return true;
+}
+
+class ProcessSingletonLinuxTest : public testing::Test {
+ public:
+ ProcessSingletonLinuxTest()
+ : kill_callbacks_(0),
+ io_thread_(content::BrowserThread::IO),
+ wait_event_(true, false),
+ signal_event_(true, false),
+ process_singleton_on_thread_(NULL) {
+ io_thread_.StartIOThread();
+ }
+
+ virtual void SetUp() {
+ testing::Test::SetUp();
+
+ ProcessSingleton::DisablePromptForTesting();
+ // Put the lock in a temporary directory. Doesn't need to be a
+ // full profile to test this code.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ lock_path_ = temp_dir_.path().Append(chrome::kSingletonLockFilename);
+ socket_path_ = temp_dir_.path().Append(chrome::kSingletonSocketFilename);
+ cookie_path_ = temp_dir_.path().Append(chrome::kSingletonCookieFilename);
+ }
+
+ virtual void TearDown() {
+ io_thread_.Stop();
+ testing::Test::TearDown();
+
+ if (process_singleton_on_thread_) {
+ worker_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProcessSingletonLinuxTest::DestructProcessSingleton,
+ base::Unretained(this)));
+
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ worker_thread_->message_loop_proxy()));
+ ASSERT_TRUE(helper->Run());
+ }
+ }
+
+ void CreateProcessSingletonOnThread() {
+ ASSERT_EQ(NULL, worker_thread_.get());
+ worker_thread_.reset(new base::Thread("BlockingThread"));
+ worker_thread_->Start();
+
+ worker_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProcessSingletonLinuxTest::
+ CreateProcessSingletonInternal,
+ base::Unretained(this)));
+
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ worker_thread_->message_loop_proxy()));
+ ASSERT_TRUE(helper->Run());
+ }
+
+ ProcessSingleton* CreateProcessSingleton() {
+ return new ProcessSingleton(temp_dir_.path());
+ }
+
+ ProcessSingleton::NotifyResult NotifyOtherProcess(
+ bool override_kill,
+ int timeout_ms) {
+ scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
+ CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
+ command_line.AppendArg("about:blank");
+ if (override_kill) {
+ process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
+ process_singleton->OverrideKillCallbackForTesting(
+ base::Bind(&ProcessSingletonLinuxTest::KillCallback,
+ base::Unretained(this)));
+ }
+
+ return process_singleton->NotifyOtherProcessWithTimeout(
+ command_line, timeout_ms / 1000, true);
+ }
+
+ // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
+ ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
+ const std::string& url,
+ int timeout_ms) {
+ scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
+ CommandLine command_line(CommandLine::ForCurrentProcess()->GetProgram());
+ command_line.AppendArg(url);
+ return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
+ command_line, base::Bind(&NotificationCallback), timeout_ms / 1000);
+ }
+
+ void CheckNotified() {
+ ASSERT_EQ(1u, callback_command_lines_.size());
+ bool found = false;
+ for (size_t i = 0; i < callback_command_lines_[0].size(); ++i) {
+ if (callback_command_lines_[0][i] == "about:blank") {
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found);
+ ASSERT_EQ(0, kill_callbacks_);
+ }
+
+ void BlockWorkerThread() {
+ worker_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProcessSingletonLinuxTest::BlockThread,
+ base::Unretained(this)));
+ }
+
+ void UnblockWorkerThread() {
+ wait_event_.Signal(); // Unblock the worker thread for shutdown.
+ signal_event_.Wait(); // Ensure thread unblocks before continuing.
+ }
+
+ void BlockThread() {
+ wait_event_.Wait();
+ signal_event_.Signal();
+ }
+
+ FilePath lock_path_;
+ FilePath socket_path_;
+ FilePath cookie_path_;
+ int kill_callbacks_;
+
+ private:
+ void CreateProcessSingletonInternal() {
+ ASSERT_TRUE(!process_singleton_on_thread_);
+ process_singleton_on_thread_ = CreateProcessSingleton();
+ ASSERT_EQ(ProcessSingleton::PROCESS_NONE,
+ process_singleton_on_thread_->NotifyOtherProcessOrCreate(
+ base::Bind(&ProcessSingletonLinuxTest::InternalCallback,
+ base::Unretained(this))));
+ }
+
+ void DestructProcessSingleton() {
+ ASSERT_TRUE(process_singleton_on_thread_);
+ delete process_singleton_on_thread_;
+ }
+
+ bool InternalCallback(const CommandLine& command_line,
+ const FilePath& current_directory) {
+ callback_command_lines_.push_back(command_line.argv());
+ return true;
+ }
+
+ void KillCallback(int pid) {
+ kill_callbacks_++;
+ }
+
+ content::TestBrowserThread io_thread_;
+ ScopedTempDir temp_dir_;
+ base::WaitableEvent wait_event_;
+ base::WaitableEvent signal_event_;
+
+ scoped_ptr<base::Thread> worker_thread_;
+ ProcessSingleton* process_singleton_on_thread_;
+
+ std::vector<CommandLine::StringVector> callback_command_lines_;
+};
+
+} // namespace
+
+// Test if the socket file and symbol link created by ProcessSingletonLinux
+// are valid.
+// If this test flakes, use http://crbug.com/74554.
+TEST_F(ProcessSingletonLinuxTest, CheckSocketFile) {
+ CreateProcessSingletonOnThread();
+ struct stat statbuf;
+ ASSERT_EQ(0, lstat(lock_path_.value().c_str(), &statbuf));
+ ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
+ char buf[PATH_MAX];
+ ssize_t len = readlink(lock_path_.value().c_str(), buf, PATH_MAX);
+ ASSERT_GT(len, 0);
+
+ ASSERT_EQ(0, lstat(socket_path_.value().c_str(), &statbuf));
+ ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
+
+ len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
+ ASSERT_GT(len, 0);
+ FilePath socket_target_path = FilePath(std::string(buf, len));
+
+ ASSERT_EQ(0, lstat(socket_target_path.value().c_str(), &statbuf));
+ ASSERT_TRUE(S_ISSOCK(statbuf.st_mode));
+
+ len = readlink(cookie_path_.value().c_str(), buf, PATH_MAX);
+ ASSERT_GT(len, 0);
+ std::string cookie(buf, len);
+
+ FilePath remote_cookie_path = socket_target_path.DirName().
+ Append(chrome::kSingletonCookieFilename);
+ len = readlink(remote_cookie_path.value().c_str(), buf, PATH_MAX);
+ ASSERT_GT(len, 0);
+ EXPECT_EQ(cookie, std::string(buf, len));
+}
+
+// TODO(james.su@gmail.com): port following tests to Windows.
+// Test success case of NotifyOtherProcess().
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessSuccess) {
+ CreateProcessSingletonOnThread();
+ EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
+ NotifyOtherProcess(true, TestTimeouts::action_timeout_ms()));
+ CheckNotified();
+}
+
+// Test failure case of NotifyOtherProcess().
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessFailure) {
+ CreateProcessSingletonOnThread();
+
+ BlockWorkerThread();
+ EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
+ NotifyOtherProcess(true, TestTimeouts::action_timeout_ms()));
+
+ ASSERT_EQ(1, kill_callbacks_);
+ UnblockWorkerThread();
+}
+
+// Test that we don't kill ourselves by accident if a lockfile with the same pid
+// happens to exist.
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessNoSuicide) {
+ CreateProcessSingletonOnThread();
+ // Replace lockfile with one containing our own pid.
+ EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
+ std::string symlink_content = base::StringPrintf(
+ "%s%c%u",
+ net::GetHostName().c_str(),
+ '-',
+ base::GetCurrentProcId());
+ EXPECT_EQ(0, symlink(symlink_content.c_str(), lock_path_.value().c_str()));
+
+ // Remove socket so that we will not be able to notify the existing browser.
+ EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
+
+ EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
+ NotifyOtherProcess(false, TestTimeouts::action_timeout_ms()));
+ // If we've gotten to this point without killing ourself, the test succeeded.
+}
+
+// Test that we can still notify a process on the same host even after the
+// hostname changed.
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessHostChanged) {
+ CreateProcessSingletonOnThread();
+ EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
+ EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
+
+ EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
+ NotifyOtherProcess(false, TestTimeouts::action_timeout_ms()));
+ CheckNotified();
+}
+
+// Test that we fail when lock says process is on another host and we can't
+// notify it over the socket.
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessDifferingHost) {
+ CreateProcessSingletonOnThread();
+
+ BlockWorkerThread();
+
+ EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
+ EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
+
+ EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
+ NotifyOtherProcess(false, TestTimeouts::action_timeout_ms()));
+
+ ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
+
+ UnblockWorkerThread();
+}
+
+// Test that we fail when lock says process is on another host and we can't
+// notify it over the socket.
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_DifferingHost) {
+ CreateProcessSingletonOnThread();
+
+ BlockWorkerThread();
+
+ EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
+ EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
+
+ std::string url("about:blank");
+ EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
+ NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout_ms()));
+
+ ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
+
+ UnblockWorkerThread();
+}
+
+// Test that Create fails when another browser is using the profile directory.
+TEST_F(ProcessSingletonLinuxTest, CreateFailsWithExistingBrowser) {
+ CreateProcessSingletonOnThread();
+
+ scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
+ process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
+ EXPECT_FALSE(process_singleton->Create(
+ base::Bind(&NotificationCallback)));
+}
+
+// Test that Create fails when another browser is using the profile directory
+// but with the old socket location.
+TEST_F(ProcessSingletonLinuxTest, CreateChecksCompatibilitySocket) {
+ CreateProcessSingletonOnThread();
+ scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
+ process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
+
+ // Do some surgery so as to look like the old configuration.
+ char buf[PATH_MAX];
+ ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf));
+ ASSERT_GT(len, 0);
+ FilePath socket_target_path = FilePath(std::string(buf, len));
+ ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
+ ASSERT_EQ(0, rename(socket_target_path.value().c_str(),
+ socket_path_.value().c_str()));
+ ASSERT_EQ(0, unlink(cookie_path_.value().c_str()));
+
+ EXPECT_FALSE(process_singleton->Create(base::Bind(&NotificationCallback)));
+}
+
+// Test that we fail when lock says process is on another host and we can't
+// notify it over the socket before of a bad cookie.
+TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessOrCreate_BadCookie) {
+ CreateProcessSingletonOnThread();
+ // Change the cookie.
+ EXPECT_EQ(0, unlink(cookie_path_.value().c_str()));
+ EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str()));
+
+ // Also change the hostname, so the remote does not retry.
+ EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
+ EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
+
+ std::string url("about:blank");
+ EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE,
+ NotifyOtherProcessOrCreate(url, TestTimeouts::action_timeout_ms()));
+}
+