blob: fabd46fe524b90dcc51350d3dcf19192e6e918e2 [file] [log] [blame]
// 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/directory_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/string_util.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;
} // namespace
class DirectoryWatcherTest : public testing::Test,
public DirectoryWatcher::Delegate {
protected:
virtual void SetUp() {
// Name a subdirectory of the temp directory.
FilePath path;
ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &path));
test_dir_ = path.Append(FILE_PATH_LITERAL("DirectoryWatcherTest"));
// Create a fresh, empty copy of this directory.
file_util::Delete(test_dir_, true);
file_util::CreateDirectory(test_dir_);
directory_mods_ = 0;
quit_mod_count_ = 0;
original_thread_id_ = PlatformThread::CurrentId();
}
virtual void OnDirectoryChanged(const FilePath& path) {
EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId());
++directory_mods_;
// The exact number is verified by VerifyExpectedNumberOfModifications.
// Sometimes we don't want such check, see WaitForFirstNotification.
if (directory_mods_ >= quit_mod_count_)
MessageLoop::current()->Quit();
}
virtual void TearDown() {
// Make sure there are no tasks in the loop.
loop_.RunAllPending();
// Clean up test directory.
ASSERT_TRUE(file_util::Delete(test_dir_, true));
ASSERT_FALSE(file_util::PathExists(test_dir_));
}
// Write |content| to a file under the test directory.
void WriteTestDirFile(const FilePath::StringType& filename,
const std::string& content) {
FilePath path = test_dir_.Append(filename);
file_util::WriteFile(path, content.c_str(), content.length());
}
void SetExpectedNumberOfModifications(int n) {
quit_mod_count_ = n;
}
void VerifyExpectedNumberOfModifications() {
// Check that we get at least the expected number of notifications.
if (quit_mod_count_ - directory_mods_ > 0)
loop_.Run();
// Check that we get no more than the expected number of notifications.
loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
kWaitForEventTime);
loop_.Run();
EXPECT_EQ(quit_mod_count_, directory_mods_);
}
// Only use this function if you don't care about getting
// too many notifications. Useful for tests where you get
// different number of notifications on different platforms.
void WaitForFirstNotification() {
directory_mods_ = 0;
SetExpectedNumberOfModifications(1);
loop_.Run();
}
MessageLoop loop_;
// The path to a temporary directory used for testing.
FilePath test_dir_;
// The number of times a directory modification has been observed.
int directory_mods_;
// The number of directory mods which, when reached, cause us to quit
// our message loop.
int quit_mod_count_;
// Keep track of original thread id to verify that callbacks are called
// on the same thread.
PlatformThreadId original_thread_id_;
};
// Basic test: add a file and verify we notice it.
TEST_F(DirectoryWatcherTest, NewFile) {
DirectoryWatcher watcher;
ASSERT_TRUE(watcher.Watch(test_dir_, this, false));
SetExpectedNumberOfModifications(2);
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
VerifyExpectedNumberOfModifications();
}
// Verify that modifying a file is caught.
TEST_F(DirectoryWatcherTest, ModifiedFile) {
DirectoryWatcher watcher;
ASSERT_TRUE(watcher.Watch(test_dir_, this, false));
// Write a file to the test dir.
SetExpectedNumberOfModifications(2);
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
VerifyExpectedNumberOfModifications();
// Now make sure we get notified if the file is modified.
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some new content");
// Use a more forgiving function to check because on Linux you will get
// 1 notification, and on Windows 2 (and nothing seems to convince it to
// send less notifications).
WaitForFirstNotification();
}
// Verify that letting the watcher go out of scope stops notifications.
TEST_F(DirectoryWatcherTest, Unregister) {
{
DirectoryWatcher watcher;
ASSERT_TRUE(watcher.Watch(test_dir_, this, false));
// And then let it fall out of scope, clearing its watch.
}
// Write a file to the test dir.
SetExpectedNumberOfModifications(0);
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
VerifyExpectedNumberOfModifications();
}
TEST_F(DirectoryWatcherTest, SubDirRecursive) {
FilePath subdir(FILE_PATH_LITERAL("SubDir"));
ASSERT_TRUE(file_util::CreateDirectory(test_dir_.Append(subdir)));
#if !defined(OS_WIN)
// TODO(port): Recursive watches are not implemented on Linux.
return;
#endif // !defined(OS_WIN)
// Verify that modifications to a subdirectory are noticed by recursive watch.
DirectoryWatcher watcher;
ASSERT_TRUE(watcher.Watch(test_dir_, this, true));
// Write a file to the subdir.
SetExpectedNumberOfModifications(2);
FilePath test_path = subdir.AppendASCII("test_file");
WriteTestDirFile(test_path.value(), "some content");
VerifyExpectedNumberOfModifications();
}
TEST_F(DirectoryWatcherTest, SubDirNonRecursive) {
#if defined(OS_WIN)
// Disable this test for earlier version of Windows. It turned out to be
// very difficult to create a reliable test for them.
if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA)
return;
#endif // defined(OS_WIN)
FilePath subdir(FILE_PATH_LITERAL("SubDir"));
ASSERT_TRUE(file_util::CreateDirectory(test_dir_.Append(subdir)));
// Create a test file before the test. On Windows we get a notification
// when creating a file in a subdir even with a non-recursive watch.
FilePath test_path = subdir.AppendASCII("test_file");
WriteTestDirFile(test_path.value(), "some content");
// Verify that modifications to a subdirectory are not noticed
// by a not-recursive watch.
DirectoryWatcher watcher;
ASSERT_TRUE(watcher.Watch(test_dir_, this, false));
// Modify the test file. There should be no notifications.
SetExpectedNumberOfModifications(0);
WriteTestDirFile(test_path.value(), "some other content");
VerifyExpectedNumberOfModifications();
}
namespace {
// Used by the DeleteDuringNotify test below.
// Deletes the DirectoryWatcher when it's notified.
class Deleter : public DirectoryWatcher::Delegate {
public:
Deleter(DirectoryWatcher* watcher, MessageLoop* loop)
: watcher_(watcher),
loop_(loop) {
}
virtual void OnDirectoryChanged(const FilePath& path) {
watcher_.reset(NULL);
loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
}
scoped_ptr<DirectoryWatcher> watcher_;
MessageLoop* loop_;
};
} // anonymous namespace
// Verify that deleting a watcher during the callback
TEST_F(DirectoryWatcherTest, DeleteDuringNotify) {
DirectoryWatcher* watcher = new DirectoryWatcher;
Deleter deleter(watcher, &loop_); // Takes ownership of watcher.
ASSERT_TRUE(watcher->Watch(test_dir_, &deleter, false));
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some 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(DirectoryWatcherTest, MultipleWatchersSingleFile) {
DirectoryWatcher watcher1, watcher2;
ASSERT_TRUE(watcher1.Watch(test_dir_, this, false));
ASSERT_TRUE(watcher2.Watch(test_dir_, this, false));
SetExpectedNumberOfModifications(4); // Each watcher should fire twice.
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
VerifyExpectedNumberOfModifications();
}
TEST_F(DirectoryWatcherTest, MultipleWatchersDifferentFiles) {
const int kNumberOfWatchers = 5;
DirectoryWatcher watchers[kNumberOfWatchers];
FilePath subdirs[kNumberOfWatchers];
for (int i = 0; i < kNumberOfWatchers; i++) {
subdirs[i] = FilePath(FILE_PATH_LITERAL("Dir")).AppendASCII(IntToString(i));
ASSERT_TRUE(file_util::CreateDirectory(test_dir_.Append(subdirs[i])));
ASSERT_TRUE(watchers[i].Watch(test_dir_.Append(subdirs[i]), this, false));
}
for (int i = 0; i < kNumberOfWatchers; i++) {
// Verify that we only get modifications from one watcher (each watcher has
// different directory).
ASSERT_EQ(0, directory_mods_);
// Write a file to the subdir.
FilePath test_path = subdirs[i].AppendASCII("test_file");
SetExpectedNumberOfModifications(2);
WriteTestDirFile(test_path.value(), "some content");
VerifyExpectedNumberOfModifications();
directory_mods_ = 0;
}
}
// Verify that watching a directory that doesn't exist fails, but doesn't
// asssert.
// Basic test: add a file and verify we notice it.
TEST_F(DirectoryWatcherTest, NonExistentDirectory) {
DirectoryWatcher watcher;
ASSERT_FALSE(watcher.Watch(test_dir_.AppendASCII("does-not-exist"), this,
false));
}