• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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.
4 
5 #include "chrome/common/service_process_util.h"
6 
7 #include "base/basictypes.h"
8 
9 #if !defined(OS_MACOSX)
10 #include "base/at_exit.h"
11 #include "base/command_line.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/process_util.h"
14 #include "base/string_util.h"
15 #include "base/test/multiprocess_test.h"
16 #include "base/test/test_timeouts.h"
17 #include "base/threading/thread.h"
18 #include "base/utf_string_conversions.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/chrome_version_info.h"
21 #include "testing/multiprocess_func_list.h"
22 
23 #if defined(OS_WIN)
24 #include "base/win/win_util.h"
25 #endif
26 
27 #if defined(OS_LINUX)
28 #include <glib.h>
29 #include "chrome/common/auto_start_linux.h"
30 #endif
31 
32 namespace {
33 
34 bool g_good_shutdown = false;
35 
ShutdownTask(MessageLoop * loop)36 void ShutdownTask(MessageLoop* loop) {
37   // Quit the main message loop.
38   ASSERT_FALSE(g_good_shutdown);
39   g_good_shutdown = true;
40   loop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
41 }
42 
43 }  // namespace
44 
TEST(ServiceProcessUtilTest,ScopedVersionedName)45 TEST(ServiceProcessUtilTest, ScopedVersionedName) {
46   std::string test_str = "test";
47   std::string scoped_name = GetServiceProcessScopedVersionedName(test_str);
48   chrome::VersionInfo version_info;
49   DCHECK(version_info.is_valid());
50   EXPECT_TRUE(EndsWith(scoped_name, test_str, true));
51   EXPECT_NE(std::string::npos, scoped_name.find(version_info.Version()));
52 }
53 
54 class ServiceProcessStateTest : public base::MultiProcessTest {
55  public:
56   ServiceProcessStateTest();
57   ~ServiceProcessStateTest();
58   virtual void SetUp();
IOMessageLoopProxy()59   base::MessageLoopProxy* IOMessageLoopProxy() {
60     return io_thread_.message_loop_proxy();
61   }
62   void LaunchAndWait(const std::string& name);
63 
64  private:
65   // This is used to release the ServiceProcessState singleton after each test.
66   base::ShadowingAtExitManager at_exit_manager_;
67   base::Thread io_thread_;
68 };
69 
ServiceProcessStateTest()70 ServiceProcessStateTest::ServiceProcessStateTest()
71     : io_thread_("ServiceProcessStateTestThread") {
72 }
73 
~ServiceProcessStateTest()74 ServiceProcessStateTest::~ServiceProcessStateTest() {
75 }
76 
SetUp()77 void ServiceProcessStateTest::SetUp() {
78   base::Thread::Options options(MessageLoop::TYPE_IO, 0);
79   ASSERT_TRUE(io_thread_.StartWithOptions(options));
80 }
81 
LaunchAndWait(const std::string & name)82 void ServiceProcessStateTest::LaunchAndWait(const std::string& name) {
83   base::ProcessHandle handle = SpawnChild(name, false);
84   ASSERT_TRUE(handle);
85   int exit_code = 0;
86   ASSERT_TRUE(base::WaitForExitCode(handle, &exit_code));
87   ASSERT_EQ(exit_code, 0);
88 }
89 
TEST_F(ServiceProcessStateTest,Singleton)90 TEST_F(ServiceProcessStateTest, Singleton) {
91   ServiceProcessState state;
92   ASSERT_TRUE(state.Initialize());
93   LaunchAndWait("ServiceProcessStateTestSingleton");
94 }
95 
TEST_F(ServiceProcessStateTest,ReadyState)96 TEST_F(ServiceProcessStateTest, ReadyState) {
97   ASSERT_FALSE(CheckServiceProcessReady());
98   ServiceProcessState state;
99   ASSERT_TRUE(state.Initialize());
100   ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), NULL));
101   LaunchAndWait("ServiceProcessStateTestReadyTrue");
102   state.SignalStopped();
103   LaunchAndWait("ServiceProcessStateTestReadyFalse");
104 }
105 
TEST_F(ServiceProcessStateTest,AutoRun)106 TEST_F(ServiceProcessStateTest, AutoRun) {
107   ServiceProcessState state;
108   ASSERT_TRUE(state.AddToAutoRun());
109   scoped_ptr<CommandLine> autorun_command_line;
110 #if defined(OS_WIN)
111   std::string value_name = GetServiceProcessScopedName("_service_run");
112   string16 value;
113   EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
114                                                 UTF8ToWide(value_name),
115                                                 &value));
116   autorun_command_line.reset(new CommandLine(CommandLine::FromString(value)));
117 #elif defined(OS_LINUX)
118 #if defined(GOOGLE_CHROME_BUILD)
119   std::string base_desktop_name = "google-chrome-service.desktop";
120 #else  // CHROMIUM_BUILD
121   std::string base_desktop_name = "chromium-service.desktop";
122 #endif
123   std::string exec_value;
124   EXPECT_TRUE(AutoStart::GetAutostartFileValue(
125       GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
126   GError *error = NULL;
127   gchar **argv = NULL;
128   gint argc = 0;
129   if (g_shell_parse_argv(exec_value.c_str(), &argc, &argv, &error)) {
130     autorun_command_line.reset(new CommandLine(argc, argv));
131     g_strfreev(argv);
132   } else {
133     ADD_FAILURE();
134     g_error_free(error);
135   }
136 #endif  // defined(OS_WIN)
137   if (autorun_command_line.get()) {
138     EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType),
139               std::string(switches::kServiceProcess));
140   }
141   ASSERT_TRUE(state.RemoveFromAutoRun());
142 #if defined(OS_WIN)
143   EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
144                                                  UTF8ToWide(value_name),
145                                                  &value));
146 #elif defined(OS_LINUX)
147   EXPECT_FALSE(AutoStart::GetAutostartFileValue(
148       GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
149 #endif  // defined(OS_WIN)
150 }
151 
TEST_F(ServiceProcessStateTest,SharedMem)152 TEST_F(ServiceProcessStateTest, SharedMem) {
153   std::string version;
154   base::ProcessId pid;
155 #if defined(OS_WIN)
156   // On Posix, named shared memory uses a file on disk. This file
157   // could be lying around from previous crashes which could cause
158   // GetServiceProcessPid to lie. On Windows, we use a named event so we
159   // don't have this issue. Until we have a more stable shared memory
160   // implementation on Posix, this check will only execute on Windows.
161   ASSERT_FALSE(GetServiceProcessData(&version, &pid));
162 #endif  // defined(OS_WIN)
163   ServiceProcessState state;
164   ASSERT_TRUE(state.Initialize());
165   ASSERT_TRUE(GetServiceProcessData(&version, &pid));
166   ASSERT_EQ(base::GetCurrentProcId(), pid);
167 }
168 
TEST_F(ServiceProcessStateTest,ForceShutdown)169 TEST_F(ServiceProcessStateTest, ForceShutdown) {
170   base::ProcessHandle handle = SpawnChild("ServiceProcessStateTestShutdown",
171                                           true);
172   ASSERT_TRUE(handle);
173   for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) {
174     base::PlatformThread::Sleep(TestTimeouts::tiny_timeout_ms());
175   }
176   ASSERT_TRUE(CheckServiceProcessReady());
177   std::string version;
178   base::ProcessId pid;
179   ASSERT_TRUE(GetServiceProcessData(&version, &pid));
180   ASSERT_TRUE(ForceServiceProcessShutdown(version, pid));
181   int exit_code = 0;
182   ASSERT_TRUE(base::WaitForExitCodeWithTimeout(handle,
183       &exit_code, TestTimeouts::action_max_timeout_ms()));
184   base::CloseProcessHandle(handle);
185   ASSERT_EQ(exit_code, 0);
186 }
187 
MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton)188 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
189   ServiceProcessState state;
190   EXPECT_FALSE(state.Initialize());
191   return 0;
192 }
193 
MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue)194 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
195   EXPECT_TRUE(CheckServiceProcessReady());
196   return 0;
197 }
198 
MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse)199 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
200   EXPECT_FALSE(CheckServiceProcessReady());
201   return 0;
202 }
203 
MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown)204 MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) {
205   MessageLoop message_loop;
206   message_loop.set_thread_name("ServiceProcessStateTestShutdownMainThread");
207   base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread");
208   base::Thread::Options options(MessageLoop::TYPE_IO, 0);
209   EXPECT_TRUE(io_thread_.StartWithOptions(options));
210   ServiceProcessState state;
211   EXPECT_TRUE(state.Initialize());
212   EXPECT_TRUE(state.SignalReady(io_thread_.message_loop_proxy(),
213                                 NewRunnableFunction(&ShutdownTask,
214                                                     MessageLoop::current())));
215   message_loop.PostDelayedTask(FROM_HERE,
216                                new MessageLoop::QuitTask(),
217                                TestTimeouts::action_max_timeout_ms());
218   EXPECT_FALSE(g_good_shutdown);
219   message_loop.Run();
220   EXPECT_TRUE(g_good_shutdown);
221   return 0;
222 }
223 
224 #else  // !OS_MACOSX
225 
226 #include <CoreFoundation/CoreFoundation.h>
227 
228 #include <launch.h>
229 #include <sys/stat.h>
230 
231 #include "base/file_path.h"
232 #include "base/file_util.h"
233 #include "base/mac/mac_util.h"
234 #include "base/mac/scoped_cftyperef.h"
235 #include "base/memory/scoped_temp_dir.h"
236 #include "base/message_loop.h"
237 #include "base/stringprintf.h"
238 #include "base/sys_string_conversions.h"
239 #include "base/test/test_timeouts.h"
240 #include "base/threading/thread.h"
241 #include "chrome/common/launchd_mac.h"
242 #include "testing/gtest/include/gtest/gtest.h"
243 
244 // TODO(dmaclach): Write this in terms of a real mock.
245 // http://crbug.com/76923
246 class MockLaunchd : public Launchd {
247  public:
MockLaunchd(const FilePath & file,MessageLoop * loop)248   MockLaunchd(const FilePath& file, MessageLoop* loop)
249       : file_(file),
250         message_loop_(loop),
251         restart_called_(false),
252         remove_called_(false),
253         checkin_called_(false),
254         write_called_(false),
255         delete_called_(false) {
256   }
~MockLaunchd()257   virtual ~MockLaunchd() { }
258 
CopyExports()259   virtual CFDictionaryRef CopyExports() OVERRIDE {
260     ADD_FAILURE();
261     return NULL;
262   }
263 
CopyJobDictionary(CFStringRef label)264   virtual CFDictionaryRef CopyJobDictionary(CFStringRef label) OVERRIDE {
265     ADD_FAILURE();
266     return NULL;
267   }
268 
CopyDictionaryByCheckingIn(CFErrorRef * error)269   virtual CFDictionaryRef CopyDictionaryByCheckingIn(CFErrorRef* error)
270       OVERRIDE {
271     checkin_called_ = true;
272     CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM);
273     CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS);
274     const void *keys[] = { program, program_args };
275     base::mac::ScopedCFTypeRef<CFStringRef> path(
276         base::SysUTF8ToCFStringRef(file_.value()));
277     const void *array_values[] = { path.get() };
278     base::mac::ScopedCFTypeRef<CFArrayRef> args(
279         CFArrayCreate(kCFAllocatorDefault,
280                       array_values,
281                       1,
282                       &kCFTypeArrayCallBacks));
283     const void *values[] = { path, args };
284     return CFDictionaryCreate(kCFAllocatorDefault,
285                               keys,
286                               values,
287                               arraysize(keys),
288                               &kCFTypeDictionaryKeyCallBacks,
289                               &kCFTypeDictionaryValueCallBacks);
290   }
291 
RemoveJob(CFStringRef label,CFErrorRef * error)292   virtual bool RemoveJob(CFStringRef label, CFErrorRef* error) OVERRIDE {
293     remove_called_ = true;
294     message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
295     return true;
296   }
297 
RestartJob(Domain domain,Type type,CFStringRef name,CFStringRef session_type)298   virtual bool RestartJob(Domain domain,
299                           Type type,
300                           CFStringRef name,
301                           CFStringRef session_type) OVERRIDE {
302     restart_called_ = true;
303     message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
304     return true;
305   }
306 
CreatePlistFromFile(Domain domain,Type type,CFStringRef name)307   virtual CFMutableDictionaryRef CreatePlistFromFile(
308       Domain domain,
309       Type type,
310       CFStringRef name) OVERRIDE {
311     base::mac::ScopedCFTypeRef<CFDictionaryRef> dict(
312         CopyDictionaryByCheckingIn(NULL));
313     return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
314   }
315 
WritePlistToFile(Domain domain,Type type,CFStringRef name,CFDictionaryRef dict)316   virtual bool WritePlistToFile(Domain domain,
317                                 Type type,
318                                 CFStringRef name,
319                                 CFDictionaryRef dict) OVERRIDE {
320     write_called_ = true;
321     return true;
322   }
323 
DeletePlist(Domain domain,Type type,CFStringRef name)324   virtual bool DeletePlist(Domain domain,
325                            Type type,
326                            CFStringRef name) OVERRIDE {
327     delete_called_ = true;
328     return true;
329   }
330 
restart_called() const331   bool restart_called() const { return restart_called_; }
remove_called() const332   bool remove_called() const { return remove_called_; }
checkin_called() const333   bool checkin_called() const { return checkin_called_; }
write_called() const334   bool write_called() const { return write_called_; }
delete_called() const335   bool delete_called() const { return delete_called_; }
336 
337  private:
338   FilePath file_;
339   MessageLoop* message_loop_;
340   bool restart_called_;
341   bool remove_called_;
342   bool checkin_called_;
343   bool write_called_;
344   bool delete_called_;
345 };
346 
347 class ServiceProcessStateFileManipulationTest : public ::testing::Test {
348  protected:
ServiceProcessStateFileManipulationTest()349   ServiceProcessStateFileManipulationTest()
350       : io_thread_("ServiceProcessStateFileManipulationTest_IO") {
351   }
~ServiceProcessStateFileManipulationTest()352   virtual ~ServiceProcessStateFileManipulationTest() { }
353 
SetUp()354   virtual void SetUp() {
355     base::Thread::Options options;
356     options.message_loop_type = MessageLoop::TYPE_IO;
357     ASSERT_TRUE(io_thread_.StartWithOptions(options));
358     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
359     ASSERT_TRUE(MakeABundle(GetTempDirPath(),
360                             "Test",
361                             &bundle_path_,
362                             &executable_path_));
363     mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_));
364     scoped_launchd_instance_.reset(
365         new Launchd::ScopedInstance(mock_launchd_.get()));
366     ASSERT_TRUE(service_process_state_.Initialize());
367     ASSERT_TRUE(service_process_state_.SignalReady(
368         io_thread_.message_loop_proxy(),
369         NULL));
370     loop_.PostDelayedTask(FROM_HERE,
371                           new MessageLoop::QuitTask,
372                           TestTimeouts::action_max_timeout_ms());
373   }
374 
MakeABundle(const FilePath & dst,const std::string & name,FilePath * bundle_root,FilePath * executable)375   bool MakeABundle(const FilePath& dst,
376                    const std::string& name,
377                    FilePath* bundle_root,
378                    FilePath* executable) {
379     *bundle_root = dst.Append(name + std::string(".app"));
380     FilePath contents = bundle_root->AppendASCII("Contents");
381     FilePath mac_os = contents.AppendASCII("MacOS");
382     *executable = mac_os.Append(name);
383     FilePath info_plist = contents.Append("Info.plist");
384 
385     if (!file_util::CreateDirectory(mac_os)) {
386       return false;
387     }
388     const char *data = "#! testbundle\n";
389     int len = strlen(data);
390     if (file_util::WriteFile(*executable, data, len) != len) {
391       return false;
392     }
393     if (chmod(executable->value().c_str(), 0555) != 0) {
394       return false;
395     }
396 
397     const char* info_plist_format =
398       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
399       "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
400       "<plist version=\"1.0\">\n"
401       "<dict>\n"
402       "  <key>CFBundleDevelopmentRegion</key>\n"
403       "  <string>English</string>\n"
404       "  <key>CFBundleIdentifier</key>\n"
405       "  <string>com.test.%s</string>\n"
406       "  <key>CFBundleInfoDictionaryVersion</key>\n"
407       "  <string>6.0</string>\n"
408       "  <key>CFBundleExecutable</key>\n"
409       "  <string>%s</string>\n"
410       "  <key>CFBundleVersion</key>\n"
411       "  <string>1</string>\n"
412       "</dict>\n"
413       "</plist>\n";
414     std::string info_plist_data = base::StringPrintf(info_plist_format,
415                                                      name.c_str(),
416                                                      name.c_str());
417     len = info_plist_data.length();
418     if (file_util::WriteFile(info_plist, info_plist_data.c_str(), len) != len) {
419       return false;
420     }
421     const UInt8* bundle_root_path =
422         reinterpret_cast<const UInt8*>(bundle_root->value().c_str());
423     base::mac::ScopedCFTypeRef<CFURLRef> url(
424       CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
425                                               bundle_root_path,
426                                               bundle_root->value().length(),
427                                               true));
428     base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
429         CFBundleCreate(kCFAllocatorDefault, url));
430     return bundle.get();
431   }
432 
mock_launchd() const433   const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); }
executable_path() const434   const FilePath& executable_path() const { return executable_path_; }
bundle_path() const435   const FilePath& bundle_path() const { return bundle_path_; }
GetTempDirPath() const436   const FilePath& GetTempDirPath() const { return temp_dir_.path(); }
437 
GetIOMessageLoopProxy()438   base::MessageLoopProxy* GetIOMessageLoopProxy() {
439     return io_thread_.message_loop_proxy().get();
440   }
Run()441   void Run() { loop_.Run(); }
442 
443  private:
444   ScopedTempDir temp_dir_;
445   MessageLoopForUI loop_;
446   base::Thread io_thread_;
447   FilePath executable_path_, bundle_path_;
448   scoped_ptr<MockLaunchd> mock_launchd_;
449   scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
450   ServiceProcessState service_process_state_;
451 };
452 
DeleteFunc(const FilePath & file)453 void DeleteFunc(const FilePath& file) {
454   EXPECT_TRUE(file_util::Delete(file, true));
455 }
456 
MoveFunc(const FilePath & from,const FilePath & to)457 void MoveFunc(const FilePath& from, const FilePath& to) {
458   EXPECT_TRUE(file_util::Move(from, to));
459 }
460 
ChangeAttr(const FilePath & from,int mode)461 void ChangeAttr(const FilePath& from, int mode) {
462   EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
463 }
464 
465 class ScopedAttributesRestorer {
466  public:
ScopedAttributesRestorer(const FilePath & path,int mode)467   ScopedAttributesRestorer(const FilePath& path, int mode)
468       : path_(path), mode_(mode) {
469   }
~ScopedAttributesRestorer()470   ~ScopedAttributesRestorer() {
471     ChangeAttr(path_, mode_);
472   }
473  private:
474   FilePath path_;
475   int mode_;
476 };
477 
TrashFunc(const FilePath & src)478 void TrashFunc(const FilePath& src) {
479   FSRef path_ref;
480   FSRef new_path_ref;
481   EXPECT_TRUE(base::mac::FSRefFromPath(src.value(), &path_ref));
482   OSStatus status = FSMoveObjectToTrashSync(&path_ref,
483                                             &new_path_ref,
484                                             kFSFileOperationDefaultOptions);
485   EXPECT_EQ(status, noErr)  << "FSMoveObjectToTrashSync " << status;
486 }
487 
TEST_F(ServiceProcessStateFileManipulationTest,DeleteFile)488 TEST_F(ServiceProcessStateFileManipulationTest, DeleteFile) {
489   GetIOMessageLoopProxy()->PostTask(
490       FROM_HERE,
491       NewRunnableFunction(&DeleteFunc, executable_path()));
492   Run();
493   ASSERT_TRUE(mock_launchd()->remove_called());
494   ASSERT_TRUE(mock_launchd()->delete_called());
495 }
496 
TEST_F(ServiceProcessStateFileManipulationTest,DeleteBundle)497 TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) {
498   GetIOMessageLoopProxy()->PostTask(
499       FROM_HERE,
500       NewRunnableFunction(&DeleteFunc, bundle_path()));
501   Run();
502   ASSERT_TRUE(mock_launchd()->remove_called());
503   ASSERT_TRUE(mock_launchd()->delete_called());
504 }
505 
TEST_F(ServiceProcessStateFileManipulationTest,MoveBundle)506 TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) {
507   FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle");
508   GetIOMessageLoopProxy()->PostTask(
509       FROM_HERE,
510       NewRunnableFunction(&MoveFunc, bundle_path(), new_loc));
511   Run();
512   ASSERT_TRUE(mock_launchd()->restart_called());
513   ASSERT_TRUE(mock_launchd()->write_called());
514 }
515 
TEST_F(ServiceProcessStateFileManipulationTest,MoveFile)516 TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) {
517   FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile");
518   GetIOMessageLoopProxy()->PostTask(
519       FROM_HERE,
520       NewRunnableFunction(&MoveFunc, executable_path(), new_loc));
521   Run();
522   ASSERT_TRUE(mock_launchd()->remove_called());
523   ASSERT_TRUE(mock_launchd()->delete_called());
524 }
525 
TEST_F(ServiceProcessStateFileManipulationTest,TrashBundle)526 TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) {
527   FSRef bundle_ref;
528   ASSERT_TRUE(base::mac::FSRefFromPath(bundle_path().value(), &bundle_ref));
529   GetIOMessageLoopProxy()->PostTask(
530       FROM_HERE,
531       NewRunnableFunction(&TrashFunc, bundle_path()));
532   Run();
533   ASSERT_TRUE(mock_launchd()->remove_called());
534   ASSERT_TRUE(mock_launchd()->delete_called());
535   std::string path(base::mac::PathFromFSRef(bundle_ref));
536   FilePath file_path(path);
537   ASSERT_TRUE(file_util::Delete(file_path, true));
538 }
539 
TEST_F(ServiceProcessStateFileManipulationTest,ChangeAttr)540 TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) {
541   ScopedAttributesRestorer restorer(bundle_path(), 0777);
542   GetIOMessageLoopProxy()->PostTask(
543       FROM_HERE,
544       NewRunnableFunction(&ChangeAttr, bundle_path(), 0222));
545   Run();
546   ASSERT_TRUE(mock_launchd()->remove_called());
547   ASSERT_TRUE(mock_launchd()->delete_called());
548 }
549 
550 #endif  // !OS_MACOSX
551