diff options
author | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-22 05:19:29 +0000 |
---|---|---|
committer | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-22 05:19:29 +0000 |
commit | 4130d9e2ee756e06aae9861eb4658b0578ee67bd (patch) | |
tree | 18d47b0dac25e8bf76783ce524d37b21dc9bcbe1 /chrome/common/service_process_util_unittest.cc | |
parent | 9b8dea2472f7df4036f07f370ab4321f24823c8c (diff) | |
download | chromium_src-4130d9e2ee756e06aae9861eb4658b0578ee67bd.zip chromium_src-4130d9e2ee756e06aae9861eb4658b0578ee67bd.tar.gz chromium_src-4130d9e2ee756e06aae9861eb4658b0578ee67bd.tar.bz2 |
Getting service process on Mac to handle having things moved/changed underneath it.
BUG=74983
TEST=See http://code.google.com/p/chromium/issues/detail?id=74983#c16
Review URL: http://codereview.chromium.org/6660001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78967 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/service_process_util_unittest.cc')
-rw-r--r-- | chrome/common/service_process_util_unittest.cc | 369 |
1 files changed, 347 insertions, 22 deletions
diff --git a/chrome/common/service_process_util_unittest.cc b/chrome/common/service_process_util_unittest.cc index 03827e0..e542f09 100644 --- a/chrome/common/service_process_util_unittest.cc +++ b/chrome/common/service_process_util_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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" #if !defined(OS_MACOSX) -// TODO(dmaclach): Figure out tests that will work with launchd on Mac OS. - #include "base/at_exit.h" #include "base/command_line.h" #include "base/process_util.h" @@ -18,7 +18,6 @@ #include "base/utf_string_conversions.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" -#include "chrome/common/service_process_util.h" #include "testing/multiprocess_func_list.h" #if defined(OS_WIN) @@ -89,24 +88,24 @@ void ServiceProcessStateTest::LaunchAndWait(const std::string& name) { } TEST_F(ServiceProcessStateTest, Singleton) { - ServiceProcessState* state = ServiceProcessState::GetInstance(); - ASSERT_TRUE(state->Initialize()); + ServiceProcessState state; + ASSERT_TRUE(state.Initialize()); LaunchAndWait("ServiceProcessStateTestSingleton"); } TEST_F(ServiceProcessStateTest, ReadyState) { ASSERT_FALSE(CheckServiceProcessReady()); - ServiceProcessState* state = ServiceProcessState::GetInstance(); - ASSERT_TRUE(state->Initialize()); - ASSERT_TRUE(state->SignalReady(IOMessageLoopProxy(), NULL)); + ServiceProcessState state; + ASSERT_TRUE(state.Initialize()); + ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), NULL)); LaunchAndWait("ServiceProcessStateTestReadyTrue"); - state->SignalStopped(); + state.SignalStopped(); LaunchAndWait("ServiceProcessStateTestReadyFalse"); } TEST_F(ServiceProcessStateTest, AutoRun) { - ServiceProcessState* state = ServiceProcessState::GetInstance(); - ASSERT_TRUE(state->AddToAutoRun()); + ServiceProcessState state; + ASSERT_TRUE(state.AddToAutoRun()); scoped_ptr<CommandLine> autorun_command_line; #if defined(OS_WIN) std::string value_name = GetServiceProcessScopedName("_service_run"); @@ -139,7 +138,7 @@ TEST_F(ServiceProcessStateTest, AutoRun) { EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType), std::string(switches::kServiceProcess)); } - ASSERT_TRUE(state->RemoveFromAutoRun()); + ASSERT_TRUE(state.RemoveFromAutoRun()); #if defined(OS_WIN) EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER, UTF8ToWide(value_name), @@ -161,8 +160,8 @@ TEST_F(ServiceProcessStateTest, SharedMem) { // implementation on Posix, this check will only execute on Windows. ASSERT_FALSE(GetServiceProcessData(&version, &pid)); #endif // defined(OS_WIN) - ServiceProcessState* state = ServiceProcessState::GetInstance(); - ASSERT_TRUE(state->Initialize()); + ServiceProcessState state; + ASSERT_TRUE(state.Initialize()); ASSERT_TRUE(GetServiceProcessData(&version, &pid)); ASSERT_EQ(base::GetCurrentProcId(), pid); } @@ -186,8 +185,8 @@ TEST_F(ServiceProcessStateTest, ForceShutdown) { } MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) { - ServiceProcessState* state = ServiceProcessState::GetInstance(); - EXPECT_FALSE(state->Initialize()); + ServiceProcessState state; + EXPECT_FALSE(state.Initialize()); return 0; } @@ -207,11 +206,11 @@ MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) { base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread"); base::Thread::Options options(MessageLoop::TYPE_IO, 0); EXPECT_TRUE(io_thread_.StartWithOptions(options)); - ServiceProcessState* state = ServiceProcessState::GetInstance(); - EXPECT_TRUE(state->Initialize()); - EXPECT_TRUE(state->SignalReady(io_thread_.message_loop_proxy(), - NewRunnableFunction(&ShutdownTask, - MessageLoop::current()))); + ServiceProcessState state; + EXPECT_TRUE(state.Initialize()); + EXPECT_TRUE(state.SignalReady(io_thread_.message_loop_proxy(), + NewRunnableFunction(&ShutdownTask, + MessageLoop::current()))); message_loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), TestTimeouts::action_max_timeout_ms()); @@ -221,4 +220,330 @@ MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) { return 0; } +#else // !OS_MACOSX + +#include <CoreFoundation/CoreFoundation.h> + +#include <launch.h> +#include <sys/stat.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "base/stringprintf.h" +#include "base/sys_string_conversions.h" +#include "base/test/test_timeouts.h" +#include "base/threading/thread.h" +#include "chrome/common/launchd_mac.h" +#include "testing/gtest/include/gtest/gtest.h" + +// TODO(dmaclach): Write this in terms of a real mock. +// http://crbug.com/76923 +class MockLaunchd : public Launchd { + public: + MockLaunchd(const FilePath& file, MessageLoop* loop) + : file_(file), + message_loop_(loop), + restart_called_(false), + remove_called_(false), + checkin_called_(false), + write_called_(false), + delete_called_(false) { + } + virtual ~MockLaunchd() { } + + virtual CFDictionaryRef CopyExports() OVERRIDE { + ADD_FAILURE(); + return NULL; + } + + virtual CFDictionaryRef CopyJobDictionary(CFStringRef label) OVERRIDE { + ADD_FAILURE(); + return NULL; + } + + virtual CFDictionaryRef CopyDictionaryByCheckingIn(CFErrorRef* error) + OVERRIDE { + checkin_called_ = true; + CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM); + CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS); + const void *keys[] = { program, program_args }; + base::mac::ScopedCFTypeRef<CFStringRef> path( + base::SysUTF8ToCFStringRef(file_.value())); + const void *array_values[] = { path.get() }; + base::mac::ScopedCFTypeRef<CFArrayRef> args( + CFArrayCreate(kCFAllocatorDefault, + array_values, + 1, + &kCFTypeArrayCallBacks)); + const void *values[] = { path, args }; + return CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + arraysize(keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } + + virtual bool RemoveJob(CFStringRef label, CFErrorRef* error) OVERRIDE { + remove_called_ = true; + message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); + return true; + } + + virtual bool RestartJob(Domain domain, + Type type, + CFStringRef name, + CFStringRef session_type) OVERRIDE { + restart_called_ = true; + message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); + return true; + } + + virtual CFMutableDictionaryRef CreatePlistFromFile( + Domain domain, + Type type, + CFStringRef name) OVERRIDE { + base::mac::ScopedCFTypeRef<CFDictionaryRef> dict( + CopyDictionaryByCheckingIn(NULL)); + return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict); + } + + virtual bool WritePlistToFile(Domain domain, + Type type, + CFStringRef name, + CFDictionaryRef dict) OVERRIDE { + write_called_ = true; + return true; + } + + virtual bool DeletePlist(Domain domain, + Type type, + CFStringRef name) OVERRIDE { + delete_called_ = true; + return true; + } + + bool restart_called() const { return restart_called_; } + bool remove_called() const { return remove_called_; } + bool checkin_called() const { return checkin_called_; } + bool write_called() const { return write_called_; } + bool delete_called() const { return delete_called_; } + + private: + FilePath file_; + MessageLoop* message_loop_; + bool restart_called_; + bool remove_called_; + bool checkin_called_; + bool write_called_; + bool delete_called_; +}; + +class ServiceProcessStateFileManipulationTest : public ::testing::Test { + protected: + ServiceProcessStateFileManipulationTest() + : io_thread_("ServiceProcessStateFileManipulationTest_IO") { + } + virtual ~ServiceProcessStateFileManipulationTest() { } + + virtual void SetUp() { + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(io_thread_.StartWithOptions(options)); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(MakeABundle(GetTempDirPath(), + "Test", + &bundle_path_, + &executable_path_)); + mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_)); + 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(), + NULL)); + loop_.PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask, + TestTimeouts::large_test_timeout_ms()); + } + + bool MakeABundle(const FilePath& dst, + const std::string& name, + FilePath* bundle_root, + FilePath* executable) { + *bundle_root = dst.Append(name + std::string(".app")); + FilePath contents = bundle_root->AppendASCII("Contents"); + FilePath mac_os = contents.AppendASCII("MacOS"); + *executable = mac_os.Append(name); + FilePath info_plist = contents.Append("Info.plist"); + + if (!file_util::CreateDirectory(mac_os)) { + return false; + } + const char *data = "#! testbundle\n"; + int len = strlen(data); + if (file_util::WriteFile(*executable, data, len) != len) { + return false; + } + if (chmod(executable->value().c_str(), 0555) != 0) { + return false; + } + + const char* info_plist_format = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + "<dict>\n" + " <key>CFBundleDevelopmentRegion</key>\n" + " <string>English</string>\n" + " <key>CFBundleIdentifier</key>\n" + " <string>com.test.%s</string>\n" + " <key>CFBundleInfoDictionaryVersion</key>\n" + " <string>6.0</string>\n" + " <key>CFBundleExecutable</key>\n" + " <string>%s</string>\n" + " <key>CFBundleVersion</key>\n" + " <string>1</string>\n" + "</dict>\n" + "</plist>\n"; + std::string info_plist_data = base::StringPrintf(info_plist_format, + name.c_str(), + name.c_str()); + len = info_plist_data.length(); + if (file_util::WriteFile(info_plist, info_plist_data.c_str(), len) != len) { + return false; + } + const UInt8* bundle_root_path = + reinterpret_cast<const UInt8*>(bundle_root->value().c_str()); + base::mac::ScopedCFTypeRef<CFURLRef> url( + CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + bundle_root_path, + bundle_root->value().length(), + true)); + base::mac::ScopedCFTypeRef<CFBundleRef> bundle( + CFBundleCreate(kCFAllocatorDefault, url)); + return bundle.get(); + } + + const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); } + const FilePath& executable_path() const { return executable_path_; } + const FilePath& bundle_path() const { return bundle_path_; } + const FilePath& GetTempDirPath() const { return temp_dir_.path(); } + + base::MessageLoopProxy* GetIOMessageLoopProxy() { + return io_thread_.message_loop_proxy().get(); + } + void Run() { loop_.Run(); } + + private: + ScopedTempDir temp_dir_; + MessageLoopForUI loop_; + base::Thread io_thread_; + FilePath executable_path_, bundle_path_; + scoped_ptr<MockLaunchd> mock_launchd_; + scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_; + ServiceProcessState service_process_state_; +}; + +void DeleteFunc(const FilePath& file) { + EXPECT_TRUE(file_util::Delete(file, true)); +} + +void MoveFunc(const FilePath& from, const FilePath& to) { + EXPECT_TRUE(file_util::Move(from, to)); +} + +void ChangeAttr(const FilePath& from, int mode) { + EXPECT_EQ(chmod(from.value().c_str(), mode), 0); +} + +class ScopedAttributesRestorer { + public: + ScopedAttributesRestorer(const FilePath& path, int mode) + : path_(path), mode_(mode) { + } + ~ScopedAttributesRestorer() { + ChangeAttr(path_, mode_); + } + private: + FilePath path_; + int mode_; +}; + +void TrashFunc(const 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, DeleteFile) { + GetIOMessageLoopProxy()->PostTask( + FROM_HERE, + NewRunnableFunction(&DeleteFunc, executable_path())); + Run(); + ASSERT_TRUE(mock_launchd()->remove_called()); + ASSERT_TRUE(mock_launchd()->delete_called()); +} + +TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) { + GetIOMessageLoopProxy()->PostTask( + FROM_HERE, + NewRunnableFunction(&DeleteFunc, bundle_path())); + Run(); + ASSERT_TRUE(mock_launchd()->remove_called()); + ASSERT_TRUE(mock_launchd()->delete_called()); +} + +TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) { + FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle"); + GetIOMessageLoopProxy()->PostTask( + FROM_HERE, + NewRunnableFunction(&MoveFunc, bundle_path(), new_loc)); + Run(); + ASSERT_TRUE(mock_launchd()->restart_called()); + ASSERT_TRUE(mock_launchd()->write_called()); +} + +TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) { + FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile"); + GetIOMessageLoopProxy()->PostTask( + FROM_HERE, + NewRunnableFunction(&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, + NewRunnableFunction(&TrashFunc, bundle_path())); + Run(); + ASSERT_TRUE(mock_launchd()->remove_called()); + ASSERT_TRUE(mock_launchd()->delete_called()); + std::string path(base::mac::PathFromFSRef(bundle_ref)); + FilePath file_path(path); + ASSERT_TRUE(file_util::Delete(file_path, true)); +} + +TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) { + ScopedAttributesRestorer restorer(bundle_path(), 0777); + GetIOMessageLoopProxy()->PostTask( + FROM_HERE, + NewRunnableFunction(&ChangeAttr, bundle_path(), 0222)); + Run(); + ASSERT_TRUE(mock_launchd()->remove_called()); + ASSERT_TRUE(mock_launchd()->delete_called()); +} + #endif // !OS_MACOSX |