Index: base/file_watcher_unittest.cc |
diff --git a/base/file_watcher_unittest.cc b/base/file_watcher_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..014488eb638104ec319ca71073897630d7d26253 |
--- /dev/null |
+++ b/base/file_watcher_unittest.cc |
@@ -0,0 +1,261 @@ |
+// Copyright (c) 2008 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/file_watcher.h" |
+ |
+#include <limits> |
+ |
+#include "base/basictypes.h" |
+#include "base/file_path.h" |
+#include "base/file_util.h" |
+#include "base/message_loop.h" |
+#include "base/path_service.h" |
+#include "base/platform_thread.h" |
+#include "base/scoped_temp_dir.h" |
+#include "base/string_util.h" |
+#include "base/thread.h" |
+#if defined(OS_WIN) |
+#include "base/win_util.h" |
+#endif // defined(OS_WIN) |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace { |
+ |
+// For tests where we wait a bit to verify nothing happened |
+const int kWaitForEventTime = 500; |
+ |
+class FileWatcherTest : public testing::Test { |
+ public: |
+ // Implementation of FileWatcher on Mac requires UI loop. |
+ FileWatcherTest() |
+ : loop_(MessageLoop::TYPE_UI), |
+ notified_delegates_(0), |
+ expected_notified_delegates_(0) { |
+ } |
+ |
+ void OnTestDelegateFirstNotification() { |
+ notified_delegates_++; |
+ if (notified_delegates_ >= expected_notified_delegates_) |
+ MessageLoop::current()->Quit(); |
+ } |
+ |
+ protected: |
+ virtual void SetUp() { |
+ temp_dir_.reset(new ScopedTempDir); |
+ ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); |
+ } |
+ |
+ FilePath test_file() { |
+ return temp_dir_->path().AppendASCII("FileWatcherTest"); |
+ } |
+ |
+ virtual void TearDown() { |
+ // Make sure there are no tasks in the loop. |
+ loop_.RunAllPending(); |
+ } |
+ |
+ // Write |content| to the test file. Returns true on success. |
+ bool WriteTestFile(const std::string& content) { |
+ int write_size = file_util::WriteFile(test_file(), content.c_str(), |
+ content.length()); |
+ return write_size == static_cast<int>(content.length()); |
+ } |
+ |
+ void SetExpectedNumberOfNotifiedDelegates(int n) { |
+ notified_delegates_ = 0; |
+ expected_notified_delegates_ = n; |
+ } |
+ |
+ void VerifyExpectedNumberOfNotifiedDelegates() { |
+ // Check that we get at least the expected number of notified delegates. |
+ if (expected_notified_delegates_ - notified_delegates_ > 0) |
+ loop_.Run(); |
+ EXPECT_EQ(expected_notified_delegates_, notified_delegates_); |
+ } |
+ |
+ void VerifyNoExtraNotifications() { |
+ // Check that we get no more than the expected number of notified delegates. |
+ loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, |
+ kWaitForEventTime); |
+ loop_.Run(); |
+ EXPECT_EQ(expected_notified_delegates_, notified_delegates_); |
+ } |
+ |
+ // We need this function for reliable tests on Mac OS X. FSEvents API |
+ // has a latency interval and can merge multiple events into one, |
+ // and we need a clear distinction between events triggered by test setup code |
+ // and test code. |
+ void SyncIfPOSIX() { |
+#if defined(OS_POSIX) |
+ sync(); |
+#endif // defined(OS_POSIX) |
+ } |
+ |
+ MessageLoop loop_; |
+ scoped_ptr<ScopedTempDir> temp_dir_; |
+ |
+ // The number of test delegates which received their notification. |
+ int notified_delegates_; |
+ |
+ // The number of notified test delegates after which we quit the message loop. |
+ int expected_notified_delegates_; |
+}; |
+ |
+class TestDelegate : public FileWatcher::Delegate { |
+ public: |
+ explicit TestDelegate(FileWatcherTest* test) |
+ : test_(test), |
+ got_notification_(false), |
+ original_thread_id_(PlatformThread::CurrentId()) { |
+ } |
+ |
+ bool got_notification() const { |
+ return got_notification_; |
+ } |
+ |
+ void reset() { |
+ got_notification_ = false; |
+ } |
+ |
+ virtual void OnFileChanged(const FilePath& path) { |
+ EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId()); |
+ if (!got_notification_) |
+ test_->OnTestDelegateFirstNotification(); |
+ got_notification_ = true; |
+ } |
+ |
+ private: |
+ // Hold a pointer to current test fixture to inform it on first notification. |
+ FileWatcherTest* test_; |
+ |
+ // Set to true after first notification. |
+ bool got_notification_; |
+ |
+ // Keep track of original thread id to verify that callbacks are called |
+ // on the same thread. |
+ PlatformThreadId original_thread_id_; |
+}; |
+ |
+// Basic test: Create the file and verify that we notice. |
+TEST_F(FileWatcherTest, NewFile) { |
+ FileWatcher watcher; |
+ TestDelegate delegate(this); |
+ ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); |
+ |
+ SetExpectedNumberOfNotifiedDelegates(1); |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ VerifyExpectedNumberOfNotifiedDelegates(); |
+} |
+ |
+// Verify that modifying the file is caught. |
+TEST_F(FileWatcherTest, ModifiedFile) { |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ SyncIfPOSIX(); |
+ |
+ FileWatcher watcher; |
+ TestDelegate delegate(this); |
+ ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); |
+ |
+ // Now make sure we get notified if the file is modified. |
+ SetExpectedNumberOfNotifiedDelegates(1); |
+ ASSERT_TRUE(WriteTestFile("new content")); |
+ VerifyExpectedNumberOfNotifiedDelegates(); |
+} |
+ |
+TEST_F(FileWatcherTest, DeletedFile) { |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ SyncIfPOSIX(); |
+ |
+ FileWatcher watcher; |
+ TestDelegate delegate(this); |
+ ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); |
+ |
+ // Now make sure we get notified if the file is deleted. |
+ SetExpectedNumberOfNotifiedDelegates(1); |
+ file_util::Delete(test_file(), false); |
+ VerifyExpectedNumberOfNotifiedDelegates(); |
+} |
+ |
+// Verify that letting the watcher go out of scope stops notifications. |
+TEST_F(FileWatcherTest, Unregister) { |
+ TestDelegate delegate(this); |
+ |
+ { |
+ FileWatcher watcher; |
+ ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); |
+ |
+ // And then let it fall out of scope, clearing its watch. |
+ } |
+ |
+ // Write a file to the test dir. |
+ SetExpectedNumberOfNotifiedDelegates(0); |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ VerifyExpectedNumberOfNotifiedDelegates(); |
+ VerifyNoExtraNotifications(); |
+} |
+ |
+ |
+namespace { |
+// Used by the DeleteDuringNotify test below. |
+// Deletes the FileWatcher when it's notified. |
+class Deleter : public FileWatcher::Delegate { |
+ public: |
+ Deleter(FileWatcher* watcher, MessageLoop* loop) |
+ : watcher_(watcher), |
+ loop_(loop) { |
+ } |
+ |
+ virtual void OnFileChanged(const FilePath& path) { |
+ watcher_.reset(NULL); |
+ loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); |
+ } |
+ |
+ scoped_ptr<FileWatcher> watcher_; |
+ MessageLoop* loop_; |
+}; |
+} // anonymous namespace |
+ |
+// Verify that deleting a watcher during the callback doesn't crash. |
+TEST_F(FileWatcherTest, DeleteDuringNotify) { |
+ FileWatcher* watcher = new FileWatcher; |
+ Deleter deleter(watcher, &loop_); // Takes ownership of watcher. |
+ ASSERT_TRUE(watcher->Watch(test_file(), &deleter, NULL)); |
+ |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ loop_.Run(); |
+ |
+ // We win if we haven't crashed yet. |
+ // Might as well double-check it got deleted, too. |
+ ASSERT_TRUE(deleter.watcher_.get() == NULL); |
+} |
+ |
+TEST_F(FileWatcherTest, BackendLoop) { |
+ base::Thread thread("test"); |
+ ASSERT_TRUE(thread.Start()); |
+ |
+ FileWatcher watcher; |
+ TestDelegate delegate(this); |
+ ASSERT_TRUE(watcher.Watch(test_file(), &delegate, thread.message_loop())); |
+} |
+ |
+TEST_F(FileWatcherTest, MultipleWatchersSingleFile) { |
+ FileWatcher watcher1, watcher2; |
+ TestDelegate delegate1(this), delegate2(this); |
+ ASSERT_TRUE(watcher1.Watch(test_file(), &delegate1, NULL)); |
+ ASSERT_TRUE(watcher2.Watch(test_file(), &delegate2, NULL)); |
+ |
+ SetExpectedNumberOfNotifiedDelegates(2); |
+ ASSERT_TRUE(WriteTestFile("content")); |
+ VerifyExpectedNumberOfNotifiedDelegates(); |
+} |
+ |
+// Verify that watching a file who's parent directory doesn't exist |
+// fails, but doesn't asssert. |
+TEST_F(FileWatcherTest, NonExistentDirectory) { |
+ FileWatcher watcher; |
+ ASSERT_FALSE(watcher.Watch(test_file().AppendASCII("FileToWatch"), |
+ NULL, NULL)); |
+} |
+ |
+} // namespace |