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