Home | History | Annotate | Download | only in base
      1 // Copyright (c) 2008 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/directory_watcher.h"
      6 
      7 #include <limits>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/file_path.h"
     11 #include "base/file_util.h"
     12 #include "base/message_loop.h"
     13 #include "base/path_service.h"
     14 #include "base/platform_thread.h"
     15 #include "base/string_util.h"
     16 #include "base/thread.h"
     17 #if defined(OS_WIN)
     18 #include "base/win_util.h"
     19 #endif  // defined(OS_WIN)
     20 #include "testing/gtest/include/gtest/gtest.h"
     21 
     22 namespace {
     23 
     24 // For tests where we wait a bit to verify nothing happened
     25 const int kWaitForEventTime = 500;
     26 
     27 class DirectoryWatcherTest : public testing::Test {
     28  public:
     29   // Implementation of DirectoryWatcher on Mac requires UI loop.
     30   DirectoryWatcherTest()
     31       : loop_(MessageLoop::TYPE_UI),
     32         notified_delegates_(0),
     33         expected_notified_delegates_(0) {
     34   }
     35 
     36   void OnTestDelegateFirstNotification(const FilePath& path) {
     37     notified_delegates_++;
     38     if (notified_delegates_ >= expected_notified_delegates_)
     39       MessageLoop::current()->Quit();
     40   }
     41 
     42  protected:
     43   virtual void SetUp() {
     44     // Name a subdirectory of the temp directory.
     45     FilePath path;
     46     ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &path));
     47     test_dir_ = path.Append(FILE_PATH_LITERAL("DirectoryWatcherTest"));
     48 
     49     // Create a fresh, empty copy of this directory.
     50     file_util::Delete(test_dir_, true);
     51     file_util::CreateDirectory(test_dir_);
     52   }
     53 
     54   virtual void TearDown() {
     55     // Make sure there are no tasks in the loop.
     56     loop_.RunAllPending();
     57 
     58     // Clean up test directory.
     59     ASSERT_TRUE(file_util::Delete(test_dir_, true));
     60     ASSERT_FALSE(file_util::PathExists(test_dir_));
     61   }
     62 
     63   // Write |content| to the |filename|. Returns true on success.
     64   bool WriteTestFile(const FilePath& filename,
     65                      const std::string& content) {
     66     return (file_util::WriteFile(filename, content.c_str(), content.length()) ==
     67             static_cast<int>(content.length()));
     68   }
     69 
     70   // Create directory |name| under test_dir_. If |sync| is true, runs
     71   // SyncIfPOSIX. Returns path to the created directory, including test_dir_.
     72   FilePath CreateTestDirDirectoryASCII(const std::string& name, bool sync) {
     73     FilePath path(test_dir_.AppendASCII(name));
     74     EXPECT_TRUE(file_util::CreateDirectory(path));
     75     if (sync)
     76       SyncIfPOSIX();
     77     return path;
     78   }
     79 
     80   void SetExpectedNumberOfNotifiedDelegates(int n) {
     81     notified_delegates_ = 0;
     82     expected_notified_delegates_ = n;
     83   }
     84 
     85   void VerifyExpectedNumberOfNotifiedDelegates() {
     86     // Check that we get at least the expected number of notified delegates.
     87     if (expected_notified_delegates_ - notified_delegates_ > 0)
     88       loop_.Run();
     89     EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
     90   }
     91 
     92   void VerifyNoExtraNotifications() {
     93     // Check that we get no more than the expected number of notified delegates.
     94     loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
     95                           kWaitForEventTime);
     96     loop_.Run();
     97     EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
     98   }
     99 
    100   // We need this function for reliable tests on Mac OS X. FSEvents API
    101   // has a latency interval and can merge multiple events into one,
    102   // and we need a clear distinction between events triggered by test setup code
    103   // and test code.
    104   void SyncIfPOSIX() {
    105 #if defined(OS_POSIX)
    106     sync();
    107 #endif  // defined(OS_POSIX)
    108   }
    109 
    110   MessageLoop loop_;
    111 
    112   // The path to a temporary directory used for testing.
    113   FilePath test_dir_;
    114 
    115   // The number of test delegates which received their notification.
    116   int notified_delegates_;
    117 
    118   // The number of notified test delegates after which we quit the message loop.
    119   int expected_notified_delegates_;
    120 };
    121 
    122 class TestDelegate : public DirectoryWatcher::Delegate {
    123  public:
    124   explicit TestDelegate(DirectoryWatcherTest* test)
    125       : test_(test),
    126         got_notification_(false),
    127         original_thread_id_(PlatformThread::CurrentId()) {
    128   }
    129 
    130   bool got_notification() const {
    131     return got_notification_;
    132   }
    133 
    134   void reset() {
    135     got_notification_ = false;
    136   }
    137 
    138   virtual void OnDirectoryChanged(const FilePath& path) {
    139     EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId());
    140     if (!got_notification_)
    141       test_->OnTestDelegateFirstNotification(path);
    142     got_notification_ = true;
    143   }
    144 
    145  private:
    146   // Hold a pointer to current test fixture to inform it on first notification.
    147   DirectoryWatcherTest* test_;
    148 
    149   // Set to true after first notification.
    150   bool got_notification_;
    151 
    152   // Keep track of original thread id to verify that callbacks are called
    153   // on the same thread.
    154   PlatformThreadId original_thread_id_;
    155 };
    156 
    157 // Basic test: add a file and verify we notice it.
    158 TEST_F(DirectoryWatcherTest, NewFile) {
    159   DirectoryWatcher watcher;
    160   TestDelegate delegate(this);
    161   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
    162 
    163   SetExpectedNumberOfNotifiedDelegates(1);
    164   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    165   VerifyExpectedNumberOfNotifiedDelegates();
    166 }
    167 
    168 // Verify that modifying a file is caught.
    169 TEST_F(DirectoryWatcherTest, ModifiedFile) {
    170   // Write a file to the test dir.
    171   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    172   SyncIfPOSIX();
    173 
    174   DirectoryWatcher watcher;
    175   TestDelegate delegate(this);
    176   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
    177 
    178   // Now make sure we get notified if the file is modified.
    179   SetExpectedNumberOfNotifiedDelegates(1);
    180   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "new content"));
    181   VerifyExpectedNumberOfNotifiedDelegates();
    182 }
    183 
    184 TEST_F(DirectoryWatcherTest, DeletedFile) {
    185   // Write a file to the test dir.
    186   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    187   SyncIfPOSIX();
    188 
    189   DirectoryWatcher watcher;
    190   TestDelegate delegate(this);
    191   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
    192 
    193   // Now make sure we get notified if the file is deleted.
    194   SetExpectedNumberOfNotifiedDelegates(1);
    195   ASSERT_TRUE(file_util::Delete(test_dir_.AppendASCII("test_file"), false));
    196   VerifyExpectedNumberOfNotifiedDelegates();
    197 }
    198 
    199 // Verify that letting the watcher go out of scope stops notifications.
    200 TEST_F(DirectoryWatcherTest, Unregister) {
    201   TestDelegate delegate(this);
    202 
    203   {
    204     DirectoryWatcher watcher;
    205     ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
    206 
    207     // And then let it fall out of scope, clearing its watch.
    208   }
    209 
    210   // Write a file to the test dir.
    211   SetExpectedNumberOfNotifiedDelegates(0);
    212   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    213   VerifyExpectedNumberOfNotifiedDelegates();
    214   VerifyNoExtraNotifications();
    215 }
    216 
    217 TEST_F(DirectoryWatcherTest, SubDirRecursive) {
    218   FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
    219 
    220   // Verify that modifications to a subdirectory are noticed by recursive watch.
    221   TestDelegate delegate(this);
    222   DirectoryWatcher watcher;
    223   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
    224   // Write a file to the subdir.
    225   SetExpectedNumberOfNotifiedDelegates(1);
    226   ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
    227   VerifyExpectedNumberOfNotifiedDelegates();
    228 }
    229 
    230 TEST_F(DirectoryWatcherTest, SubDirNonRecursive) {
    231 #if defined(OS_WIN)
    232   // Disable this test for earlier version of Windows. It turned out to be
    233   // very difficult to create a reliable test for them.
    234   if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA)
    235     return;
    236 #endif  // defined(OS_WIN)
    237 
    238   FilePath subdir(CreateTestDirDirectoryASCII("SubDir", false));
    239 
    240   // Create a test file before the test. On Windows we get a notification
    241   // when creating a file in a subdir even with a non-recursive watch.
    242   ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
    243 
    244   SyncIfPOSIX();
    245 
    246   // Verify that modifications to a subdirectory are not noticed
    247   // by a not-recursive watch.
    248   DirectoryWatcher watcher;
    249   TestDelegate delegate(this);
    250   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
    251 
    252   // Modify the test file. There should be no notifications.
    253   SetExpectedNumberOfNotifiedDelegates(0);
    254   ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "other content"));
    255   VerifyExpectedNumberOfNotifiedDelegates();
    256   VerifyNoExtraNotifications();
    257 }
    258 
    259 namespace {
    260 // Used by the DeleteDuringNotify test below.
    261 // Deletes the DirectoryWatcher when it's notified.
    262 class Deleter : public DirectoryWatcher::Delegate {
    263  public:
    264   Deleter(DirectoryWatcher* watcher, MessageLoop* loop)
    265       : watcher_(watcher),
    266         loop_(loop) {
    267   }
    268 
    269   virtual void OnDirectoryChanged(const FilePath& path) {
    270     watcher_.reset(NULL);
    271     loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
    272   }
    273 
    274   scoped_ptr<DirectoryWatcher> watcher_;
    275   MessageLoop* loop_;
    276 };
    277 }  // anonymous namespace
    278 
    279 // Verify that deleting a watcher during the callback
    280 TEST_F(DirectoryWatcherTest, DeleteDuringNotify) {
    281   DirectoryWatcher* watcher = new DirectoryWatcher;
    282   Deleter deleter(watcher, &loop_);  // Takes ownership of watcher.
    283   ASSERT_TRUE(watcher->Watch(test_dir_, &deleter, NULL, false));
    284 
    285   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    286   loop_.Run();
    287 
    288   // We win if we haven't crashed yet.
    289   // Might as well double-check it got deleted, too.
    290   ASSERT_TRUE(deleter.watcher_.get() == NULL);
    291 }
    292 
    293 TEST_F(DirectoryWatcherTest, BackendLoop) {
    294   base::Thread thread("test");
    295   ASSERT_TRUE(thread.Start());
    296 
    297   DirectoryWatcher watcher;
    298   TestDelegate delegate(this);
    299   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, thread.message_loop(),
    300                             true));
    301 }
    302 
    303 TEST_F(DirectoryWatcherTest, MultipleWatchersSingleFile) {
    304   DirectoryWatcher watcher1, watcher2;
    305   TestDelegate delegate1(this), delegate2(this);
    306   ASSERT_TRUE(watcher1.Watch(test_dir_, &delegate1, NULL, false));
    307   ASSERT_TRUE(watcher2.Watch(test_dir_, &delegate2, NULL, false));
    308 
    309   SetExpectedNumberOfNotifiedDelegates(2);
    310   ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
    311   VerifyExpectedNumberOfNotifiedDelegates();
    312 }
    313 
    314 TEST_F(DirectoryWatcherTest, MultipleWatchersDifferentFiles) {
    315   const int kNumberOfWatchers = 3;
    316   DirectoryWatcher watchers[kNumberOfWatchers];
    317   TestDelegate delegates[kNumberOfWatchers] = {
    318     TestDelegate(this),
    319     TestDelegate(this),
    320     TestDelegate(this),
    321   };
    322   FilePath subdirs[kNumberOfWatchers];
    323   for (int i = 0; i < kNumberOfWatchers; i++) {
    324     subdirs[i] = CreateTestDirDirectoryASCII("Dir" + IntToString(i), false);
    325     ASSERT_TRUE(watchers[i].Watch(subdirs[i], &delegates[i],
    326                                   NULL, ((i % 2) == 0)));
    327   }
    328   for (int i = 0; i < kNumberOfWatchers; i++) {
    329     // Verify that we only get modifications from one watcher (each watcher has
    330     // different directory).
    331 
    332     for (int j = 0; j < kNumberOfWatchers; j++)
    333       delegates[j].reset();
    334 
    335     // Write a file to the subdir.
    336     SetExpectedNumberOfNotifiedDelegates(1);
    337     ASSERT_TRUE(WriteTestFile(subdirs[i].AppendASCII("test_file"), "content"));
    338     VerifyExpectedNumberOfNotifiedDelegates();
    339     VerifyNoExtraNotifications();
    340 
    341     loop_.RunAllPending();
    342   }
    343 }
    344 
    345 #if defined(OS_WIN) || defined(OS_MACOSX)
    346 // TODO(phajdan.jr): Enable when support for Linux recursive watches is added.
    347 
    348 TEST_F(DirectoryWatcherTest, WatchCreatedDirectory) {
    349   TestDelegate delegate(this);
    350   DirectoryWatcher watcher;
    351   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
    352 
    353   SetExpectedNumberOfNotifiedDelegates(1);
    354   FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
    355   VerifyExpectedNumberOfNotifiedDelegates();
    356 
    357   delegate.reset();
    358 
    359   // Verify that changes inside the subdir are noticed.
    360   SetExpectedNumberOfNotifiedDelegates(1);
    361   ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
    362   VerifyExpectedNumberOfNotifiedDelegates();
    363 }
    364 
    365 TEST_F(DirectoryWatcherTest, RecursiveWatchDeletedSubdirectory) {
    366   FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
    367 
    368   TestDelegate delegate(this);
    369   DirectoryWatcher watcher;
    370   ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
    371 
    372   // Write a file to the subdir.
    373   SetExpectedNumberOfNotifiedDelegates(1);
    374   ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
    375   VerifyExpectedNumberOfNotifiedDelegates();
    376 
    377   delegate.reset();
    378 
    379   SetExpectedNumberOfNotifiedDelegates(1);
    380   ASSERT_TRUE(file_util::Delete(subdir, true));
    381   VerifyExpectedNumberOfNotifiedDelegates();
    382 }
    383 
    384 TEST_F(DirectoryWatcherTest, MoveFileAcrossWatches) {
    385   FilePath subdir1(CreateTestDirDirectoryASCII("SubDir1", true));
    386   FilePath subdir2(CreateTestDirDirectoryASCII("SubDir2", true));
    387 
    388   TestDelegate delegate1(this), delegate2(this);
    389   DirectoryWatcher watcher1, watcher2;
    390   ASSERT_TRUE(watcher1.Watch(subdir1, &delegate1, NULL, true));
    391   ASSERT_TRUE(watcher2.Watch(subdir2, &delegate2, NULL, true));
    392 
    393   SetExpectedNumberOfNotifiedDelegates(1);
    394   ASSERT_TRUE(WriteTestFile(subdir1.AppendASCII("file"), "some content"));
    395   SyncIfPOSIX();
    396   VerifyExpectedNumberOfNotifiedDelegates();
    397   VerifyNoExtraNotifications();
    398 
    399   delegate1.reset();
    400   delegate2.reset();
    401 
    402   SetExpectedNumberOfNotifiedDelegates(2);
    403   ASSERT_TRUE(file_util::Move(subdir1.AppendASCII("file"),
    404                               subdir2.AppendASCII("file")));
    405   VerifyExpectedNumberOfNotifiedDelegates();
    406 
    407   delegate1.reset();
    408   delegate2.reset();
    409 
    410   SetExpectedNumberOfNotifiedDelegates(1);
    411   ASSERT_TRUE(WriteTestFile(subdir2.AppendASCII("file"), "other content"));
    412   VerifyExpectedNumberOfNotifiedDelegates();
    413   VerifyNoExtraNotifications();
    414 }
    415 #endif  // defined(OS_WIN) || defined(OS_MACOSX)
    416 
    417 // Verify that watching a directory that doesn't exist fails, but doesn't
    418 // asssert.
    419 // Basic test: add a file and verify we notice it.
    420 TEST_F(DirectoryWatcherTest, NonExistentDirectory) {
    421   DirectoryWatcher watcher;
    422   ASSERT_FALSE(watcher.Watch(test_dir_.AppendASCII("does-not-exist"),
    423                              NULL, NULL, false));
    424 }
    425 
    426 }  // namespace
    427