blob: 5fec06a64d629cfa59edfa785eee1668829706c0 [file] [log] [blame]
//===- unittests/DirectoryWatcher/DirectoryWatcherTest.cpp ----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/DirectoryWatcher/DirectoryWatcher.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <thread>
using namespace llvm;
using namespace llvm::sys;
using namespace llvm::sys::fs;
using namespace clang;
namespace {
class EventCollection {
SmallVector<DirectoryWatcher::Event, 6> Events;
public:
EventCollection() = default;
explicit EventCollection(ArrayRef<DirectoryWatcher::Event> events) {
append(events);
}
void append(ArrayRef<DirectoryWatcher::Event> events) {
Events.append(events.begin(), events.end());
}
bool empty() const { return Events.empty(); }
size_t size() const { return Events.size(); }
void clear() { Events.clear(); }
bool hasEvents(ArrayRef<StringRef> filenames,
ArrayRef<DirectoryWatcher::EventKind> kinds,
ArrayRef<file_status> stats) const {
assert(filenames.size() == kinds.size());
assert(filenames.size() == stats.size());
SmallVector<DirectoryWatcher::Event, 6> evts = Events;
bool hadError = false;
for (unsigned i = 0, e = filenames.size(); i < e; ++i) {
StringRef fname = filenames[i];
DirectoryWatcher::EventKind kind = kinds[i];
file_status stat = stats[i];
auto it = std::find_if(evts.begin(), evts.end(), [&](const DirectoryWatcher::Event &evt)->bool {
return path::filename(evt.Filename) == fname;
});
if (it == evts.end()) {
hadError = err(Twine("expected filename '"+fname+"' not found"));
continue;
}
if (it->Kind != kind) {
hadError = err(Twine("filename '" + fname + "' has event kind " +
std::to_string((int)it->Kind) + ", expected ") +
std::to_string((int)kind));
}
if (it->Kind != DirectoryWatcher::EventKind::Removed &&
it->ModTime != stat.getLastModificationTime())
hadError = err(Twine("filename '"+fname+"' has different mod time"));
evts.erase(it);
}
for (const auto &evt : evts) {
hadError = err(Twine("unexpected filename '"+path::filename(evt.Filename)+"' found"));
}
return !hadError;
}
bool hasAdded(ArrayRef<StringRef> filenames, ArrayRef<file_status> stats) const {
std::vector<DirectoryWatcher::EventKind> kinds{
filenames.size(), DirectoryWatcher::EventKind::Added };
return hasEvents(filenames, kinds, stats);
}
bool hasRemoved(ArrayRef<StringRef> filenames) const {
std::vector<DirectoryWatcher::EventKind> kinds{
filenames.size(), DirectoryWatcher::EventKind::Removed };
std::vector<file_status> stats{ filenames.size(), file_status{} };
return hasEvents(filenames, kinds, stats);
}
private:
bool err(Twine msg) const {
SmallString<128> buf;
llvm::errs() << msg.toStringRef(buf) << '\n';
return true;
}
};
struct EventOccurrence {
std::vector<DirectoryWatcher::Event> Events;
bool IsInitial;
};
class DirectoryWatcherTest: public std::enable_shared_from_this<DirectoryWatcherTest> {
std::string WatchedDir;
std::string TempDir;
std::unique_ptr<DirectoryWatcher> DirWatcher;
std::condition_variable Condition;
std::mutex Mutex;
std::deque<EventOccurrence> EvtOccurs;
public:
void init() {
SmallString<128> pathBuf;
std::error_code EC = createUniqueDirectory("dirwatcher", pathBuf);
ASSERT_FALSE(EC);
TempDir = pathBuf.str();
path::append(pathBuf, "watch");
WatchedDir = pathBuf.str();
EC = create_directory(WatchedDir);
ASSERT_FALSE(EC);
}
~DirectoryWatcherTest() {
stopWatching();
remove_directories(TempDir);
}
public:
StringRef getWatchedDir() const {
return WatchedDir;
}
void addFile(StringRef filename, file_status &stat) {
SmallString<128> pathBuf;
pathBuf = TempDir;
path::append(pathBuf, filename);
Expected<file_t> ft = openNativeFileForWrite(pathBuf, CD_CreateNew, OF_None);
ASSERT_TRUE((bool)ft);
closeFile(*ft);
SmallString<128> newPath;
newPath = WatchedDir;
path::append(newPath, filename);
std::error_code EC = rename(pathBuf, newPath);
ASSERT_FALSE(EC);
EC = status(newPath, stat);
ASSERT_FALSE(EC);
}
void addFiles(ArrayRef<StringRef> filenames, std::vector<file_status> &stats) {
for (auto fname : filenames) {
file_status stat;
addFile(fname, stat);
stats.push_back(stat);
}
}
void addFiles(ArrayRef<StringRef> filenames) {
std::vector<file_status> stats;
addFiles(filenames, stats);
}
void removeFile(StringRef filename) {
SmallString<128> pathBuf;
pathBuf = WatchedDir;
path::append(pathBuf, filename);
std::error_code EC = remove(pathBuf, /*IgnoreNonExisting=*/false);
ASSERT_FALSE(EC);
}
void removeFiles(ArrayRef<StringRef> filenames) {
for (auto fname : filenames) {
removeFile(fname);
}
}
/// \returns true for error.
bool startWatching(bool waitInitialSync) {
std::weak_ptr<DirectoryWatcherTest> weakThis = shared_from_this();
auto receiver = [weakThis](ArrayRef<DirectoryWatcher::Event> events, bool isInitial) {
if (auto this_ = weakThis.lock())
this_->onEvents(events, isInitial);
};
std::string error;
DirWatcher = DirectoryWatcher::create(getWatchedDir(), receiver,
waitInitialSync, error);
return DirWatcher == nullptr;
}
void stopWatching() {
DirWatcher.reset();
}
/// \returns None if the timeout is reached before getting an event.
Optional<EventOccurrence> getNextEvent(unsigned timeout_seconds = 5) {
std::unique_lock<std::mutex> lck(Mutex);
auto pred = [&]()->bool { return !EvtOccurs.empty(); };
bool gotEvent = Condition.wait_for(lck, std::chrono::seconds(timeout_seconds), pred);
if (!gotEvent)
return None;
EventOccurrence occur = EvtOccurs.front();
EvtOccurs.pop_front();
return occur;
}
EventOccurrence getNextEventImmediately() {
std::lock_guard<std::mutex> LG(Mutex);
assert(!EvtOccurs.empty());
EventOccurrence occur = EvtOccurs.front();
EvtOccurs.pop_front();
return occur;
}
private:
void onEvents(ArrayRef<DirectoryWatcher::Event> events, bool isInitial) {
std::lock_guard<std::mutex> LG(Mutex);
EvtOccurs.push_back({events, isInitial});
Condition.notify_all();
}
};
}
TEST(DirectoryWatcherTest, initialScan) {
auto t = std::make_shared<DirectoryWatcherTest>();
t->init();
std::vector<StringRef> fnames = {"a", "b", "c"};
std::vector<file_status> stats;
t->addFiles(fnames, stats);
bool err = t->startWatching(/*waitInitialSync=*/true);
ASSERT_FALSE(err);
auto evt = t->getNextEventImmediately();
EXPECT_TRUE(evt.IsInitial);
EventCollection coll1{evt.Events};
EXPECT_TRUE(coll1.hasAdded(fnames, stats));
StringRef additionalFname = "d";
file_status additionalStat;
t->addFile(additionalFname, additionalStat);
auto evtOpt = t->getNextEvent();
ASSERT_TRUE(evtOpt.hasValue());
EXPECT_FALSE(evtOpt->IsInitial);
EventCollection coll2{evtOpt->Events};
EXPECT_TRUE(coll2.hasAdded({additionalFname}, {additionalStat}));
}
TEST(DirectoryWatcherTest, fileEvents) {
auto t = std::make_shared<DirectoryWatcherTest>();
t->init();
bool err = t->startWatching(/*waitInitialSync=*/false);
ASSERT_FALSE(err);
auto evt = t->getNextEvent();
ASSERT_TRUE(evt.hasValue());
EXPECT_TRUE(evt->IsInitial);
EXPECT_TRUE(evt->Events.empty());
return;
{
std::vector<StringRef> fnames = {"a", "b"};
std::vector<file_status> stats;
t->addFiles(fnames, stats);
EventCollection coll{};
while (coll.size() < 2) {
evt = t->getNextEvent();
ASSERT_TRUE(evt.hasValue());
coll.append(evt->Events);
}
EXPECT_TRUE(coll.hasAdded(fnames, stats));
}
{
std::vector<StringRef> fnames = {"b", "c"};
std::vector<file_status> stats;
t->addFiles(fnames, stats);
EventCollection coll{};
while (coll.size() < 2) {
evt = t->getNextEvent();
ASSERT_TRUE(evt.hasValue());
coll.append(evt->Events);
}
EXPECT_TRUE(coll.hasAdded(fnames, stats));
}
{
std::vector<StringRef> fnames = {"a", "c"};
std::vector<file_status> stats;
t->addFiles(fnames, stats);
t->removeFile("b");
EventCollection coll{};
while (coll.size() < 3) {
evt = t->getNextEvent();
ASSERT_TRUE(evt.hasValue());
coll.append(evt->Events);
}
EXPECT_TRUE(coll.hasEvents(
std::vector<StringRef>{"a", "b", "c"},
std::vector<DirectoryWatcher::EventKind>{
DirectoryWatcher::EventKind::Added,
DirectoryWatcher::EventKind::Removed,
DirectoryWatcher::EventKind::Added,
},
std::vector<file_status>{
stats[0],
file_status{},
stats[1],
}));
}
{
std::vector<StringRef> fnames = {"a", "c"};
t->removeFiles(fnames);
EventCollection coll{};
while (coll.size() < 2) {
evt = t->getNextEvent();
ASSERT_TRUE(evt.hasValue());
coll.append(evt->Events);
}
EXPECT_TRUE(coll.hasRemoved(fnames));
}
}