1 // Copyright (c) 2012 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 #if defined(OS_WIN) 8 #include <windows.h> 9 #include <aclapi.h> 10 #elif defined(OS_POSIX) 11 #include <sys/stat.h> 12 #endif 13 14 #include <set> 15 16 #include "base/basictypes.h" 17 #include "base/bind.h" 18 #include "base/bind_helpers.h" 19 #include "base/compiler_specific.h" 20 #include "base/files/file_path.h" 21 #include "base/files/file_util.h" 22 #include "base/files/scoped_temp_dir.h" 23 #include "base/message_loop/message_loop.h" 24 #include "base/message_loop/message_loop_proxy.h" 25 #include "base/run_loop.h" 26 #include "base/stl_util.h" 27 #include "base/strings/stringprintf.h" 28 #include "base/synchronization/waitable_event.h" 29 #include "base/test/test_file_util.h" 30 #include "base/test/test_timeouts.h" 31 #include "base/threading/thread.h" 32 #include "testing/gtest/include/gtest/gtest.h" 33 34 namespace base { 35 36 namespace { 37 38 class TestDelegate; 39 40 // Aggregates notifications from the test delegates and breaks the message loop 41 // the test thread is waiting on once they all came in. 42 class NotificationCollector 43 : public base::RefCountedThreadSafe<NotificationCollector> { 44 public: 45 NotificationCollector() 46 : loop_(base::MessageLoopProxy::current()) {} 47 48 // Called from the file thread by the delegates. 49 void OnChange(TestDelegate* delegate) { 50 loop_->PostTask(FROM_HERE, 51 base::Bind(&NotificationCollector::RecordChange, this, 52 base::Unretained(delegate))); 53 } 54 55 void Register(TestDelegate* delegate) { 56 delegates_.insert(delegate); 57 } 58 59 void Reset() { 60 signaled_.clear(); 61 } 62 63 bool Success() { 64 return signaled_ == delegates_; 65 } 66 67 private: 68 friend class base::RefCountedThreadSafe<NotificationCollector>; 69 ~NotificationCollector() {} 70 71 void RecordChange(TestDelegate* delegate) { 72 // Warning: |delegate| is Unretained. Do not dereference. 73 ASSERT_TRUE(loop_->BelongsToCurrentThread()); 74 ASSERT_TRUE(delegates_.count(delegate)); 75 signaled_.insert(delegate); 76 77 // Check whether all delegates have been signaled. 78 if (signaled_ == delegates_) 79 loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); 80 } 81 82 // Set of registered delegates. 83 std::set<TestDelegate*> delegates_; 84 85 // Set of signaled delegates. 86 std::set<TestDelegate*> signaled_; 87 88 // The loop we should break after all delegates signaled. 89 scoped_refptr<base::MessageLoopProxy> loop_; 90 }; 91 92 class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> { 93 public: 94 TestDelegateBase() {} 95 virtual ~TestDelegateBase() {} 96 97 virtual void OnFileChanged(const FilePath& path, bool error) = 0; 98 99 private: 100 DISALLOW_COPY_AND_ASSIGN(TestDelegateBase); 101 }; 102 103 // A mock class for testing. Gmock is not appropriate because it is not 104 // thread-safe for setting expectations. Thus the test code cannot safely 105 // reset expectations while the file watcher is running. 106 // Instead, TestDelegate gets the notifications from FilePathWatcher and uses 107 // NotificationCollector to aggregate the results. 108 class TestDelegate : public TestDelegateBase { 109 public: 110 explicit TestDelegate(NotificationCollector* collector) 111 : collector_(collector) { 112 collector_->Register(this); 113 } 114 virtual ~TestDelegate() {} 115 116 virtual void OnFileChanged(const FilePath& path, bool error) OVERRIDE { 117 if (error) 118 ADD_FAILURE() << "Error " << path.value(); 119 else 120 collector_->OnChange(this); 121 } 122 123 private: 124 scoped_refptr<NotificationCollector> collector_; 125 126 DISALLOW_COPY_AND_ASSIGN(TestDelegate); 127 }; 128 129 void SetupWatchCallback(const FilePath& target, 130 FilePathWatcher* watcher, 131 TestDelegateBase* delegate, 132 bool recursive_watch, 133 bool* result, 134 base::WaitableEvent* completion) { 135 *result = watcher->Watch(target, recursive_watch, 136 base::Bind(&TestDelegateBase::OnFileChanged, 137 delegate->AsWeakPtr())); 138 completion->Signal(); 139 } 140 141 class FilePathWatcherTest : public testing::Test { 142 public: 143 FilePathWatcherTest() 144 : file_thread_("FilePathWatcherTest") {} 145 146 virtual ~FilePathWatcherTest() {} 147 148 protected: 149 virtual void SetUp() OVERRIDE { 150 // Create a separate file thread in order to test proper thread usage. 151 base::Thread::Options options(MessageLoop::TYPE_IO, 0); 152 ASSERT_TRUE(file_thread_.StartWithOptions(options)); 153 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 154 collector_ = new NotificationCollector(); 155 } 156 157 virtual void TearDown() OVERRIDE { 158 RunLoop().RunUntilIdle(); 159 } 160 161 void DeleteDelegateOnFileThread(TestDelegate* delegate) { 162 file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, delegate); 163 } 164 165 FilePath test_file() { 166 return temp_dir_.path().AppendASCII("FilePathWatcherTest"); 167 } 168 169 FilePath test_link() { 170 return temp_dir_.path().AppendASCII("FilePathWatcherTest.lnk"); 171 } 172 173 // Write |content| to |file|. Returns true on success. 174 bool WriteFile(const FilePath& file, const std::string& content) { 175 int write_size = ::base::WriteFile(file, content.c_str(), content.length()); 176 return write_size == static_cast<int>(content.length()); 177 } 178 179 bool SetupWatch(const FilePath& target, 180 FilePathWatcher* watcher, 181 TestDelegateBase* delegate, 182 bool recursive_watch) WARN_UNUSED_RESULT; 183 184 bool WaitForEvents() WARN_UNUSED_RESULT { 185 collector_->Reset(); 186 loop_.Run(); 187 return collector_->Success(); 188 } 189 190 NotificationCollector* collector() { return collector_.get(); } 191 192 MessageLoop loop_; 193 base::Thread file_thread_; 194 ScopedTempDir temp_dir_; 195 scoped_refptr<NotificationCollector> collector_; 196 197 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherTest); 198 }; 199 200 bool FilePathWatcherTest::SetupWatch(const FilePath& target, 201 FilePathWatcher* watcher, 202 TestDelegateBase* delegate, 203 bool recursive_watch) { 204 base::WaitableEvent completion(false, false); 205 bool result; 206 file_thread_.message_loop_proxy()->PostTask( 207 FROM_HERE, 208 base::Bind(SetupWatchCallback, target, watcher, delegate, recursive_watch, 209 &result, &completion)); 210 completion.Wait(); 211 return result; 212 } 213 214 // Basic test: Create the file and verify that we notice. 215 TEST_F(FilePathWatcherTest, NewFile) { 216 FilePathWatcher watcher; 217 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 218 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 219 220 ASSERT_TRUE(WriteFile(test_file(), "content")); 221 ASSERT_TRUE(WaitForEvents()); 222 DeleteDelegateOnFileThread(delegate.release()); 223 } 224 225 // Verify that modifying the file is caught. 226 TEST_F(FilePathWatcherTest, ModifiedFile) { 227 ASSERT_TRUE(WriteFile(test_file(), "content")); 228 229 FilePathWatcher watcher; 230 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 231 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 232 233 // Now make sure we get notified if the file is modified. 234 ASSERT_TRUE(WriteFile(test_file(), "new content")); 235 ASSERT_TRUE(WaitForEvents()); 236 DeleteDelegateOnFileThread(delegate.release()); 237 } 238 239 // Verify that moving the file into place is caught. 240 TEST_F(FilePathWatcherTest, MovedFile) { 241 FilePath source_file(temp_dir_.path().AppendASCII("source")); 242 ASSERT_TRUE(WriteFile(source_file, "content")); 243 244 FilePathWatcher watcher; 245 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 246 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 247 248 // Now make sure we get notified if the file is modified. 249 ASSERT_TRUE(base::Move(source_file, test_file())); 250 ASSERT_TRUE(WaitForEvents()); 251 DeleteDelegateOnFileThread(delegate.release()); 252 } 253 254 TEST_F(FilePathWatcherTest, DeletedFile) { 255 ASSERT_TRUE(WriteFile(test_file(), "content")); 256 257 FilePathWatcher watcher; 258 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 259 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 260 261 // Now make sure we get notified if the file is deleted. 262 base::DeleteFile(test_file(), false); 263 ASSERT_TRUE(WaitForEvents()); 264 DeleteDelegateOnFileThread(delegate.release()); 265 } 266 267 // Used by the DeleteDuringNotify test below. 268 // Deletes the FilePathWatcher when it's notified. 269 class Deleter : public TestDelegateBase { 270 public: 271 Deleter(FilePathWatcher* watcher, MessageLoop* loop) 272 : watcher_(watcher), 273 loop_(loop) { 274 } 275 virtual ~Deleter() {} 276 277 virtual void OnFileChanged(const FilePath&, bool) OVERRIDE { 278 watcher_.reset(); 279 loop_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); 280 } 281 282 FilePathWatcher* watcher() const { return watcher_.get(); } 283 284 private: 285 scoped_ptr<FilePathWatcher> watcher_; 286 MessageLoop* loop_; 287 288 DISALLOW_COPY_AND_ASSIGN(Deleter); 289 }; 290 291 // Verify that deleting a watcher during the callback doesn't crash. 292 TEST_F(FilePathWatcherTest, DeleteDuringNotify) { 293 FilePathWatcher* watcher = new FilePathWatcher; 294 // Takes ownership of watcher. 295 scoped_ptr<Deleter> deleter(new Deleter(watcher, &loop_)); 296 ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get(), false)); 297 298 ASSERT_TRUE(WriteFile(test_file(), "content")); 299 ASSERT_TRUE(WaitForEvents()); 300 301 // We win if we haven't crashed yet. 302 // Might as well double-check it got deleted, too. 303 ASSERT_TRUE(deleter->watcher() == NULL); 304 } 305 306 // Verify that deleting the watcher works even if there is a pending 307 // notification. 308 // Flaky on MacOS (and ARM linux): http://crbug.com/85930 309 TEST_F(FilePathWatcherTest, DISABLED_DestroyWithPendingNotification) { 310 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 311 FilePathWatcher* watcher = new FilePathWatcher; 312 ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get(), false)); 313 ASSERT_TRUE(WriteFile(test_file(), "content")); 314 file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher); 315 DeleteDelegateOnFileThread(delegate.release()); 316 } 317 318 TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) { 319 FilePathWatcher watcher1, watcher2; 320 scoped_ptr<TestDelegate> delegate1(new TestDelegate(collector())); 321 scoped_ptr<TestDelegate> delegate2(new TestDelegate(collector())); 322 ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get(), false)); 323 ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get(), false)); 324 325 ASSERT_TRUE(WriteFile(test_file(), "content")); 326 ASSERT_TRUE(WaitForEvents()); 327 DeleteDelegateOnFileThread(delegate1.release()); 328 DeleteDelegateOnFileThread(delegate2.release()); 329 } 330 331 // Verify that watching a file whose parent directory doesn't exist yet works if 332 // the directory and file are created eventually. 333 TEST_F(FilePathWatcherTest, NonExistentDirectory) { 334 FilePathWatcher watcher; 335 FilePath dir(temp_dir_.path().AppendASCII("dir")); 336 FilePath file(dir.AppendASCII("file")); 337 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 338 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); 339 340 ASSERT_TRUE(base::CreateDirectory(dir)); 341 342 ASSERT_TRUE(WriteFile(file, "content")); 343 344 VLOG(1) << "Waiting for file creation"; 345 ASSERT_TRUE(WaitForEvents()); 346 347 ASSERT_TRUE(WriteFile(file, "content v2")); 348 VLOG(1) << "Waiting for file change"; 349 ASSERT_TRUE(WaitForEvents()); 350 351 ASSERT_TRUE(base::DeleteFile(file, false)); 352 VLOG(1) << "Waiting for file deletion"; 353 ASSERT_TRUE(WaitForEvents()); 354 DeleteDelegateOnFileThread(delegate.release()); 355 } 356 357 // Exercises watch reconfiguration for the case that directories on the path 358 // are rapidly created. 359 TEST_F(FilePathWatcherTest, DirectoryChain) { 360 FilePath path(temp_dir_.path()); 361 std::vector<std::string> dir_names; 362 for (int i = 0; i < 20; i++) { 363 std::string dir(base::StringPrintf("d%d", i)); 364 dir_names.push_back(dir); 365 path = path.AppendASCII(dir); 366 } 367 368 FilePathWatcher watcher; 369 FilePath file(path.AppendASCII("file")); 370 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 371 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); 372 373 FilePath sub_path(temp_dir_.path()); 374 for (std::vector<std::string>::const_iterator d(dir_names.begin()); 375 d != dir_names.end(); ++d) { 376 sub_path = sub_path.AppendASCII(*d); 377 ASSERT_TRUE(base::CreateDirectory(sub_path)); 378 } 379 VLOG(1) << "Create File"; 380 ASSERT_TRUE(WriteFile(file, "content")); 381 VLOG(1) << "Waiting for file creation"; 382 ASSERT_TRUE(WaitForEvents()); 383 384 ASSERT_TRUE(WriteFile(file, "content v2")); 385 VLOG(1) << "Waiting for file modification"; 386 ASSERT_TRUE(WaitForEvents()); 387 DeleteDelegateOnFileThread(delegate.release()); 388 } 389 390 #if defined(OS_MACOSX) 391 // http://crbug.com/85930 392 #define DisappearingDirectory DISABLED_DisappearingDirectory 393 #endif 394 TEST_F(FilePathWatcherTest, DisappearingDirectory) { 395 FilePathWatcher watcher; 396 FilePath dir(temp_dir_.path().AppendASCII("dir")); 397 FilePath file(dir.AppendASCII("file")); 398 ASSERT_TRUE(base::CreateDirectory(dir)); 399 ASSERT_TRUE(WriteFile(file, "content")); 400 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 401 ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); 402 403 ASSERT_TRUE(base::DeleteFile(dir, true)); 404 ASSERT_TRUE(WaitForEvents()); 405 DeleteDelegateOnFileThread(delegate.release()); 406 } 407 408 // Tests that a file that is deleted and reappears is tracked correctly. 409 TEST_F(FilePathWatcherTest, DeleteAndRecreate) { 410 ASSERT_TRUE(WriteFile(test_file(), "content")); 411 FilePathWatcher watcher; 412 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 413 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 414 415 ASSERT_TRUE(base::DeleteFile(test_file(), false)); 416 VLOG(1) << "Waiting for file deletion"; 417 ASSERT_TRUE(WaitForEvents()); 418 419 ASSERT_TRUE(WriteFile(test_file(), "content")); 420 VLOG(1) << "Waiting for file creation"; 421 ASSERT_TRUE(WaitForEvents()); 422 DeleteDelegateOnFileThread(delegate.release()); 423 } 424 425 TEST_F(FilePathWatcherTest, WatchDirectory) { 426 FilePathWatcher watcher; 427 FilePath dir(temp_dir_.path().AppendASCII("dir")); 428 FilePath file1(dir.AppendASCII("file1")); 429 FilePath file2(dir.AppendASCII("file2")); 430 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 431 ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), false)); 432 433 ASSERT_TRUE(base::CreateDirectory(dir)); 434 VLOG(1) << "Waiting for directory creation"; 435 ASSERT_TRUE(WaitForEvents()); 436 437 ASSERT_TRUE(WriteFile(file1, "content")); 438 VLOG(1) << "Waiting for file1 creation"; 439 ASSERT_TRUE(WaitForEvents()); 440 441 #if !defined(OS_MACOSX) 442 // Mac implementation does not detect files modified in a directory. 443 ASSERT_TRUE(WriteFile(file1, "content v2")); 444 VLOG(1) << "Waiting for file1 modification"; 445 ASSERT_TRUE(WaitForEvents()); 446 #endif // !OS_MACOSX 447 448 ASSERT_TRUE(base::DeleteFile(file1, false)); 449 VLOG(1) << "Waiting for file1 deletion"; 450 ASSERT_TRUE(WaitForEvents()); 451 452 ASSERT_TRUE(WriteFile(file2, "content")); 453 VLOG(1) << "Waiting for file2 creation"; 454 ASSERT_TRUE(WaitForEvents()); 455 DeleteDelegateOnFileThread(delegate.release()); 456 } 457 458 TEST_F(FilePathWatcherTest, MoveParent) { 459 FilePathWatcher file_watcher; 460 FilePathWatcher subdir_watcher; 461 FilePath dir(temp_dir_.path().AppendASCII("dir")); 462 FilePath dest(temp_dir_.path().AppendASCII("dest")); 463 FilePath subdir(dir.AppendASCII("subdir")); 464 FilePath file(subdir.AppendASCII("file")); 465 scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); 466 ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get(), false)); 467 scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); 468 ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get(), 469 false)); 470 471 // Setup a directory hierarchy. 472 ASSERT_TRUE(base::CreateDirectory(subdir)); 473 ASSERT_TRUE(WriteFile(file, "content")); 474 VLOG(1) << "Waiting for file creation"; 475 ASSERT_TRUE(WaitForEvents()); 476 477 // Move the parent directory. 478 base::Move(dir, dest); 479 VLOG(1) << "Waiting for directory move"; 480 ASSERT_TRUE(WaitForEvents()); 481 DeleteDelegateOnFileThread(file_delegate.release()); 482 DeleteDelegateOnFileThread(subdir_delegate.release()); 483 } 484 485 TEST_F(FilePathWatcherTest, RecursiveWatch) { 486 FilePathWatcher watcher; 487 FilePath dir(temp_dir_.path().AppendASCII("dir")); 488 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 489 bool setup_result = SetupWatch(dir, &watcher, delegate.get(), true); 490 if (!FilePathWatcher::RecursiveWatchAvailable()) { 491 ASSERT_FALSE(setup_result); 492 DeleteDelegateOnFileThread(delegate.release()); 493 return; 494 } 495 ASSERT_TRUE(setup_result); 496 497 // Main directory("dir") creation. 498 ASSERT_TRUE(base::CreateDirectory(dir)); 499 ASSERT_TRUE(WaitForEvents()); 500 501 // Create "$dir/file1". 502 FilePath file1(dir.AppendASCII("file1")); 503 ASSERT_TRUE(WriteFile(file1, "content")); 504 ASSERT_TRUE(WaitForEvents()); 505 506 // Create "$dir/subdir". 507 FilePath subdir(dir.AppendASCII("subdir")); 508 ASSERT_TRUE(base::CreateDirectory(subdir)); 509 ASSERT_TRUE(WaitForEvents()); 510 511 // Create "$dir/subdir/subdir_file1". 512 FilePath subdir_file1(subdir.AppendASCII("subdir_file1")); 513 ASSERT_TRUE(WriteFile(subdir_file1, "content")); 514 ASSERT_TRUE(WaitForEvents()); 515 516 // Create "$dir/subdir/subdir_child_dir". 517 FilePath subdir_child_dir(subdir.AppendASCII("subdir_child_dir")); 518 ASSERT_TRUE(base::CreateDirectory(subdir_child_dir)); 519 ASSERT_TRUE(WaitForEvents()); 520 521 // Create "$dir/subdir/subdir_child_dir/child_dir_file1". 522 FilePath child_dir_file1(subdir_child_dir.AppendASCII("child_dir_file1")); 523 ASSERT_TRUE(WriteFile(child_dir_file1, "content v2")); 524 ASSERT_TRUE(WaitForEvents()); 525 526 // Write into "$dir/subdir/subdir_child_dir/child_dir_file1". 527 ASSERT_TRUE(WriteFile(child_dir_file1, "content")); 528 ASSERT_TRUE(WaitForEvents()); 529 530 // Modify "$dir/subdir/subdir_child_dir/child_dir_file1" attributes. 531 ASSERT_TRUE(base::MakeFileUnreadable(child_dir_file1)); 532 ASSERT_TRUE(WaitForEvents()); 533 534 // Delete "$dir/subdir/subdir_file1". 535 ASSERT_TRUE(base::DeleteFile(subdir_file1, false)); 536 ASSERT_TRUE(WaitForEvents()); 537 538 // Delete "$dir/subdir/subdir_child_dir/child_dir_file1". 539 ASSERT_TRUE(base::DeleteFile(child_dir_file1, false)); 540 ASSERT_TRUE(WaitForEvents()); 541 DeleteDelegateOnFileThread(delegate.release()); 542 } 543 544 #if defined(OS_POSIX) 545 TEST_F(FilePathWatcherTest, RecursiveWithSymLink) { 546 if (!FilePathWatcher::RecursiveWatchAvailable()) 547 return; 548 549 FilePathWatcher watcher; 550 FilePath test_dir(temp_dir_.path().AppendASCII("test_dir")); 551 ASSERT_TRUE(base::CreateDirectory(test_dir)); 552 FilePath symlink(test_dir.AppendASCII("symlink")); 553 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 554 ASSERT_TRUE(SetupWatch(symlink, &watcher, delegate.get(), true)); 555 556 // Link creation. 557 FilePath target1(temp_dir_.path().AppendASCII("target1")); 558 ASSERT_TRUE(base::CreateSymbolicLink(target1, symlink)); 559 ASSERT_TRUE(WaitForEvents()); 560 561 // Target1 creation. 562 ASSERT_TRUE(base::CreateDirectory(target1)); 563 ASSERT_TRUE(WaitForEvents()); 564 565 // Create a file in target1. 566 FilePath target1_file(target1.AppendASCII("file")); 567 ASSERT_TRUE(WriteFile(target1_file, "content")); 568 ASSERT_TRUE(WaitForEvents()); 569 570 // Link change. 571 FilePath target2(temp_dir_.path().AppendASCII("target2")); 572 ASSERT_TRUE(base::CreateDirectory(target2)); 573 ASSERT_TRUE(base::DeleteFile(symlink, false)); 574 ASSERT_TRUE(base::CreateSymbolicLink(target2, symlink)); 575 ASSERT_TRUE(WaitForEvents()); 576 577 // Create a file in target2. 578 FilePath target2_file(target2.AppendASCII("file")); 579 ASSERT_TRUE(WriteFile(target2_file, "content")); 580 ASSERT_TRUE(WaitForEvents()); 581 582 DeleteDelegateOnFileThread(delegate.release()); 583 } 584 #endif // OS_POSIX 585 586 TEST_F(FilePathWatcherTest, MoveChild) { 587 FilePathWatcher file_watcher; 588 FilePathWatcher subdir_watcher; 589 FilePath source_dir(temp_dir_.path().AppendASCII("source")); 590 FilePath source_subdir(source_dir.AppendASCII("subdir")); 591 FilePath source_file(source_subdir.AppendASCII("file")); 592 FilePath dest_dir(temp_dir_.path().AppendASCII("dest")); 593 FilePath dest_subdir(dest_dir.AppendASCII("subdir")); 594 FilePath dest_file(dest_subdir.AppendASCII("file")); 595 596 // Setup a directory hierarchy. 597 ASSERT_TRUE(base::CreateDirectory(source_subdir)); 598 ASSERT_TRUE(WriteFile(source_file, "content")); 599 600 scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); 601 ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get(), false)); 602 scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); 603 ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get(), 604 false)); 605 606 // Move the directory into place, s.t. the watched file appears. 607 ASSERT_TRUE(base::Move(source_dir, dest_dir)); 608 ASSERT_TRUE(WaitForEvents()); 609 DeleteDelegateOnFileThread(file_delegate.release()); 610 DeleteDelegateOnFileThread(subdir_delegate.release()); 611 } 612 613 // Verify that changing attributes on a file is caught 614 TEST_F(FilePathWatcherTest, FileAttributesChanged) { 615 ASSERT_TRUE(WriteFile(test_file(), "content")); 616 FilePathWatcher watcher; 617 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 618 ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); 619 620 // Now make sure we get notified if the file is modified. 621 ASSERT_TRUE(base::MakeFileUnreadable(test_file())); 622 ASSERT_TRUE(WaitForEvents()); 623 DeleteDelegateOnFileThread(delegate.release()); 624 } 625 626 #if defined(OS_LINUX) 627 628 // Verify that creating a symlink is caught. 629 TEST_F(FilePathWatcherTest, CreateLink) { 630 FilePathWatcher watcher; 631 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 632 // Note that we are watching the symlink 633 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); 634 635 // Now make sure we get notified if the link is created. 636 // Note that test_file() doesn't have to exist. 637 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); 638 ASSERT_TRUE(WaitForEvents()); 639 DeleteDelegateOnFileThread(delegate.release()); 640 } 641 642 // Verify that deleting a symlink is caught. 643 TEST_F(FilePathWatcherTest, DeleteLink) { 644 // Unfortunately this test case only works if the link target exists. 645 // TODO(craig) fix this as part of crbug.com/91561. 646 ASSERT_TRUE(WriteFile(test_file(), "content")); 647 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); 648 FilePathWatcher watcher; 649 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 650 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); 651 652 // Now make sure we get notified if the link is deleted. 653 ASSERT_TRUE(base::DeleteFile(test_link(), false)); 654 ASSERT_TRUE(WaitForEvents()); 655 DeleteDelegateOnFileThread(delegate.release()); 656 } 657 658 // Verify that modifying a target file that a link is pointing to 659 // when we are watching the link is caught. 660 TEST_F(FilePathWatcherTest, ModifiedLinkedFile) { 661 ASSERT_TRUE(WriteFile(test_file(), "content")); 662 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); 663 FilePathWatcher watcher; 664 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 665 // Note that we are watching the symlink. 666 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); 667 668 // Now make sure we get notified if the file is modified. 669 ASSERT_TRUE(WriteFile(test_file(), "new content")); 670 ASSERT_TRUE(WaitForEvents()); 671 DeleteDelegateOnFileThread(delegate.release()); 672 } 673 674 // Verify that creating a target file that a link is pointing to 675 // when we are watching the link is caught. 676 TEST_F(FilePathWatcherTest, CreateTargetLinkedFile) { 677 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); 678 FilePathWatcher watcher; 679 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 680 // Note that we are watching the symlink. 681 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); 682 683 // Now make sure we get notified if the target file is created. 684 ASSERT_TRUE(WriteFile(test_file(), "content")); 685 ASSERT_TRUE(WaitForEvents()); 686 DeleteDelegateOnFileThread(delegate.release()); 687 } 688 689 // Verify that deleting a target file that a link is pointing to 690 // when we are watching the link is caught. 691 TEST_F(FilePathWatcherTest, DeleteTargetLinkedFile) { 692 ASSERT_TRUE(WriteFile(test_file(), "content")); 693 ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); 694 FilePathWatcher watcher; 695 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 696 // Note that we are watching the symlink. 697 ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); 698 699 // Now make sure we get notified if the target file is deleted. 700 ASSERT_TRUE(base::DeleteFile(test_file(), false)); 701 ASSERT_TRUE(WaitForEvents()); 702 DeleteDelegateOnFileThread(delegate.release()); 703 } 704 705 // Verify that watching a file whose parent directory is a link that 706 // doesn't exist yet works if the symlink is created eventually. 707 TEST_F(FilePathWatcherTest, LinkedDirectoryPart1) { 708 FilePathWatcher watcher; 709 FilePath dir(temp_dir_.path().AppendASCII("dir")); 710 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); 711 FilePath file(dir.AppendASCII("file")); 712 FilePath linkfile(link_dir.AppendASCII("file")); 713 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 714 // dir/file should exist. 715 ASSERT_TRUE(base::CreateDirectory(dir)); 716 ASSERT_TRUE(WriteFile(file, "content")); 717 // Note that we are watching dir.lnk/file which doesn't exist yet. 718 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); 719 720 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); 721 VLOG(1) << "Waiting for link creation"; 722 ASSERT_TRUE(WaitForEvents()); 723 724 ASSERT_TRUE(WriteFile(file, "content v2")); 725 VLOG(1) << "Waiting for file change"; 726 ASSERT_TRUE(WaitForEvents()); 727 728 ASSERT_TRUE(base::DeleteFile(file, false)); 729 VLOG(1) << "Waiting for file deletion"; 730 ASSERT_TRUE(WaitForEvents()); 731 DeleteDelegateOnFileThread(delegate.release()); 732 } 733 734 // Verify that watching a file whose parent directory is a 735 // dangling symlink works if the directory is created eventually. 736 TEST_F(FilePathWatcherTest, LinkedDirectoryPart2) { 737 FilePathWatcher watcher; 738 FilePath dir(temp_dir_.path().AppendASCII("dir")); 739 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); 740 FilePath file(dir.AppendASCII("file")); 741 FilePath linkfile(link_dir.AppendASCII("file")); 742 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 743 // Now create the link from dir.lnk pointing to dir but 744 // neither dir nor dir/file exist yet. 745 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); 746 // Note that we are watching dir.lnk/file. 747 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); 748 749 ASSERT_TRUE(base::CreateDirectory(dir)); 750 ASSERT_TRUE(WriteFile(file, "content")); 751 VLOG(1) << "Waiting for dir/file creation"; 752 ASSERT_TRUE(WaitForEvents()); 753 754 ASSERT_TRUE(WriteFile(file, "content v2")); 755 VLOG(1) << "Waiting for file change"; 756 ASSERT_TRUE(WaitForEvents()); 757 758 ASSERT_TRUE(base::DeleteFile(file, false)); 759 VLOG(1) << "Waiting for file deletion"; 760 ASSERT_TRUE(WaitForEvents()); 761 DeleteDelegateOnFileThread(delegate.release()); 762 } 763 764 // Verify that watching a file with a symlink on the path 765 // to the file works. 766 TEST_F(FilePathWatcherTest, LinkedDirectoryPart3) { 767 FilePathWatcher watcher; 768 FilePath dir(temp_dir_.path().AppendASCII("dir")); 769 FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); 770 FilePath file(dir.AppendASCII("file")); 771 FilePath linkfile(link_dir.AppendASCII("file")); 772 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 773 ASSERT_TRUE(base::CreateDirectory(dir)); 774 ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); 775 // Note that we are watching dir.lnk/file but the file doesn't exist yet. 776 ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); 777 778 ASSERT_TRUE(WriteFile(file, "content")); 779 VLOG(1) << "Waiting for file creation"; 780 ASSERT_TRUE(WaitForEvents()); 781 782 ASSERT_TRUE(WriteFile(file, "content v2")); 783 VLOG(1) << "Waiting for file change"; 784 ASSERT_TRUE(WaitForEvents()); 785 786 ASSERT_TRUE(base::DeleteFile(file, false)); 787 VLOG(1) << "Waiting for file deletion"; 788 ASSERT_TRUE(WaitForEvents()); 789 DeleteDelegateOnFileThread(delegate.release()); 790 } 791 792 #endif // OS_LINUX 793 794 enum Permission { 795 Read, 796 Write, 797 Execute 798 }; 799 800 #if defined(OS_MACOSX) 801 bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) { 802 struct stat stat_buf; 803 804 if (stat(path.value().c_str(), &stat_buf) != 0) 805 return false; 806 807 mode_t mode = 0; 808 switch (perm) { 809 case Read: 810 mode = S_IRUSR | S_IRGRP | S_IROTH; 811 break; 812 case Write: 813 mode = S_IWUSR | S_IWGRP | S_IWOTH; 814 break; 815 case Execute: 816 mode = S_IXUSR | S_IXGRP | S_IXOTH; 817 break; 818 default: 819 ADD_FAILURE() << "unknown perm " << perm; 820 return false; 821 } 822 if (allow) { 823 stat_buf.st_mode |= mode; 824 } else { 825 stat_buf.st_mode &= ~mode; 826 } 827 return chmod(path.value().c_str(), stat_buf.st_mode) == 0; 828 } 829 #endif // defined(OS_MACOSX) 830 831 #if defined(OS_MACOSX) 832 // Linux implementation of FilePathWatcher doesn't catch attribute changes. 833 // http://crbug.com/78043 834 // Windows implementation of FilePathWatcher catches attribute changes that 835 // don't affect the path being watched. 836 // http://crbug.com/78045 837 838 // Verify that changing attributes on a directory works. 839 TEST_F(FilePathWatcherTest, DirAttributesChanged) { 840 FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1")); 841 FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2")); 842 FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile")); 843 // Setup a directory hierarchy. 844 ASSERT_TRUE(base::CreateDirectory(test_dir1)); 845 ASSERT_TRUE(base::CreateDirectory(test_dir2)); 846 ASSERT_TRUE(WriteFile(test_file, "content")); 847 848 FilePathWatcher watcher; 849 scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); 850 ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get(), false)); 851 852 // We should not get notified in this case as it hasn't affected our ability 853 // to access the file. 854 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false)); 855 loop_.PostDelayedTask(FROM_HERE, 856 MessageLoop::QuitWhenIdleClosure(), 857 TestTimeouts::tiny_timeout()); 858 ASSERT_FALSE(WaitForEvents()); 859 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true)); 860 861 // We should get notified in this case because filepathwatcher can no 862 // longer access the file 863 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false)); 864 ASSERT_TRUE(WaitForEvents()); 865 ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true)); 866 DeleteDelegateOnFileThread(delegate.release()); 867 } 868 869 #endif // OS_MACOSX 870 } // namespace 871 872 } // namespace base 873