summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/process_singleton.h26
-rw-r--r--chrome/browser/process_singleton_linux.cc127
-rw-r--r--chrome/browser/process_singleton_linux_uitest.cc324
-rw-r--r--chrome/browser/process_singleton_linux_unittest.cc370
-rw-r--r--chrome/chrome_tests.gypi12
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
7 files changed, 467 insertions, 397 deletions
diff --git a/chrome/browser/process_singleton.h b/chrome/browser/process_singleton.h
index 213999d..203a162 100644
--- a/chrome/browser/process_singleton.h
+++ b/chrome/browser/process_singleton.h
@@ -21,6 +21,7 @@
#include "base/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
+#include "base/process.h"
#include "base/threading/non_thread_safe.h"
#include "ui/gfx/native_widget_types.h"
@@ -94,6 +95,9 @@ class ProcessSingleton : public base::NonThreadSafe {
const CommandLine& command_line,
const NotificationCallback& notification_callback,
int timeout_seconds);
+ void OverrideCurrentPidForTesting(base::ProcessId pid);
+ void OverrideKillCallbackForTesting(const base::Callback<void(int)>& callback);
+ static void DisablePromptForTesting();
#endif // defined(OS_LINUX) || defined(OS_OPENBSD)
#if defined(OS_WIN) && !defined(USE_AURA)
@@ -168,6 +172,28 @@ class ProcessSingleton : public base::NonThreadSafe {
HWND window_; // The HWND_MESSAGE window.
bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment.
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
+ // Return true if the given pid is one of our child processes.
+ // Assumes that the current pid is the root of all pids of the current
+ // instance.
+ bool IsSameChromeInstance(pid_t pid);
+
+ // Extract the process's pid from a symbol link path and if it is on
+ // the same host, kill the process, unlink the lock file and return true.
+ // If the process is part of the same chrome instance, unlink the lock file
+ // and return true without killing it.
+ // If the process is on a different host, return false.
+ bool KillProcessByLockPath();
+
+ // Default function to kill a process, overridable by tests.
+ void KillProcess(int pid);
+
+ // Allow overriding for tests.
+ base::ProcessId current_pid_;
+
+ // Function to call when the other process is hung and needs to be killed.
+ // Allows overriding for tests.
+ base::Callback<void(int)> kill_callback_;
+
// Path in file system to the socket.
FilePath socket_path_;
diff --git a/chrome/browser/process_singleton_linux.cc b/chrome/browser/process_singleton_linux.cc
index 2aa49d7..22d7a92 100644
--- a/chrome/browser/process_singleton_linux.cc
+++ b/chrome/browser/process_singleton_linux.cc
@@ -36,8 +36,6 @@
// process will be considered as hung for some reason. The second process then
// retrieves the process id from the symbol link and kills it by sending
// SIGKILL. Then the second process starts as normal.
-//
-// TODO(james.su@gmail.com): Add unittest for this class.
#include "chrome/browser/process_singleton.h"
@@ -84,7 +82,6 @@
#include "chrome/browser/ui/gtk/process_singleton_dialog.h"
#endif
#include "chrome/common/chrome_constants.h"
-#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_thread.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
@@ -97,6 +94,7 @@ const int ProcessSingleton::kTimeoutInSeconds;
namespace {
+static bool g_disable_prompt;
const char kStartToken[] = "START";
const char kACKToken[] = "ACK";
const char kShutdownToken[] = "SHUTDOWN";
@@ -307,13 +305,13 @@ void DisplayProfileInUseError(const std::string& lock_path,
WideToUTF16(base::SysNativeMBToWide(lock_path)),
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
LOG(ERROR) << base::SysWideToNativeMB(UTF16ToWide(error)).c_str();
+ if (!g_disable_prompt) {
#if defined(TOOLKIT_GTK)
- if (!CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kNoProcessSingletonDialog))
ProcessSingletonDialog::ShowAndRun(UTF16ToUTF8(error));
#else
- NOTIMPLEMENTED();
+ NOTIMPLEMENTED();
#endif
+ }
}
bool IsChromeProcess(pid_t pid) {
@@ -323,53 +321,6 @@ bool IsChromeProcess(pid_t pid) {
FilePath(chrome::kBrowserProcessExecutableName));
}
-// Return true if the given pid is one of our child processes.
-// Assumes that the current pid is the root of all pids of the current instance.
-bool IsSameChromeInstance(pid_t pid) {
- pid_t cur_pid = base::GetCurrentProcId();
- while (pid != cur_pid) {
- pid = base::GetParentProcessId(pid);
- if (pid < 0)
- return false;
- if (!IsChromeProcess(pid))
- return false;
- }
- return true;
-}
-
-// Extract the process's pid from a symbol link path and if it is on
-// the same host, kill the process, unlink the lock file and return true.
-// If the process is part of the same chrome instance, unlink the lock file and
-// return true without killing it.
-// If the process is on a different host, return false.
-bool KillProcessByLockPath(const FilePath& path) {
- std::string hostname;
- int pid;
- ParseLockPath(path, &hostname, &pid);
-
- if (!hostname.empty() && hostname != net::GetHostName()) {
- DisplayProfileInUseError(path.value(), hostname, pid);
- return false;
- }
- UnlinkPath(path);
-
- if (IsSameChromeInstance(pid))
- return true;
-
- if (pid > 0) {
- // TODO(james.su@gmail.com): Is SIGKILL ok?
- int rv = kill(static_cast<base::ProcessHandle>(pid), SIGKILL);
- // ESRCH = No Such Process (can happen if the other process is already in
- // progress of shutting down and finishes before we try to kill it).
- DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: "
- << safe_strerror(errno);
- return true;
- }
-
- LOG(ERROR) << "Failed to extract pid from path: " << path.value();
- return true;
-}
-
// A helper class to hold onto a socket.
class ScopedSocket {
public:
@@ -741,10 +692,14 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
: locked_(false),
foreground_window_(NULL),
+ current_pid_(base::GetCurrentProcId()),
ALLOW_THIS_IN_INITIALIZER_LIST(watcher_(new LinuxWatcher(this))) {
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename);
+
+ kill_callback_ = base::Bind(&ProcessSingleton::KillProcess,
+ base::Unretained(this));
}
ProcessSingleton::~ProcessSingleton() {
@@ -807,7 +762,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
if (retries == timeout_seconds) {
// Retries failed. Kill the unresponsive chrome process and continue.
- if (!kill_unresponsive || !KillProcessByLockPath(lock_path_))
+ if (!kill_unresponsive || !KillProcessByLockPath())
return PROFILE_IN_USE;
return PROCESS_NONE;
}
@@ -838,7 +793,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
// Send the message
if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) {
// Try to kill the other process, because it might have been dead.
- if (!kill_unresponsive || !KillProcessByLockPath(lock_path_))
+ if (!kill_unresponsive || !KillProcessByLockPath())
return PROFILE_IN_USE;
return PROCESS_NONE;
}
@@ -854,7 +809,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
// Failed to read ACK, the other process might have been frozen.
if (len <= 0) {
- if (!kill_unresponsive || !KillProcessByLockPath(lock_path_))
+ if (!kill_unresponsive || !KillProcessByLockPath())
return PROFILE_IN_USE;
return PROCESS_NONE;
}
@@ -908,6 +863,19 @@ ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate(
return LOCK_ERROR;
}
+void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) {
+ current_pid_ = pid;
+}
+
+void ProcessSingleton::OverrideKillCallbackForTesting(
+ const base::Callback<void(int)>& callback) {
+ kill_callback_ = callback;
+}
+
+void ProcessSingleton::DisablePromptForTesting() {
+ g_disable_prompt = true;
+}
+
bool ProcessSingleton::Create(
const NotificationCallback& notification_callback) {
int sock;
@@ -919,7 +887,7 @@ bool ProcessSingleton::Create(
"%s%c%u",
net::GetHostName().c_str(),
kLockDelimiter,
- base::GetCurrentProcId()));
+ current_pid_));
// Create symbol link before binding the socket, to ensure only one instance
// can have the socket open.
@@ -984,3 +952,48 @@ void ProcessSingleton::Cleanup() {
UnlinkPath(cookie_path_);
UnlinkPath(lock_path_);
}
+
+bool ProcessSingleton::IsSameChromeInstance(pid_t pid) {
+ pid_t cur_pid = current_pid_;
+ while (pid != cur_pid) {
+ pid = base::GetParentProcessId(pid);
+ if (pid < 0)
+ return false;
+ if (!IsChromeProcess(pid))
+ return false;
+ }
+ return true;
+}
+
+bool ProcessSingleton::KillProcessByLockPath() {
+ std::string hostname;
+ int pid;
+ ParseLockPath(lock_path_, &hostname, &pid);
+
+ if (!hostname.empty() && hostname != net::GetHostName()) {
+ DisplayProfileInUseError(lock_path_.value(), hostname, pid);
+ return false;
+ }
+ UnlinkPath(lock_path_);
+
+ if (IsSameChromeInstance(pid))
+ return true;
+
+ if (pid > 0) {
+ kill_callback_.Run(pid);
+ return true;
+ }
+
+ LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value();
+ return true;
+}
+
+void ProcessSingleton::KillProcess(int pid) {
+ // TODO(james.su@gmail.com): Is SIGKILL ok?
+ int rv = kill(static_cast<base::ProcessHandle>(pid), SIGKILL);
+ // ESRCH = No Such Process (can happen if the other process is already in
+ // progress of shutting down and finishes before we try to kill it).
+ DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: "
+ << safe_strerror(errno);
+}
+
diff --git a/chrome/browser/process_singleton_linux_uitest.cc b/chrome/browser/process_singleton_linux_uitest.cc
deleted file mode 100644
index d09b3fc..0000000
--- a/chrome/browser/process_singleton_linux_uitest.cc
+++ /dev/null
@@ -1,324 +0,0 @@
-// 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/eintr_wrapper.h"
-#include "base/file_path.h"
-#include "base/path_service.h"
-#include "base/stringprintf.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread.h"
-#include "base/utf_string_conversions.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/chrome_process_util.h"
-#include "chrome/test/ui/ui_test.h"
-#include "net/base/net_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-bool UnexpectedNotificationCallback(const CommandLine& command_line,
- const FilePath& current_directory) {
- ADD_FAILURE() << "This callback should never be invoked because the active "
- << "ProcessSingleton is that of the Browser process hosted by "
- << "UITest.";
- return false;
-}
-
-class ProcessSingletonLinuxTest : public UITest {
- public:
- virtual void SetUp() {
- UITest::SetUp();
- lock_path_ = user_data_dir().Append(chrome::kSingletonLockFilename);
- socket_path_ = user_data_dir().Append(chrome::kSingletonSocketFilename);
- cookie_path_ = user_data_dir().Append(chrome::kSingletonCookieFilename);
- }
-
- virtual void TearDown() {
- UITest::TearDown();
-
- // Check that the test cleaned up after itself.
- struct stat statbuf;
- bool lock_exists = lstat(lock_path_.value().c_str(), &statbuf) == 0;
- EXPECT_FALSE(lock_exists);
-
- if (lock_exists) {
- // Unlink to prevent failing future tests if the lock still exists.
- EXPECT_EQ(unlink(lock_path_.value().c_str()), 0);
- }
- }
-
- FilePath lock_path_;
- FilePath socket_path_;
- FilePath cookie_path_;
-};
-
-ProcessSingleton* CreateProcessSingleton() {
- FilePath user_data_dir;
- PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
-
- return new ProcessSingleton(user_data_dir);
-}
-
-CommandLine CommandLineForUrl(const std::string& url) {
- // Hack: mutate the current process's command line so we don't show a dialog.
- // Note that this only works if we have no loose values on the command line,
- // but that's fine for unit tests. In a UI test we disable error dialogs
- // when spawning Chrome, but this test hits the ProcessSingleton directly.
- CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (!cmd_line->HasSwitch(switches::kNoProcessSingletonDialog))
- cmd_line->AppendSwitch(switches::kNoProcessSingletonDialog);
-
- CommandLine new_cmd_line(*cmd_line);
- new_cmd_line.AppendArg(url);
- return new_cmd_line;
-}
-
-// A helper method to call ProcessSingleton::NotifyOtherProcess().
-// |url| will be added to CommandLine for current process, so that it can be
-// sent to browser process by ProcessSingleton::NotifyOtherProcess().
-ProcessSingleton::NotifyResult NotifyOtherProcess(const std::string& url,
- int timeout_ms) {
- scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
- return process_singleton->NotifyOtherProcessWithTimeout(
- CommandLineForUrl(url), timeout_ms / 1000, true);
-}
-
-// A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
-// |url| will be added to CommandLine for current process, so that it can be
-// sent to browser process by ProcessSingleton::NotifyOtherProcessOrCreate().
-ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
- const std::string& url,
- int timeout_ms) {
- scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
- return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
- CommandLineForUrl(url),
- base::Bind(&UnexpectedNotificationCallback),
- timeout_ms / 1000);
-}
-
-} // namespace
-
-// Test if the socket file and symbol link created by ProcessSingletonLinux
-// are valid. When running this test, the ProcessSingleton object is already
-// initiated by UITest. So we just test against this existing object.
-// This test is flaky as per http://crbug.com/74554.
-TEST_F(ProcessSingletonLinuxTest, DISABLED_CheckSocketFile) {
- 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));
-}
-
-#if defined(OS_LINUX) && defined(TOOLKIT_VIEWS)
-// The following tests in linux/view does not pass without a window manager,
-// which is true in build/try bots.
-// See http://crbug.com/30953.
-#define NotifyOtherProcessSuccess FAILS_NotifyOtherProcessSuccess
-#define NotifyOtherProcessHostChanged FAILS_NotifyOtherProcessHostChanged
-#endif
-
-// TODO(james.su@gmail.com): port following tests to Windows.
-// Test success case of NotifyOtherProcess().
-TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessSuccess) {
- std::string url("about:blank");
- int original_tab_count = GetTabCount();
-
- EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
- NotifyOtherProcess(url, TestTimeouts::action_timeout_ms()));
- EXPECT_EQ(original_tab_count + 1, GetTabCount());
- EXPECT_EQ(url, GetActiveTabURL().spec());
-}
-
-// Test failure case of NotifyOtherProcess().
-TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessFailure) {
- base::ProcessId pid = browser_process_id();
-
- ASSERT_GE(pid, 1);
-
- // Block the browser process, then it'll be killed by
- // ProcessSingleton::NotifyOtherProcess().
- kill(pid, SIGSTOP);
-
- // Wait to make sure the browser process is actually stopped.
- // It's necessary when running with valgrind.
- EXPECT_GE(HANDLE_EINTR(waitpid(pid, 0, WUNTRACED)), 0);
-
- std::string url("about:blank");
- EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
- NotifyOtherProcess(url, TestTimeouts::action_timeout_ms()));
-
- // Wait for a while to make sure the browser process is actually killed.
- int exit_code = 0;
- ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit(
- TestTimeouts::action_max_timeout_ms(), &exit_code));
- EXPECT_EQ(-1, exit_code); // Expect unclean shutdown.
-}
-
-// Test that we don't kill ourselves by accident if a lockfile with the same pid
-// happens to exist.
-// TODO(mattm): This doesn't really need to be a uitest. (We don't use the
-// uitest created browser process, but we do use some uitest provided stuff like
-// the user_data_dir and the NotifyOtherProcess function in this file, which
-// would have to be duplicated or shared if this test was moved into a
-// unittest.)
-TEST_F(ProcessSingletonLinuxTest, NotifyOtherProcessNoSuicide) {
- // 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()));
-
- std::string url("about:blank");
- EXPECT_EQ(ProcessSingleton::PROCESS_NONE,
- NotifyOtherProcess(url, 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) {
- EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
- EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
-
- int original_tab_count = GetTabCount();
-
- std::string url("about:blank");
- EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED,
- NotifyOtherProcess(url, TestTimeouts::action_timeout_ms()));
- EXPECT_EQ(original_tab_count + 1, GetTabCount());
- EXPECT_EQ(url, GetActiveTabURL().spec());
-}
-
-// 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) {
- base::ProcessId pid = browser_process_id();
-
- ASSERT_GE(pid, 1);
-
- // Kill the browser process, so that it does not respond on the socket.
- kill(pid, SIGKILL);
- // Wait for a while to make sure the browser process is actually killed.
- int exit_code = 0;
- ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit(
- TestTimeouts::action_max_timeout_ms(), &exit_code));
- EXPECT_EQ(-1, exit_code); // Expect unclean shutdown.
-
- 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,
- NotifyOtherProcess(url, TestTimeouts::action_timeout_ms()));
-
- ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
-}
-
-// 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) {
- base::ProcessId pid = browser_process_id();
-
- ASSERT_GE(pid, 1);
-
- // Kill the browser process, so that it does not respond on the socket.
- kill(pid, SIGKILL);
- // Wait for a while to make sure the browser process is actually killed.
- int exit_code = 0;
- ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit(
- TestTimeouts::action_max_timeout_ms(), &exit_code));
- EXPECT_EQ(-1, exit_code); // Expect unclean shutdown.
-
- 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()));
-}
-
-// Test that Create fails when another browser is using the profile directory.
-TEST_F(ProcessSingletonLinuxTest, CreateFailsWithExistingBrowser) {
- scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
- EXPECT_FALSE(process_singleton->Create(
- base::Bind(&UnexpectedNotificationCallback)));
-}
-
-// Test that Create fails when another browser is using the profile directory
-// but with the old socket location.
-TEST_F(ProcessSingletonLinuxTest, CreateChecksCompatibilitySocket) {
- scoped_ptr<ProcessSingleton> process_singleton(CreateProcessSingleton());
-
- // 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(&UnexpectedNotificationCallback)));
-}
-
-// 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) {
- // 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()));
-}
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()));
+}
+
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 2f05ebc..feac4f9 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -716,7 +716,6 @@
# NOTE: DON'T ADD NEW TESTS HERE!
# New tests should be browser_tests. browser_tests are sharded and are
# less flakier.
- 'browser/process_singleton_linux_uitest.cc',
'browser/ui/tests/browser_uitest.cc',
'test/automation/automation_proxy_uitest.cc',
'test/automation/automation_proxy_uitest.h',
@@ -726,16 +725,6 @@
# DON'T ADD NEW FILES! SEE NOTE AT TOP OF SECTION.
],
'conditions': [
- ['toolkit_uses_gtk == 1', {
- 'dependencies': [
- '../build/linux/system.gyp:gtk',
- '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck',
- ],
- }, { # else: toolkit_uses_gtk != 1
- 'sources!': [
- 'browser/process_singleton_linux_uitest.cc',
- ],
- }],
['toolkit_views==1', {
'dependencies': [
'../ui/views/views.gyp:views',
@@ -1569,6 +1558,7 @@
'browser/printing/print_preview_unit_test_base.cc',
'browser/printing/print_preview_unit_test_base.h',
'browser/process_info_snapshot_mac_unittest.cc',
+ 'browser/process_singleton_linux_unittest.cc',
'browser/process_singleton_mac_unittest.cc',
'browser/profiles/avatar_menu_model_unittest.cc',
'browser/profiles/gaia_info_update_service_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index d8ed129..48681ef 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1352,10 +1352,6 @@ const char kOobeSkipPostLogin[] = "oobe-skip-postlogin";
// files needed to make this decision.
const char kEnableCrashReporter[] = "enable-crash-reporter";
-// Bypass the error dialog when the profile lock couldn't be attained. This
-// switch is used during automated testing.
-const char kNoProcessSingletonDialog[] = "no-process-singleton-dialog";
-
#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
// Specifies which password store to use (detect, default, gnome, kwallet).
const char kPasswordStore[] = "password-store";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 348344f..11eca01 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -375,7 +375,6 @@ extern const char kOobeSkipPostLogin[];
#if defined(OS_POSIX)
extern const char kEnableCrashReporter[];
-extern const char kNoProcessSingletonDialog[];
#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
extern const char kPasswordStore[];
#endif