1ebb20e1d8
Review URL: http://codereview.chromium.org/155022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19974 0039d316-1c4b-4281-b951-d872f2087c98
415 linhas
14 KiB
C++
415 linhas
14 KiB
C++
// 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"
|
|
#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 = 1000;
|
|
|
|
class DirectoryWatcherTest : public testing::Test {
|
|
public:
|
|
// Implementation of DirectoryWatcher on Mac requires UI loop.
|
|
DirectoryWatcherTest()
|
|
: loop_(MessageLoop::TYPE_UI),
|
|
notified_delegates_(0),
|
|
expected_notified_delegates_(0) {
|
|
}
|
|
|
|
void OnTestDelegateFirstNotification(const FilePath& path) {
|
|
notified_delegates_++;
|
|
if (notified_delegates_ >= expected_notified_delegates_)
|
|
MessageLoop::current()->Quit();
|
|
}
|
|
|
|
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_);
|
|
}
|
|
|
|
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 the |filename|. Returns true on success.
|
|
bool WriteTestFile(const FilePath& filename,
|
|
const std::string& content) {
|
|
return (file_util::WriteFile(filename, content.c_str(), content.length()) ==
|
|
static_cast<int>(content.length()));
|
|
}
|
|
|
|
// Create directory |name| under test_dir_. If |sync| is true, runs
|
|
// SyncIfPOSIX. Returns path to the created directory, including test_dir_.
|
|
FilePath CreateTestDirDirectoryASCII(const std::string& name, bool sync) {
|
|
FilePath path(test_dir_.AppendASCII(name));
|
|
EXPECT_TRUE(file_util::CreateDirectory(path));
|
|
if (sync)
|
|
SyncIfPOSIX();
|
|
return path;
|
|
}
|
|
|
|
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();
|
|
|
|
// 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_;
|
|
|
|
// The path to a temporary directory used for testing.
|
|
FilePath test_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 DirectoryWatcher::Delegate {
|
|
public:
|
|
TestDelegate(DirectoryWatcherTest* 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 OnDirectoryChanged(const FilePath& path) {
|
|
EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId());
|
|
if (!got_notification_)
|
|
test_->OnTestDelegateFirstNotification(path);
|
|
got_notification_ = true;
|
|
}
|
|
|
|
private:
|
|
// Hold a pointer to current test fixture to inform it on first notification.
|
|
DirectoryWatcherTest* 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: add a file and verify we notice it.
|
|
TEST_F(DirectoryWatcherTest, NewFile) {
|
|
DirectoryWatcher watcher;
|
|
TestDelegate delegate(this);
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
// Verify that modifying a file is caught.
|
|
TEST_F(DirectoryWatcherTest, ModifiedFile) {
|
|
// Write a file to the test dir.
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
|
SyncIfPOSIX();
|
|
|
|
DirectoryWatcher watcher;
|
|
TestDelegate delegate(this);
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
|
|
|
|
// Now make sure we get notified if the file is modified.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "new content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, DeletedFile) {
|
|
// Write a file to the test dir.
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
|
SyncIfPOSIX();
|
|
|
|
DirectoryWatcher watcher;
|
|
TestDelegate delegate(this);
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
|
|
|
|
// Now make sure we get notified if the file is deleted.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(file_util::Delete(test_dir_.AppendASCII("test_file"), false));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
// Verify that letting the watcher go out of scope stops notifications.
|
|
TEST_F(DirectoryWatcherTest, Unregister) {
|
|
TestDelegate delegate(this);
|
|
|
|
{
|
|
DirectoryWatcher watcher;
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
|
|
|
|
// And then let it fall out of scope, clearing its watch.
|
|
}
|
|
|
|
// Write a file to the test dir.
|
|
SetExpectedNumberOfNotifiedDelegates(0);
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, SubDirRecursive) {
|
|
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
|
|
|
// Verify that modifications to a subdirectory are noticed by recursive watch.
|
|
TestDelegate delegate(this);
|
|
DirectoryWatcher watcher;
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
|
|
// Write a file to the subdir.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
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(CreateTestDirDirectoryASCII("SubDir", false));
|
|
|
|
// 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.
|
|
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
|
|
|
SyncIfPOSIX();
|
|
|
|
// Verify that modifications to a subdirectory are not noticed
|
|
// by a not-recursive watch.
|
|
DirectoryWatcher watcher;
|
|
TestDelegate delegate(this);
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, false));
|
|
|
|
// Modify the test file. There should be no notifications.
|
|
SetExpectedNumberOfNotifiedDelegates(0);
|
|
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "other content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
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, NULL, false));
|
|
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "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, BackendLoop) {
|
|
base::Thread thread("test");
|
|
ASSERT_TRUE(thread.Start());
|
|
|
|
DirectoryWatcher watcher;
|
|
TestDelegate delegate(this);
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, thread.message_loop(),
|
|
true));
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, MultipleWatchersSingleFile) {
|
|
DirectoryWatcher watcher1, watcher2;
|
|
TestDelegate delegate1(this), delegate2(this);
|
|
ASSERT_TRUE(watcher1.Watch(test_dir_, &delegate1, NULL, false));
|
|
ASSERT_TRUE(watcher2.Watch(test_dir_, &delegate2, NULL, false));
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(2);
|
|
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, MultipleWatchersDifferentFiles) {
|
|
const int kNumberOfWatchers = 5;
|
|
DirectoryWatcher watchers[kNumberOfWatchers];
|
|
TestDelegate delegates[kNumberOfWatchers] = {this, this, this, this, this};
|
|
FilePath subdirs[kNumberOfWatchers];
|
|
for (int i = 0; i < kNumberOfWatchers; i++) {
|
|
subdirs[i] = CreateTestDirDirectoryASCII("Dir" + IntToString(i), false);
|
|
ASSERT_TRUE(watchers[i].Watch(subdirs[i], &delegates[i],
|
|
NULL, ((i % 2) == 0)));
|
|
}
|
|
for (int i = 0; i < kNumberOfWatchers; i++) {
|
|
// Verify that we only get modifications from one watcher (each watcher has
|
|
// different directory).
|
|
|
|
for (int j = 0; j < kNumberOfWatchers; j++)
|
|
delegates[j].reset();
|
|
|
|
// Write a file to the subdir.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdirs[i].AppendASCII("test_file"), "content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
|
|
loop_.RunAllPending();
|
|
}
|
|
}
|
|
|
|
#if defined(OS_WIN) || defined(OS_MACOSX)
|
|
// TODO(phajdan.jr): Enable when support for Linux recursive watches is added.
|
|
|
|
TEST_F(DirectoryWatcherTest, WatchCreatedDirectory) {
|
|
TestDelegate delegate(this);
|
|
DirectoryWatcher watcher;
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
|
|
delegate.reset();
|
|
|
|
// Verify that changes inside the subdir are noticed.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, RecursiveWatchDeletedSubdirectory) {
|
|
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
|
|
|
TestDelegate delegate(this);
|
|
DirectoryWatcher watcher;
|
|
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, NULL, true));
|
|
|
|
// Write a file to the subdir.
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
|
|
delegate.reset();
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(file_util::Delete(subdir, true));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
|
|
TEST_F(DirectoryWatcherTest, MoveFileAcrossWatches) {
|
|
FilePath subdir1(CreateTestDirDirectoryASCII("SubDir1", true));
|
|
FilePath subdir2(CreateTestDirDirectoryASCII("SubDir2", true));
|
|
|
|
TestDelegate delegate1(this), delegate2(this);
|
|
DirectoryWatcher watcher1, watcher2;
|
|
ASSERT_TRUE(watcher1.Watch(subdir1, &delegate1, NULL, true));
|
|
ASSERT_TRUE(watcher2.Watch(subdir2, &delegate2, NULL, true));
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdir1.AppendASCII("file"), "some content"));
|
|
SyncIfPOSIX();
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
|
|
delegate1.reset();
|
|
delegate2.reset();
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(2);
|
|
ASSERT_TRUE(file_util::Move(subdir1.AppendASCII("file"),
|
|
subdir2.AppendASCII("file")));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
|
|
delegate1.reset();
|
|
delegate2.reset();
|
|
|
|
SetExpectedNumberOfNotifiedDelegates(1);
|
|
ASSERT_TRUE(WriteTestFile(subdir2.AppendASCII("file"), "other content"));
|
|
VerifyExpectedNumberOfNotifiedDelegates();
|
|
}
|
|
#endif // defined(OS_WIN) || defined(OS_MACOSX)
|
|
|
|
// 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"),
|
|
NULL, NULL, false));
|
|
}
|
|
|
|
} // namespace
|