diff options
author | Jan Korous <jkorous@apple.com> | 2019-07-12 19:47:55 +0000 |
---|---|---|
committer | Jan Korous <jkorous@apple.com> | 2019-07-12 19:47:55 +0000 |
commit | fdcb7f47e783933e0af8a5fae91132269a208268 (patch) | |
tree | df797e88fc287170518dc9f51bfd87399b874622 /clang/unittests/DirectoryWatcher | |
parent | b828f0b90adfa24b582acde7d2ba5ebb55192556 (diff) | |
download | bcm5719-llvm-fdcb7f47e783933e0af8a5fae91132269a208268.tar.gz bcm5719-llvm-fdcb7f47e783933e0af8a5fae91132269a208268.zip |
Reland [clang] DirectoryWatcher
This reverts commit abce8c457dd3de6b156756e547cc0eefb7653c79.
+ Fix the build for platforms that don't have DW implementated.
llvm-svn: 365947
Diffstat (limited to 'clang/unittests/DirectoryWatcher')
-rw-r--r-- | clang/unittests/DirectoryWatcher/CMakeLists.txt | 17 | ||||
-rw-r--r-- | clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp | 426 |
2 files changed, 443 insertions, 0 deletions
diff --git a/clang/unittests/DirectoryWatcher/CMakeLists.txt b/clang/unittests/DirectoryWatcher/CMakeLists.txt new file mode 100644 index 00000000000..fc93323b374 --- /dev/null +++ b/clang/unittests/DirectoryWatcher/CMakeLists.txt @@ -0,0 +1,17 @@ +if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Linux") + + set(LLVM_LINK_COMPONENTS + Support + ) + + add_clang_unittest(DirectoryWatcherTests + DirectoryWatcherTest.cpp + ) + + target_link_libraries(DirectoryWatcherTests + PRIVATE + clangDirectoryWatcher + clangBasic + ) + +endif()
\ No newline at end of file diff --git a/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp b/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp new file mode 100644 index 00000000000..a2c50fc7d00 --- /dev/null +++ b/clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp @@ -0,0 +1,426 @@ +//===- 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/Mutex.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.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 { + +struct DirectoryWatcherTestFixture { + std::string TestRootDir; + std::string TestWatchedDir; + + DirectoryWatcherTestFixture() { + SmallString<128> pathBuf; + std::error_code UniqDirRes = createUniqueDirectory("dirwatcher", pathBuf); + assert(!UniqDirRes); + TestRootDir = pathBuf.str(); + path::append(pathBuf, "watch"); + TestWatchedDir = pathBuf.str(); + std::error_code CreateDirRes = 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 DirectoryWatcher::Event::EventKind K) { + switch (K) { + case DirectoryWatcher::Event::EventKind::Removed: + return "Removed"; + case DirectoryWatcher::Event::EventKind::Modified: + return "Modified"; + case DirectoryWatcher::Event::EventKind::WatchedDirRemoved: + return "WatchedDirRemoved"; + case DirectoryWatcher::Event::EventKind::WatcherGotInvalidated: + return "WatcherGotInvalidated"; + } + llvm_unreachable("unknown event kind"); +} + +struct VerifyingConsumer { + std::vector<DirectoryWatcher::Event> ExpectedInitial; + std::vector<DirectoryWatcher::Event> ExpectedNonInitial; + 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), + ExpectedNonInitial(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()) + 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()) + 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 (!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."; + 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{ + {{DirectoryWatcher::Event::EventKind::Modified, "a"}, + {DirectoryWatcher::Event::EventKind::Modified, "b"}, + {DirectoryWatcher::Event::EventKind::Modified, "c"}}, + {}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, InitialScanAsync) { + DirectoryWatcherTestFixture fixture; + + fixture.addFile("a"); + fixture.addFile("b"); + fixture.addFile("c"); + + VerifyingConsumer TestConsumer{ + {{DirectoryWatcher::Event::EventKind::Modified, "a"}, + {DirectoryWatcher::Event::EventKind::Modified, "b"}, + {DirectoryWatcher::Event::EventKind::Modified, "c"}}, + {}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/false); + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, AddFiles) { + DirectoryWatcherTestFixture fixture; + + VerifyingConsumer TestConsumer{ + {}, + {{DirectoryWatcher::Event::EventKind::Modified, "a"}, + {DirectoryWatcher::Event::EventKind::Modified, "b"}, + {DirectoryWatcher::Event::EventKind::Modified, "c"}}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + fixture.addFile("a"); + fixture.addFile("b"); + fixture.addFile("c"); + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, ModifyFile) { + DirectoryWatcherTestFixture fixture; + + fixture.addFile("a"); + + VerifyingConsumer TestConsumer{ + {{DirectoryWatcher::Event::EventKind::Modified, "a"}}, + {{DirectoryWatcher::Event::EventKind::Modified, "a"}}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + // 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{ + {{DirectoryWatcher::Event::EventKind::Modified, "a"}}, + {{DirectoryWatcher::Event::EventKind::Removed, "a"}}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + fixture.deleteFile("a"); + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, DeleteWatchedDir) { + DirectoryWatcherTestFixture fixture; + + VerifyingConsumer TestConsumer{ + {}, + {{DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""}, + {DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + remove_directories(fixture.TestWatchedDir); + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, InvalidatedWatcher) { + DirectoryWatcherTestFixture fixture; + + VerifyingConsumer TestConsumer{ + {}, {{DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}}}; + + { + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + } // DW is destructed here. + + checkEventualResultWithTimeout(TestConsumer); +} + +TEST(DirectoryWatcherTest, ChangeMetadata) { + DirectoryWatcherTestFixture fixture; + fixture.addFile("a"); + + VerifyingConsumer TestConsumer{ + {{DirectoryWatcher::Event::EventKind::Modified, "a"}}, + // We don't expect any notification for file having access file changed. + {}, + // Given the timing we are ok with receiving the duplicate event. + {{DirectoryWatcher::Event::EventKind::Modified, "a"}}}; + + auto DW = DirectoryWatcher::create( + fixture.TestWatchedDir, + [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, + bool IsInitial) { + TestConsumer.consume(Events, IsInitial); + }, + /*waitForInitialSync=*/true); + + { // Change access and modification time of file a. + Expected<file_t> HopefullyTheFD = llvm::sys::fs::openNativeFileForWrite( + fixture.getPathInWatched("a"), CD_OpenExisting, OF_None); + if (!HopefullyTheFD) { + llvm::outs() << HopefullyTheFD.takeError(); + } + + const int FD = HopefullyTheFD.get(); + const TimePoint<> NewTimePt = + std::chrono::system_clock::now() - std::chrono::minutes(1); + + std::error_code setTimeRes = + llvm::sys::fs::setLastAccessAndModificationTime(FD, NewTimePt, + NewTimePt); + assert(!setTimeRes); + } + + checkEventualResultWithTimeout(TestConsumer); +} |