// 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 "chrome/browser/file_watcher.h" #include #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" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_MACOSX) // TODO(tony): Tests are flaky on mac. http://crbug.com/38188 #define MAYBE(name) FLAKY_ ## name #else #define MAYBE(name) name #endif namespace { // For tests where we wait a bit to verify nothing happened const int kWaitForEventTime = 500; // Maximum amount of time to wait on a test. const int kMaxTestTimeMs = 10 * 1000; class FileWatcherTest : public testing::Test { public: // Implementation of FileWatcher on Mac requires UI loop. FileWatcherTest() : loop_(MessageLoop::TYPE_UI), ui_thread_(ChromeThread::UI, &loop_), file_thread_(ChromeThread::FILE, &loop_), 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()); // Make sure that not getting an event doesn't cause the whole // test suite to hang. loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, kMaxTestTimeMs); } FilePath test_file() { return temp_dir_->path().AppendASCII("FileWatcherTest"); } // Write |content| to the test file. Returns true on success. bool WriteTestFile(const std::string& content) { // Logging to try and figure out why these tests are flaky on mac. LOG(INFO) << "WriteTestFile"; int write_size = file_util::WriteFile(test_file(), content.c_str(), content.length()); SyncIfPOSIX(); return write_size == static_cast(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_; ChromeThread ui_thread_; ChromeThread file_thread_; scoped_ptr 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) { } bool got_notification() const { return got_notification_; } void reset() { got_notification_ = false; } virtual void OnFileChanged(const FilePath& path) { EXPECT_TRUE(ChromeThread::CurrentlyOn(ChromeThread::UI)); 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_; }; // Basic test: Create the file and verify that we notice. TEST_F(FileWatcherTest, MAYBE(NewFile)) { FileWatcher watcher; TestDelegate delegate(this); ASSERT_TRUE(watcher.Watch(test_file(), &delegate)); SetExpectedNumberOfNotifiedDelegates(1); ASSERT_TRUE(WriteTestFile("content")); VerifyExpectedNumberOfNotifiedDelegates(); } // Verify that modifying the file is caught. TEST_F(FileWatcherTest, MAYBE(ModifiedFile)) { ASSERT_TRUE(WriteTestFile("content")); FileWatcher watcher; TestDelegate delegate(this); ASSERT_TRUE(watcher.Watch(test_file(), &delegate)); // Now make sure we get notified if the file is modified. SetExpectedNumberOfNotifiedDelegates(1); ASSERT_TRUE(WriteTestFile("new content")); VerifyExpectedNumberOfNotifiedDelegates(); } TEST_F(FileWatcherTest, MAYBE(DeletedFile)) { ASSERT_TRUE(WriteTestFile("content")); FileWatcher watcher; TestDelegate delegate(this); ASSERT_TRUE(watcher.Watch(test_file(), &delegate)); // Now make sure we get notified if the file is deleted. SetExpectedNumberOfNotifiedDelegates(1); file_util::Delete(test_file(), false); SyncIfPOSIX(); VerifyExpectedNumberOfNotifiedDelegates(); } // Verify that letting the watcher go out of scope stops notifications. TEST_F(FileWatcherTest, MAYBE(Unregister)) { TestDelegate delegate(this); { FileWatcher watcher; ASSERT_TRUE(watcher.Watch(test_file(), &delegate)); // 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 watcher_; MessageLoop* loop_; }; } // anonymous namespace // Verify that deleting a watcher during the callback doesn't crash. TEST_F(FileWatcherTest, MAYBE(DeleteDuringNotify)) { FileWatcher* watcher = new FileWatcher; Deleter deleter(watcher, &loop_); // Takes ownership of watcher. ASSERT_TRUE(watcher->Watch(test_file(), &deleter)); 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, MAYBE(MultipleWatchersSingleFile)) { FileWatcher watcher1, watcher2; TestDelegate delegate1(this), delegate2(this); ASSERT_TRUE(watcher1.Watch(test_file(), &delegate1)); ASSERT_TRUE(watcher2.Watch(test_file(), &delegate2)); 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)); } } // namespace