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 "base/files/file_path_watcher.h" 6 7 #include <set> 8 9 #if defined(OS_WIN) 10 #include <windows.h> 11 #include <aclapi.h> 12 #elif defined(OS_POSIX) 13 #include <sys/stat.h> 14 #endif 15 16 #include "base/basictypes.h" 17 #include "base/compiler_specific.h" 18 #include "base/file_path.h" 19 #include "base/file_util.h" 20 #include "base/memory/scoped_temp_dir.h" 21 #include "base/message_loop.h" 22 #include "base/message_loop_proxy.h" 23 #include "base/path_service.h" 24 #include "base/string_util.h" 25 #include "base/stl_util-inl.h" 26 #include "base/synchronization/waitable_event.h" 27 #include "base/test/test_timeouts.h" 28 #include "base/threading/thread.h" 29 #include "testing/gtest/include/gtest/gtest.h" 30 31 namespace base { 32 namespace files { 33 34 namespace { 35 36 class TestDelegate; 37 38 // Aggregates notifications from the test delegates and breaks the message loop 39 // the test thread is waiting on once they all came in. 40 class NotificationCollector 41 : public base::RefCountedThreadSafe<NotificationCollector> { 42 public: 43 NotificationCollector() 44 : loop_(base::MessageLoopProxy::CreateForCurrentThread()) {} 45 46 // Called from the file thread by the delegates. 47 void OnChange(TestDelegate* delegate) { 48 loop_->PostTask(FROM_HERE, 49 NewRunnableMethod(this, 50 &NotificationCollector::RecordChange, 51 make_scoped_refptr(delegate))); 52 } 53 54 void Register(TestDelegate* delegate) { 55 delegates_.insert(delegate); 56 } 57 58 void Reset() { 59 signaled_.clear(); 60 } 61 62 bool Success() { 63 return signaled_ == delegates_; 64 } 65 66 private: 67 void RecordChange(TestDelegate* delegate) { 68 ASSERT_TRUE(loop_->BelongsToCurrentThread()); 69 ASSERT_TRUE(delegates_.count(delegate)); 70 signaled_.insert(delegate); 71 72 // Check whether all delegates have been signaled. 73 if (signaled_ == delegates_) 74 loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); 75 } 76 77 // Set of registered delegates. 78 std::set<TestDelegate*> delegates_; 79 80 // Set of signaled delegates. 81 std::set<TestDelegate*> signaled_; 82 83 // The loop we should break after all delegates signaled. 84 scoped_refptr<base::MessageLoopProxy> loop_; 85 }; 86 87 // A mock FilePathWatcher::Delegate for testing. I'd rather use gmock, but it's 88 // not thread safe for setting expectations, so the test code couldn't safely 89 // reset expectations while the file watcher is running. In order to allow this, 90 // we keep simple thread safe status flags in TestDelegate. 91 class TestDelegate : public FilePathWatcher::Delegate { 92 public: 93 // The message loop specified by |loop| will be quit if a notification is 94 // received while the delegate is |armed_|. Note that the testing code must 95 // guarantee |loop| outlives the file thread on which OnFilePathChanged runs. 96 explicit TestDelegate(NotificationCollector* collector) 97 : collector_(collector) { 98 collector_->Register(this); 99 } 100 101 virtual void OnFilePathChanged(const FilePath&) { 102 collector_->OnChange(this); 103 } 104 105 virtual void OnFilePathError(const FilePath& path) { 106 ADD_FAILURE() << "Error " << path.value(); 107 } 108 109 private: 110 scoped_refptr<NotificationCollector> collector_; 111 112 DISALLOW_COPY_AND_ASSIGN(TestDelegate); 113 }; 114 115 // A helper class for setting up watches on the file thread. 116 class SetupWatchTask : public Task { 117 public: 118 SetupWatchTask(const FilePath& target, 119 FilePathWatcher* watcher, 120 FilePathWatcher::Delegate* delegate, 121 bool* result, 122 base::WaitableEvent* completion) 123 : target_(target), 124 watcher_(watcher), 125 delegate_(delegate), 126 result_(result), 127 completion_(completion) {} 128 129 void Run() { 130 *result_ = watcher_->Watch(target_, delegate_); 131 completion_->Signal(); 132 } 133 134 private: 135 const FilePath target_; 136 FilePathWatcher* watcher_; 137 FilePathWatcher::Delegate* delegate_; 138 bool* result_; 139 base::WaitableEvent* completion_; 140 141 DISALLOW_COPY_AND_ASSIGN(SetupWatchTask); 142 }; 143 144 class FilePathWatcherTest : public testing::Test { 145 public: 146 FilePathWatcherTest() 147 : file_thread_("FilePathWatcherTest") {} 148 149 virtual ~FilePathWatcherTest() {} 150 151 protected: 152 virtual void SetUp() { 153 // Create a separate file thread in order to test proper thread usage. 154 base::Thread::Options options(MessageLoop::TYPE_IO, 0); 155 ASSERT_TRUE(file_thread_.StartWithOptions(options)); 156 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 157 collector_ = new NotificationCollector(); 158 } 159 160 virtual void TearDown() { 161 loop_.RunAllPending(); 162 } 163 164 FilePath test_file() { 165 return temp_dir_.path().AppendASCII("FilePathWatcherTest"); 166 } 167 168 // Write |content| to |file|. Returns true on success. 169 bool WriteFile(const FilePath& file, const std::string& content) { 170 int write_size = file_util::WriteFile(file, content.c_str(), 171 content.length()); 172 return write_size == static_cast<int>(content.length()); 173 } 174 175 bool SetupWatch(const FilePath& target, 176 FilePathWatcher* watcher, 177 FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT { 178 base::WaitableEvent completion(false, false); 179 bool result; 180 file_thread_.message_loop_proxy()->PostTask(FROM_HERE, 181 new SetupWatchTask(target, 182 watcher, 183 delegate, 184 &result, 185 &completion)); 186 completion.Wait(); 187 return result; 188 } 189 190 bool WaitForEvents() WARN_UNUSED_RESULT { 191 collector_->Reset(); 192 loop_.Run(); 193 return collector_->Success(); 194 } 195 196 NotificationCollector* collector() { return collector_.get(); } 197 198 MessageLoop loop_; 199 base::Thread file_thread_; 200 ScopedTempDir temp_dir_; 201 scoped_refptr<NotificationCollector> collector_; 202 }; 203 204 // Basic test: Create the file and verify that we notice. 205 TEST_F(FilePathWatcherTest, NewFile) { 206 FilePathWatcher watcher; 207 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 208 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 209 210 ASSERT_TRUE(WriteFile(test_file(), "content")); 211 ASSERT_TRUE(WaitForEvents()); 212 } 213 214 // Verify that modifying the file is caught. 215 TEST_F(FilePathWatcherTest, ModifiedFile) { 216 ASSERT_TRUE(WriteFile(test_file(), "content")); 217 218 FilePathWatcher watcher; 219 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 220 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 221 222 // Now make sure we get notified if the file is modified. 223 ASSERT_TRUE(WriteFile(test_file(), "new content")); 224 ASSERT_TRUE(WaitForEvents()); 225 } 226 227 // Verify that moving the file into place is caught. 228 TEST_F(FilePathWatcherTest, MovedFile) { 229 FilePath source_file(temp_dir_.path().AppendASCII("source")); 230 ASSERT_TRUE(WriteFile(source_file, "content")); 231 232 FilePathWatcher watcher; 233 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 234 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 235 236 // Now make sure we get notified if the file is modified. 237 ASSERT_TRUE(file_util::Move(source_file, test_file())); 238 ASSERT_TRUE(WaitForEvents()); 239 } 240 241 TEST_F(FilePathWatcherTest, DeletedFile) { 242 ASSERT_TRUE(WriteFile(test_file(), "content")); 243 244 FilePathWatcher watcher; 245 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 246 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 247 248 // Now make sure we get notified if the file is deleted. 249 file_util::Delete(test_file(), false); 250 ASSERT_TRUE(WaitForEvents()); 251 } 252 253 // Used by the DeleteDuringNotify test below. 254 // Deletes the FilePathWatcher when it's notified. 255 class Deleter : public FilePathWatcher::Delegate { 256 public: 257 Deleter(FilePathWatcher* watcher, MessageLoop* loop) 258 : watcher_(watcher), 259 loop_(loop) { 260 } 261 262 virtual void OnFilePathChanged(const FilePath& path) { 263 watcher_.reset(); 264 loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); 265 } 266 267 scoped_ptr<FilePathWatcher> watcher_; 268 MessageLoop* loop_; 269 }; 270 271 // Verify that deleting a watcher during the callback doesn't crash. 272 TEST_F(FilePathWatcherTest, DeleteDuringNotify) { 273 FilePathWatcher* watcher = new FilePathWatcher; 274 // Takes ownership of watcher. 275 scoped_refptr<Deleter> deleter(new Deleter(watcher, &loop_)); 276 ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get())); 277 278 ASSERT_TRUE(WriteFile(test_file(), "content")); 279 ASSERT_TRUE(WaitForEvents()); 280 281 // We win if we haven't crashed yet. 282 // Might as well double-check it got deleted, too. 283 ASSERT_TRUE(deleter->watcher_.get() == NULL); 284 } 285 286 // Verify that deleting the watcher works even if there is a pending 287 // notification. 288 TEST_F(FilePathWatcherTest, DestroyWithPendingNotification) { 289 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 290 FilePathWatcher* watcher = new FilePathWatcher; 291 ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get())); 292 ASSERT_TRUE(WriteFile(test_file(), "content")); 293 file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher); 294 } 295 296 TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) { 297 FilePathWatcher watcher1, watcher2; 298 scoped_refptr<TestDelegate> delegate1(new TestDelegate(collector())); 299 scoped_refptr<TestDelegate> delegate2(new TestDelegate(collector())); 300 ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get())); 301 ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get())); 302 303 ASSERT_TRUE(WriteFile(test_file(), "content")); 304 ASSERT_TRUE(WaitForEvents()); 305 } 306 307 // Verify that watching a file whose parent directory doesn't exist yet works if 308 // the directory and file are created eventually. 309 TEST_F(FilePathWatcherTest, NonExistentDirectory) { 310 FilePathWatcher watcher; 311 FilePath dir(temp_dir_.path().AppendASCII("dir")); 312 FilePath file(dir.AppendASCII("file")); 313 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 314 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get())); 315 316 ASSERT_TRUE(file_util::CreateDirectory(dir)); 317 318 ASSERT_TRUE(WriteFile(file, "content")); 319 320 VLOG(1) << "Waiting for file creation"; 321 ASSERT_TRUE(WaitForEvents()); 322 323 ASSERT_TRUE(WriteFile(file, "content v2")); 324 VLOG(1) << "Waiting for file change"; 325 ASSERT_TRUE(WaitForEvents()); 326 327 ASSERT_TRUE(file_util::Delete(file, false)); 328 VLOG(1) << "Waiting for file deletion"; 329 ASSERT_TRUE(WaitForEvents()); 330 } 331 332 // Exercises watch reconfiguration for the case that directories on the path 333 // are rapidly created. 334 TEST_F(FilePathWatcherTest, DirectoryChain) { 335 FilePath path(temp_dir_.path()); 336 std::vector<std::string> dir_names; 337 for (int i = 0; i < 20; i++) { 338 std::string dir(StringPrintf("d%d", i)); 339 dir_names.push_back(dir); 340 path = path.AppendASCII(dir); 341 } 342 343 FilePathWatcher watcher; 344 FilePath file(path.AppendASCII("file")); 345 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 346 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get())); 347 348 FilePath sub_path(temp_dir_.path()); 349 for (std::vector<std::string>::const_iterator d(dir_names.begin()); 350 d != dir_names.end(); ++d) { 351 sub_path = sub_path.AppendASCII(*d); 352 ASSERT_TRUE(file_util::CreateDirectory(sub_path)); 353 } 354 VLOG(1) << "Create File"; 355 ASSERT_TRUE(WriteFile(file, "content")); 356 VLOG(1) << "Waiting for file creation"; 357 ASSERT_TRUE(WaitForEvents()); 358 359 ASSERT_TRUE(WriteFile(file, "content v2")); 360 VLOG(1) << "Waiting for file modification"; 361 ASSERT_TRUE(WaitForEvents()); 362 } 363 364 TEST_F(FilePathWatcherTest, DisappearingDirectory) { 365 FilePathWatcher watcher; 366 FilePath dir(temp_dir_.path().AppendASCII("dir")); 367 FilePath file(dir.AppendASCII("file")); 368 ASSERT_TRUE(file_util::CreateDirectory(dir)); 369 ASSERT_TRUE(WriteFile(file, "content")); 370 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 371 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get())); 372 373 ASSERT_TRUE(file_util::Delete(dir, true)); 374 ASSERT_TRUE(WaitForEvents()); 375 } 376 377 // Tests that a file that is deleted and reappears is tracked correctly. 378 TEST_F(FilePathWatcherTest, DeleteAndRecreate) { 379 ASSERT_TRUE(WriteFile(test_file(), "content")); 380 FilePathWatcher watcher; 381 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 382 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 383 384 ASSERT_TRUE(file_util::Delete(test_file(), false)); 385 VLOG(1) << "Waiting for file deletion"; 386 ASSERT_TRUE(WaitForEvents()); 387 388 ASSERT_TRUE(WriteFile(test_file(), "content")); 389 VLOG(1) << "Waiting for file creation"; 390 ASSERT_TRUE(WaitForEvents()); 391 } 392 393 TEST_F(FilePathWatcherTest, WatchDirectory) { 394 FilePathWatcher watcher; 395 FilePath dir(temp_dir_.path().AppendASCII("dir")); 396 FilePath file1(dir.AppendASCII("file1")); 397 FilePath file2(dir.AppendASCII("file2")); 398 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 399 ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get())); 400 401 ASSERT_TRUE(file_util::CreateDirectory(dir)); 402 VLOG(1) << "Waiting for directory creation"; 403 ASSERT_TRUE(WaitForEvents()); 404 405 ASSERT_TRUE(WriteFile(file1, "content")); 406 VLOG(1) << "Waiting for file1 creation"; 407 ASSERT_TRUE(WaitForEvents()); 408 409 #if !defined(OS_MACOSX) 410 // Mac implementation does not detect files modified in a directory. 411 ASSERT_TRUE(WriteFile(file1, "content v2")); 412 VLOG(1) << "Waiting for file1 modification"; 413 ASSERT_TRUE(WaitForEvents()); 414 #endif // !OS_MACOSX 415 416 ASSERT_TRUE(file_util::Delete(file1, false)); 417 VLOG(1) << "Waiting for file1 deletion"; 418 ASSERT_TRUE(WaitForEvents()); 419 420 ASSERT_TRUE(WriteFile(file2, "content")); 421 VLOG(1) << "Waiting for file2 creation"; 422 ASSERT_TRUE(WaitForEvents()); 423 } 424 425 TEST_F(FilePathWatcherTest, MoveParent) { 426 FilePathWatcher file_watcher; 427 FilePathWatcher subdir_watcher; 428 FilePath dir(temp_dir_.path().AppendASCII("dir")); 429 FilePath dest(temp_dir_.path().AppendASCII("dest")); 430 FilePath subdir(dir.AppendASCII("subdir")); 431 FilePath file(subdir.AppendASCII("file")); 432 scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector())); 433 ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get())); 434 scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); 435 ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get())); 436 437 // Setup a directory hierarchy. 438 ASSERT_TRUE(file_util::CreateDirectory(subdir)); 439 ASSERT_TRUE(WriteFile(file, "content")); 440 VLOG(1) << "Waiting for file creation"; 441 ASSERT_TRUE(WaitForEvents()); 442 443 // Move the parent directory. 444 file_util::Move(dir, dest); 445 VLOG(1) << "Waiting for directory move"; 446 ASSERT_TRUE(WaitForEvents()); 447 } 448 449 TEST_F(FilePathWatcherTest, MoveChild) { 450 FilePathWatcher file_watcher; 451 FilePathWatcher subdir_watcher; 452 FilePath source_dir(temp_dir_.path().AppendASCII("source")); 453 FilePath source_subdir(source_dir.AppendASCII("subdir")); 454 FilePath source_file(source_subdir.AppendASCII("file")); 455 FilePath dest_dir(temp_dir_.path().AppendASCII("dest")); 456 FilePath dest_subdir(dest_dir.AppendASCII("subdir")); 457 FilePath dest_file(dest_subdir.AppendASCII("file")); 458 459 // Setup a directory hierarchy. 460 ASSERT_TRUE(file_util::CreateDirectory(source_subdir)); 461 ASSERT_TRUE(WriteFile(source_file, "content")); 462 463 scoped_refptr<TestDelegate> file_delegate(new TestDelegate(collector())); 464 ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get())); 465 scoped_refptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); 466 ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get())); 467 468 // Move the directory into place, s.t. the watched file appears. 469 ASSERT_TRUE(file_util::Move(source_dir, dest_dir)); 470 ASSERT_TRUE(WaitForEvents()); 471 } 472 473 #if !defined(OS_LINUX) 474 // Linux implementation of FilePathWatcher doesn't catch attribute changes. 475 // http://crbug.com/78043 476 477 // Verify that changing attributes on a file is caught 478 TEST_F(FilePathWatcherTest, FileAttributesChanged) { 479 ASSERT_TRUE(WriteFile(test_file(), "content")); 480 FilePathWatcher watcher; 481 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 482 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get())); 483 484 // Now make sure we get notified if the file is modified. 485 ASSERT_TRUE(file_util::MakeFileUnreadable(test_file())); 486 ASSERT_TRUE(WaitForEvents()); 487 } 488 489 #endif // !OS_LINUX 490 491 enum Permission { 492 Read, 493 Write, 494 Execute 495 }; 496 497 bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) { 498 #if defined(OS_POSIX) 499 struct stat stat_buf; 500 501 if (stat(path.value().c_str(), &stat_buf) != 0) 502 return false; 503 504 mode_t mode = 0; 505 switch (perm) { 506 case Read: 507 mode = S_IRUSR | S_IRGRP | S_IROTH; 508 break; 509 case Write: 510 mode = S_IWUSR | S_IWGRP | S_IWOTH; 511 break; 512 case Execute: 513 mode = S_IXUSR | S_IXGRP | S_IXOTH; 514 break; 515 default: 516 ADD_FAILURE() << "unknown perm " << perm; 517 return false; 518 } 519 if (allow) { 520 stat_buf.st_mode |= mode; 521 } else { 522 stat_buf.st_mode &= ~mode; 523 } 524 return chmod(path.value().c_str(), stat_buf.st_mode) == 0; 525 526 #elif defined(OS_WIN) 527 PACL old_dacl; 528 PSECURITY_DESCRIPTOR security_descriptor; 529 if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), 530 SE_FILE_OBJECT, 531 DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl, 532 NULL, &security_descriptor) != ERROR_SUCCESS) 533 return false; 534 535 DWORD mode = 0; 536 switch (perm) { 537 case Read: 538 mode = GENERIC_READ; 539 break; 540 case Write: 541 mode = GENERIC_WRITE; 542 break; 543 case Execute: 544 mode = GENERIC_EXECUTE; 545 break; 546 default: 547 ADD_FAILURE() << "unknown perm " << perm; 548 return false; 549 } 550 551 // Deny Read access for the current user. 552 EXPLICIT_ACCESS change; 553 change.grfAccessPermissions = mode; 554 change.grfAccessMode = allow ? GRANT_ACCESS : DENY_ACCESS; 555 change.grfInheritance = 0; 556 change.Trustee.pMultipleTrustee = NULL; 557 change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; 558 change.Trustee.TrusteeForm = TRUSTEE_IS_NAME; 559 change.Trustee.TrusteeType = TRUSTEE_IS_USER; 560 change.Trustee.ptstrName = L"CURRENT_USER"; 561 562 PACL new_dacl; 563 if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) { 564 LocalFree(security_descriptor); 565 return false; 566 } 567 568 DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), 569 SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, 570 NULL, NULL, new_dacl, NULL); 571 LocalFree(security_descriptor); 572 LocalFree(new_dacl); 573 574 return rc == ERROR_SUCCESS; 575 #else 576 NOTIMPLEMENTED(); 577 return false; 578 #endif 579 } 580 581 #if defined(OS_MACOSX) 582 // Linux implementation of FilePathWatcher doesn't catch attribute changes. 583 // http://crbug.com/78043 584 // Windows implementation of FilePathWatcher catches attribute changes that 585 // don't affect the path being watched. 586 // http://crbug.com/78045 587 588 // Verify that changing attributes on a directory works. 589 TEST_F(FilePathWatcherTest, DirAttributesChanged) { 590 FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1")); 591 FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2")); 592 FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile")); 593 // Setup a directory hierarchy. 594 ASSERT_TRUE(file_util::CreateDirectory(test_dir1)); 595 ASSERT_TRUE(file_util::CreateDirectory(test_dir2)); 596 ASSERT_TRUE(WriteFile(test_file, "content")); 597 598 FilePathWatcher watcher; 599 scoped_refptr<TestDelegate> delegate(new TestDelegate(collector())); 600 ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get())); 601 602 // We should not get notified in this case as it hasn't affected our ability 603 // to access the file. 604 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false)); 605 loop_.PostDelayedTask(FROM_HERE, 606 new MessageLoop::QuitTask, 607 TestTimeouts::tiny_timeout_ms()); 608 ASSERT_FALSE(WaitForEvents()); 609 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true)); 610 611 // We should get notified in this case because filepathwatcher can no 612 // longer access the file 613 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false)); 614 ASSERT_TRUE(WaitForEvents()); 615 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true)); 616 } 617 618 #endif // OS_MACOSX 619 } // namespace 620 621 } // namespace files 622 } // namespace base 623