123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- //===- unittests/DirectoryWatcher/DirectoryWatcherTest.cpp ----------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- #include "clang/DirectoryWatcher/DirectoryWatcher.h"
- #include "llvm/Support/FileSystem.h"
- #include "llvm/Support/Path.h"
- #include "llvm/Support/raw_ostream.h"
- #include "llvm/Testing/Support/Error.h"
- #include "gtest/gtest.h"
- #include <condition_variable>
- #include <future>
- #include <mutex>
- #include <thread>
- using namespace llvm;
- using namespace llvm::sys;
- using namespace llvm::sys::fs;
- using namespace clang;
- namespace clang {
- static bool operator==(const DirectoryWatcher::Event &lhs,
- const DirectoryWatcher::Event &rhs) {
- return lhs.Filename == rhs.Filename &&
- static_cast<int>(lhs.Kind) == static_cast<int>(rhs.Kind);
- }
- } // namespace clang
- namespace {
- typedef DirectoryWatcher::Event::EventKind EventKind;
- struct DirectoryWatcherTestFixture {
- std::string TestRootDir;
- std::string TestWatchedDir;
- DirectoryWatcherTestFixture() {
- SmallString<128> pathBuf;
- #ifndef NDEBUG
- std::error_code UniqDirRes =
- #endif
- createUniqueDirectory("dirwatcher", pathBuf);
- assert(!UniqDirRes);
- TestRootDir = pathBuf.str();
- path::append(pathBuf, "watch");
- TestWatchedDir = pathBuf.str();
- #ifndef NDEBUG
- std::error_code CreateDirRes =
- #endif
- create_directory(TestWatchedDir, false);
- assert(!CreateDirRes);
- }
- ~DirectoryWatcherTestFixture() { remove_directories(TestRootDir); }
- SmallString<128> getPathInWatched(const std::string &testFile) {
- SmallString<128> pathBuf;
- pathBuf = TestWatchedDir;
- path::append(pathBuf, testFile);
- return pathBuf;
- }
- void addFile(const std::string &testFile) {
- Expected<file_t> ft = openNativeFileForWrite(getPathInWatched(testFile),
- CD_CreateNew, OF_None);
- if (ft) {
- closeFile(*ft);
- } else {
- llvm::errs() << llvm::toString(ft.takeError()) << "\n";
- llvm::errs() << getPathInWatched(testFile) << "\n";
- llvm_unreachable("Couldn't create test file.");
- }
- }
- void deleteFile(const std::string &testFile) {
- std::error_code EC =
- remove(getPathInWatched(testFile), /*IgnoreNonExisting=*/false);
- ASSERT_FALSE(EC);
- }
- };
- std::string eventKindToString(const EventKind K) {
- switch (K) {
- case EventKind::Removed:
- return "Removed";
- case EventKind::Modified:
- return "Modified";
- case EventKind::WatchedDirRemoved:
- return "WatchedDirRemoved";
- case EventKind::WatcherGotInvalidated:
- return "WatcherGotInvalidated";
- }
- llvm_unreachable("unknown event kind");
- }
- struct VerifyingConsumer {
- std::vector<DirectoryWatcher::Event> ExpectedInitial;
- const std::vector<DirectoryWatcher::Event> ExpectedInitialCopy;
- std::vector<DirectoryWatcher::Event> ExpectedNonInitial;
- const std::vector<DirectoryWatcher::Event> ExpectedNonInitialCopy;
- std::vector<DirectoryWatcher::Event> OptionalNonInitial;
- std::vector<DirectoryWatcher::Event> UnexpectedInitial;
- std::vector<DirectoryWatcher::Event> UnexpectedNonInitial;
- std::mutex Mtx;
- std::condition_variable ResultIsReady;
- VerifyingConsumer(
- const std::vector<DirectoryWatcher::Event> &ExpectedInitial,
- const std::vector<DirectoryWatcher::Event> &ExpectedNonInitial,
- const std::vector<DirectoryWatcher::Event> &OptionalNonInitial = {})
- : ExpectedInitial(ExpectedInitial), ExpectedInitialCopy(ExpectedInitial),
- ExpectedNonInitial(ExpectedNonInitial), ExpectedNonInitialCopy(ExpectedNonInitial),
- OptionalNonInitial(OptionalNonInitial) {}
- // This method is used by DirectoryWatcher.
- void consume(DirectoryWatcher::Event E, bool IsInitial) {
- if (IsInitial)
- consumeInitial(E);
- else
- consumeNonInitial(E);
- }
- void consumeInitial(DirectoryWatcher::Event E) {
- std::unique_lock<std::mutex> L(Mtx);
- auto It = std::find(ExpectedInitial.begin(), ExpectedInitial.end(), E);
- if (It == ExpectedInitial.end()) {
- UnexpectedInitial.push_back(E);
- } else {
- ExpectedInitial.erase(It);
- }
- if (result()) {
- L.unlock();
- ResultIsReady.notify_one();
- }
- }
- void consumeNonInitial(DirectoryWatcher::Event E) {
- std::unique_lock<std::mutex> L(Mtx);
- auto It =
- std::find(ExpectedNonInitial.begin(), ExpectedNonInitial.end(), E);
- if (It == ExpectedNonInitial.end()) {
- auto OptIt =
- std::find(OptionalNonInitial.begin(), OptionalNonInitial.end(), E);
- if (OptIt != OptionalNonInitial.end()) {
- OptionalNonInitial.erase(OptIt);
- } else {
- UnexpectedNonInitial.push_back(E);
- }
- } else {
- ExpectedNonInitial.erase(It);
- }
- if (result()) {
- L.unlock();
- ResultIsReady.notify_one();
- }
- }
- // This method is used by DirectoryWatcher.
- void consume(llvm::ArrayRef<DirectoryWatcher::Event> Es, bool IsInitial) {
- for (const auto &E : Es)
- consume(E, IsInitial);
- }
- // Not locking - caller has to lock Mtx.
- llvm::Optional<bool> result() const {
- if (ExpectedInitial.empty() && ExpectedNonInitial.empty() &&
- UnexpectedInitial.empty() && UnexpectedNonInitial.empty())
- return true;
- if (!UnexpectedInitial.empty() || !UnexpectedNonInitial.empty())
- return false;
- return llvm::None;
- }
- // This method is used by tests.
- // \returns true on success
- bool blockUntilResult() {
- std::unique_lock<std::mutex> L(Mtx);
- while (true) {
- if (result())
- return *result();
- ResultIsReady.wait(L, [this]() { return result().hasValue(); });
- }
- return false; // Just to make compiler happy.
- }
- void printUnmetExpectations(llvm::raw_ostream &OS) {
- // If there was any issue, print the expected state
- if (
- !ExpectedInitial.empty()
- ||
- !ExpectedNonInitial.empty()
- ||
- !UnexpectedInitial.empty()
- ||
- !UnexpectedNonInitial.empty()
- ) {
- OS << "Expected initial events: \n";
- for (const auto &E : ExpectedInitialCopy) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- OS << "Expected non-initial events: \n";
- for (const auto &E : ExpectedNonInitialCopy) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- }
- if (!ExpectedInitial.empty()) {
- OS << "Expected but not seen initial events: \n";
- for (const auto &E : ExpectedInitial) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- }
- if (!ExpectedNonInitial.empty()) {
- OS << "Expected but not seen non-initial events: \n";
- for (const auto &E : ExpectedNonInitial) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- }
- if (!UnexpectedInitial.empty()) {
- OS << "Unexpected initial events seen: \n";
- for (const auto &E : UnexpectedInitial) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- }
- if (!UnexpectedNonInitial.empty()) {
- OS << "Unexpected non-initial events seen: \n";
- for (const auto &E : UnexpectedNonInitial) {
- OS << eventKindToString(E.Kind) << " " << E.Filename << "\n";
- }
- }
- }
- };
- void checkEventualResultWithTimeout(VerifyingConsumer &TestConsumer) {
- std::packaged_task<int(void)> task(
- [&TestConsumer]() { return TestConsumer.blockUntilResult(); });
- std::future<int> WaitForExpectedStateResult = task.get_future();
- std::thread worker(std::move(task));
- worker.detach();
- EXPECT_TRUE(WaitForExpectedStateResult.wait_for(std::chrono::seconds(3)) ==
- std::future_status::ready)
- << "The expected result state wasn't reached before the time-out.";
- std::unique_lock<std::mutex> L(TestConsumer.Mtx);
- EXPECT_TRUE(TestConsumer.result().hasValue());
- if (TestConsumer.result().hasValue()) {
- EXPECT_TRUE(*TestConsumer.result());
- }
- if ((TestConsumer.result().hasValue() && !TestConsumer.result().getValue()) ||
- !TestConsumer.result().hasValue())
- TestConsumer.printUnmetExpectations(llvm::outs());
- }
- } // namespace
- TEST(DirectoryWatcherTest, InitialScanSync) {
- DirectoryWatcherTestFixture fixture;
- fixture.addFile("a");
- fixture.addFile("b");
- fixture.addFile("c");
- VerifyingConsumer TestConsumer{
- {{EventKind::Modified, "a"},
- {EventKind::Modified, "b"},
- {EventKind::Modified, "c"}},
- {},
- // We have to ignore these as it's a race between the test process
- // which is scanning the directory and kernel which is sending
- // notification.
- {{EventKind::Modified, "a"},
- {EventKind::Modified, "b"},
- {EventKind::Modified, "c"}}
- };
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, InitialScanAsync) {
- DirectoryWatcherTestFixture fixture;
- fixture.addFile("a");
- fixture.addFile("b");
- fixture.addFile("c");
- VerifyingConsumer TestConsumer{
- {{EventKind::Modified, "a"},
- {EventKind::Modified, "b"},
- {EventKind::Modified, "c"}},
- {},
- // We have to ignore these as it's a race between the test process
- // which is scanning the directory and kernel which is sending
- // notification.
- {{EventKind::Modified, "a"},
- {EventKind::Modified, "b"},
- {EventKind::Modified, "c"}}
- };
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/false);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, AddFiles) {
- DirectoryWatcherTestFixture fixture;
- VerifyingConsumer TestConsumer{
- {},
- {{EventKind::Modified, "a"},
- {EventKind::Modified, "b"},
- {EventKind::Modified, "c"}}};
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- fixture.addFile("a");
- fixture.addFile("b");
- fixture.addFile("c");
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, ModifyFile) {
- DirectoryWatcherTestFixture fixture;
- fixture.addFile("a");
- VerifyingConsumer TestConsumer{
- {{EventKind::Modified, "a"}},
- {{EventKind::Modified, "a"}},
- {{EventKind::Modified, "a"}}};
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- // modify the file
- {
- std::error_code error;
- llvm::raw_fd_ostream bStream(fixture.getPathInWatched("a"), error,
- CD_OpenExisting);
- assert(!error);
- bStream << "foo";
- }
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, DeleteFile) {
- DirectoryWatcherTestFixture fixture;
- fixture.addFile("a");
- VerifyingConsumer TestConsumer{
- {{EventKind::Modified, "a"}},
- {{EventKind::Removed, "a"}},
- {{EventKind::Modified, "a"}, {EventKind::Removed, "a"}}};
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- fixture.deleteFile("a");
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, DeleteWatchedDir) {
- DirectoryWatcherTestFixture fixture;
- VerifyingConsumer TestConsumer{
- {},
- {{EventKind::WatchedDirRemoved, ""},
- {EventKind::WatcherGotInvalidated, ""}}};
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- remove_directories(fixture.TestWatchedDir);
- checkEventualResultWithTimeout(TestConsumer);
- }
- TEST(DirectoryWatcherTest, InvalidatedWatcher) {
- DirectoryWatcherTestFixture fixture;
- VerifyingConsumer TestConsumer{
- {}, {{EventKind::WatcherGotInvalidated, ""}}};
- {
- llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
- DirectoryWatcher::create(
- fixture.TestWatchedDir,
- [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events,
- bool IsInitial) {
- TestConsumer.consume(Events, IsInitial);
- },
- /*waitForInitialSync=*/true);
- ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
- } // DW is destructed here.
- checkEventualResultWithTimeout(TestConsumer);
- }
|