// 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/common/service_process_util.h" #include "base/basictypes.h" #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "base/strings/string_split.h" #if !defined(OS_MACOSX) #include "base/at_exit.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" #include "base/threading/thread.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "testing/multiprocess_func_list.h" #if defined(OS_WIN) #include "base/win/win_util.h" #endif #if defined(OS_POSIX) #include "chrome/common/auto_start_linux.h" #endif #if defined(USE_AURA) // This test fails http://crbug.com/84854, and is very flaky on CrOS and // somewhat flaky on other Linux. #define MAYBE_ForceShutdown DISABLED_ForceShutdown #else #if defined(OS_LINUX) || defined(OS_WIN) #define MAYBE_ForceShutdown DISABLED_ForceShutdown #else #define MAYBE_ForceShutdown ForceShutdown #endif #endif namespace { bool g_good_shutdown = false; void ShutdownTask(base::MessageLoop* loop) { // Quit the main message loop. ASSERT_FALSE(g_good_shutdown); g_good_shutdown = true; loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); } } // namespace TEST(ServiceProcessUtilTest, ScopedVersionedName) { std::string test_str = "test"; std::string scoped_name = GetServiceProcessScopedVersionedName(test_str); chrome::VersionInfo version_info; DCHECK(version_info.is_valid()); EXPECT_TRUE(EndsWith(scoped_name, test_str, true)); EXPECT_NE(std::string::npos, scoped_name.find(version_info.Version())); } class ServiceProcessStateTest : public base::MultiProcessTest { public: ServiceProcessStateTest(); virtual ~ServiceProcessStateTest(); virtual void SetUp(); base::MessageLoopProxy* IOMessageLoopProxy() { return io_thread_.message_loop_proxy().get(); } void LaunchAndWait(const std::string& name); private: // This is used to release the ServiceProcessState singleton after each test. base::ShadowingAtExitManager at_exit_manager_; base::Thread io_thread_; }; ServiceProcessStateTest::ServiceProcessStateTest() : io_thread_("ServiceProcessStateTestThread") { } ServiceProcessStateTest::~ServiceProcessStateTest() { } void ServiceProcessStateTest::SetUp() { base::Thread::Options options(base::MessageLoop::TYPE_IO, 0); ASSERT_TRUE(io_thread_.StartWithOptions(options)); } void ServiceProcessStateTest::LaunchAndWait(const std::string& name) { base::ProcessHandle handle = SpawnChild(name); ASSERT_TRUE(handle); int exit_code = 0; ASSERT_TRUE(base::WaitForExitCode(handle, &exit_code)); ASSERT_EQ(exit_code, 0); } TEST_F(ServiceProcessStateTest, Singleton) { ServiceProcessState state; ASSERT_TRUE(state.Initialize()); LaunchAndWait("ServiceProcessStateTestSingleton"); } TEST_F(ServiceProcessStateTest, ReadyState) { ASSERT_FALSE(CheckServiceProcessReady()); ServiceProcessState state; ASSERT_TRUE(state.Initialize()); ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), base::Closure())); LaunchAndWait("ServiceProcessStateTestReadyTrue"); state.SignalStopped(); LaunchAndWait("ServiceProcessStateTestReadyFalse"); } TEST_F(ServiceProcessStateTest, AutoRun) { ServiceProcessState state; ASSERT_TRUE(state.AddToAutoRun()); scoped_ptr<CommandLine> autorun_command_line; #if defined(OS_WIN) std::string value_name = GetServiceProcessScopedName("_service_run"); base::string16 value; EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER, base::UTF8ToWide(value_name), &value)); autorun_command_line.reset(new CommandLine(CommandLine::FromString(value))); #elif defined(OS_POSIX) && !defined(OS_MACOSX) #if defined(GOOGLE_CHROME_BUILD) std::string base_desktop_name = "google-chrome-service.desktop"; #else // CHROMIUM_BUILD std::string base_desktop_name = "chromium-service.desktop"; #endif std::string exec_value; EXPECT_TRUE(AutoStart::GetAutostartFileValue( GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value)); // Make sure |exec_value| doesn't contain strings a shell would // treat specially. ASSERT_EQ(std::string::npos, exec_value.find('#')); ASSERT_EQ(std::string::npos, exec_value.find('\n')); ASSERT_EQ(std::string::npos, exec_value.find('"')); ASSERT_EQ(std::string::npos, exec_value.find('\'')); CommandLine::StringVector argv; base::SplitString(exec_value, ' ', &argv); ASSERT_GE(argv.size(), 2U) << "Expected at least one command-line option in: " << exec_value; autorun_command_line.reset(new CommandLine(argv)); #endif // defined(OS_WIN) if (autorun_command_line.get()) { EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType), std::string(switches::kServiceProcess)); } ASSERT_TRUE(state.RemoveFromAutoRun()); #if defined(OS_WIN) EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER, base::UTF8ToWide(value_name), &value)); #elif defined(OS_POSIX) && !defined(OS_MACOSX) EXPECT_FALSE(AutoStart::GetAutostartFileValue( GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value)); #endif // defined(OS_WIN) } TEST_F(ServiceProcessStateTest, SharedMem) { std::string version; base::ProcessId pid; #if defined(OS_WIN) // On Posix, named shared memory uses a file on disk. This file // could be lying around from previous crashes which could cause // GetServiceProcessPid to lie. On Windows, we use a named event so we // don't have this issue. Until we have a more stable shared memory // implementation on Posix, this check will only execute on Windows. ASSERT_FALSE(GetServiceProcessData(&version, &pid)); #endif // defined(OS_WIN) ServiceProcessState state; ASSERT_TRUE(state.Initialize()); ASSERT_TRUE(GetServiceProcessData(&version, &pid)); ASSERT_EQ(base::GetCurrentProcId(), pid); } TEST_F(ServiceProcessStateTest, MAYBE_ForceShutdown) { base::ProcessHandle handle = SpawnChild("ServiceProcessStateTestShutdown"); ASSERT_TRUE(handle); for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) { base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); } ASSERT_TRUE(CheckServiceProcessReady()); std::string version; base::ProcessId pid; ASSERT_TRUE(GetServiceProcessData(&version, &pid)); ASSERT_TRUE(ForceServiceProcessShutdown(version, pid)); int exit_code = 0; ASSERT_TRUE(base::WaitForExitCodeWithTimeout(handle, &exit_code, TestTimeouts::action_max_timeout())); base::CloseProcessHandle(handle); ASSERT_EQ(exit_code, 0); } MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) { ServiceProcessState state; EXPECT_FALSE(state.Initialize()); return 0; } MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) { EXPECT_TRUE(CheckServiceProcessReady()); return 0; } MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) { EXPECT_FALSE(CheckServiceProcessReady()); return 0; } MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) { base::MessageLoop message_loop; message_loop.set_thread_name("ServiceProcessStateTestShutdownMainThread"); base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread"); base::Thread::Options options(base::MessageLoop::TYPE_IO, 0); EXPECT_TRUE(io_thread_.StartWithOptions(options)); ServiceProcessState state; EXPECT_TRUE(state.Initialize()); EXPECT_TRUE(state.SignalReady( io_thread_.message_loop_proxy().get(), base::Bind(&ShutdownTask, base::MessageLoop::current()))); message_loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), TestTimeouts::action_max_timeout()); EXPECT_FALSE(g_good_shutdown); message_loop.Run(); EXPECT_TRUE(g_good_shutdown); return 0; } #else // !OS_MACOSX #include <CoreFoundation/CoreFoundation.h> #include "base/file_util.h" #include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" #include "base/mac/mac_util.h" #include "base/test/test_timeouts.h" #include "base/threading/thread.h" #include "chrome/common/mac/launchd.h" #include "chrome/common/mac/mock_launchd.h" #include "testing/gtest/include/gtest/gtest.h" class ServiceProcessStateFileManipulationTest : public ::testing::Test { protected: ServiceProcessStateFileManipulationTest() : io_thread_("ServiceProcessStateFileManipulationTest_IO") { } virtual ~ServiceProcessStateFileManipulationTest() { } virtual void SetUp() { base::Thread::Options options; options.message_loop_type = base::MessageLoop::TYPE_IO; ASSERT_TRUE(io_thread_.StartWithOptions(options)); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(MockLaunchd::MakeABundle(GetTempDirPath(), "Test", &bundle_path_, &executable_path_)); mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_, false, false)); scoped_launchd_instance_.reset( new Launchd::ScopedInstance(mock_launchd_.get())); ASSERT_TRUE(service_process_state_.Initialize()); ASSERT_TRUE(service_process_state_.SignalReady( io_thread_.message_loop_proxy().get(), base::Closure())); loop_.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), TestTimeouts::action_max_timeout()); } const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); } const base::FilePath& executable_path() const { return executable_path_; } const base::FilePath& bundle_path() const { return bundle_path_; } const base::FilePath& GetTempDirPath() const { return temp_dir_.path(); } base::MessageLoopProxy* GetIOMessageLoopProxy() { return io_thread_.message_loop_proxy().get(); } void Run() { loop_.Run(); } private: base::ScopedTempDir temp_dir_; base::MessageLoopForUI loop_; base::Thread io_thread_; base::FilePath executable_path_, bundle_path_; scoped_ptr<MockLaunchd> mock_launchd_; scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_; ServiceProcessState service_process_state_; }; void DeleteFunc(const base::FilePath& file) { EXPECT_TRUE(base::DeleteFile(file, true)); } void MoveFunc(const base::FilePath& from, const base::FilePath& to) { EXPECT_TRUE(base::Move(from, to)); } void ChangeAttr(const base::FilePath& from, int mode) { EXPECT_EQ(chmod(from.value().c_str(), mode), 0); } class ScopedAttributesRestorer { public: ScopedAttributesRestorer(const base::FilePath& path, int mode) : path_(path), mode_(mode) { } ~ScopedAttributesRestorer() { ChangeAttr(path_, mode_); } private: base::FilePath path_; int mode_; }; void TrashFunc(const base::FilePath& src) { FSRef path_ref; FSRef new_path_ref; EXPECT_TRUE(base::mac::FSRefFromPath(src.value(), &path_ref)); OSStatus status = FSMoveObjectToTrashSync(&path_ref, &new_path_ref, kFSFileOperationDefaultOptions); EXPECT_EQ(status, noErr) << "FSMoveObjectToTrashSync " << status; } TEST_F(ServiceProcessStateFileManipulationTest, VerifyLaunchD) { // There have been problems where launchd has gotten into a bad state, usually // because something had deleted all the files in /tmp. launchd depends on // a Unix Domain Socket that it creates at /tmp/launchd*/sock. // The symptom of this problem is that the service process connect fails // on Mac and "launch_msg(): Socket is not connected" appears. // This test is designed to make sure that launchd is working. // http://crbug/75518 CommandLine cl(base::FilePath("/bin/launchctl")); cl.AppendArg("list"); cl.AppendArg("com.apple.launchctl.Aqua"); std::string output; int exit_code = -1; ASSERT_TRUE(base::GetAppOutputWithExitCode(cl, &output, &exit_code) && exit_code == 0) << " exit_code:" << exit_code << " " << output; } TEST_F(ServiceProcessStateFileManipulationTest, DeleteFile) { GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&DeleteFunc, executable_path())); Run(); ASSERT_TRUE(mock_launchd()->remove_called()); ASSERT_TRUE(mock_launchd()->delete_called()); } TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) { GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&DeleteFunc, bundle_path())); Run(); ASSERT_TRUE(mock_launchd()->remove_called()); ASSERT_TRUE(mock_launchd()->delete_called()); } TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) { base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle"); GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&MoveFunc, bundle_path(), new_loc)); Run(); ASSERT_TRUE(mock_launchd()->restart_called()); ASSERT_TRUE(mock_launchd()->write_called()); } TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) { base::FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile"); GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&MoveFunc, executable_path(), new_loc)); Run(); ASSERT_TRUE(mock_launchd()->remove_called()); ASSERT_TRUE(mock_launchd()->delete_called()); } TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) { FSRef bundle_ref; ASSERT_TRUE(base::mac::FSRefFromPath(bundle_path().value(), &bundle_ref)); GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&TrashFunc, bundle_path())); Run(); ASSERT_TRUE(mock_launchd()->remove_called()); ASSERT_TRUE(mock_launchd()->delete_called()); std::string path(base::mac::PathFromFSRef(bundle_ref)); base::FilePath file_path(path); ASSERT_TRUE(base::DeleteFile(file_path, true)); } TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) { ScopedAttributesRestorer restorer(bundle_path(), 0777); GetIOMessageLoopProxy()->PostTask( FROM_HERE, base::Bind(&ChangeAttr, bundle_path(), 0222)); Run(); ASSERT_TRUE(mock_launchd()->remove_called()); ASSERT_TRUE(mock_launchd()->delete_called()); } #endif // !OS_MACOSX