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