summaryrefslogtreecommitdiffstats
path: root/gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
diff options
context:
space:
mode:
authorpatrick <patrick@openbsd.org>2020-08-03 14:31:31 +0000
committerpatrick <patrick@openbsd.org>2020-08-03 14:31:31 +0000
commite5dd70708596ae51455a0ffa086a00c5b29f8583 (patch)
tree5d676f27b570bacf71e786c3b5cff3e6f6679b59 /gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
parentImport LLVM 10.0.0 release including clang, lld and lldb. (diff)
downloadwireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.tar.xz
wireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.zip
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom tested by plenty
Diffstat (limited to 'gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp')
-rw-r--r--gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp451
1 files changed, 451 insertions, 0 deletions
diff --git a/gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp b/gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
new file mode 100644
index 00000000000..7a898cd0a74
--- /dev/null
+++ b/gnu/llvm/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
@@ -0,0 +1,451 @@
+//===- 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);
+}