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 34namespace base { 35 36namespace { 37 38class 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. 42class 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 92class 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. 108class 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 129void 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 141class 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 200bool 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. 215TEST_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. 226TEST_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. 240TEST_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 254TEST_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. 269class 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. 292TEST_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 309TEST_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 318TEST_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. 333TEST_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. 359TEST_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 394TEST_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. 409TEST_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 425TEST_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 458TEST_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 485TEST_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) 545TEST_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 586TEST_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 614TEST_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. 629TEST_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. 643TEST_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. 660TEST_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. 676TEST_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. 691TEST_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. 707TEST_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. 736TEST_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. 766TEST_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 794enum Permission { 795 Read, 796 Write, 797 Execute 798}; 799 800#if defined(OS_MACOSX) 801bool 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. 839TEST_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