1 //===- unittests/DirectoryWatcher/DirectoryWatcherTest.cpp ----------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/DirectoryWatcher/DirectoryWatcher.h" 10 #include "llvm/Support/FileSystem.h" 11 #include "llvm/Support/Path.h" 12 #include "llvm/Support/raw_ostream.h" 13 #include "llvm/Testing/Support/Error.h" 14 #include "gtest/gtest.h" 15 #include <condition_variable> 16 #include <future> 17 #include <mutex> 18 #include <thread> 19 20 using namespace llvm; 21 using namespace llvm::sys; 22 using namespace llvm::sys::fs; 23 using namespace clang; 24 25 namespace clang { 26 static bool operator==(const DirectoryWatcher::Event &lhs, 27 const DirectoryWatcher::Event &rhs) { 28 return lhs.Filename == rhs.Filename && 29 static_cast<int>(lhs.Kind) == static_cast<int>(rhs.Kind); 30 } 31 } // namespace clang 32 33 namespace { 34 35 typedef DirectoryWatcher::Event::EventKind EventKind; 36 37 struct DirectoryWatcherTestFixture { 38 std::string TestRootDir; 39 std::string TestWatchedDir; 40 41 DirectoryWatcherTestFixture() { 42 SmallString<128> pathBuf; 43 #ifndef NDEBUG 44 std::error_code UniqDirRes = 45 #endif 46 createUniqueDirectory("dirwatcher", pathBuf); 47 assert(!UniqDirRes); 48 TestRootDir = std::string(pathBuf.str()); 49 path::append(pathBuf, "watch"); 50 TestWatchedDir = std::string(pathBuf.str()); 51 #ifndef NDEBUG 52 std::error_code CreateDirRes = 53 #endif 54 create_directory(TestWatchedDir, false); 55 assert(!CreateDirRes); 56 } 57 58 ~DirectoryWatcherTestFixture() { remove_directories(TestRootDir); } 59 60 SmallString<128> getPathInWatched(const std::string &testFile) { 61 SmallString<128> pathBuf; 62 pathBuf = TestWatchedDir; 63 path::append(pathBuf, testFile); 64 return pathBuf; 65 } 66 67 void addFile(const std::string &testFile) { 68 Expected<file_t> ft = openNativeFileForWrite(getPathInWatched(testFile), 69 CD_CreateNew, OF_None); 70 if (ft) { 71 closeFile(*ft); 72 } else { 73 llvm::errs() << llvm::toString(ft.takeError()) << "\n"; 74 llvm::errs() << getPathInWatched(testFile) << "\n"; 75 llvm_unreachable("Couldn't create test file."); 76 } 77 } 78 79 void deleteFile(const std::string &testFile) { 80 std::error_code EC = 81 remove(getPathInWatched(testFile), /*IgnoreNonExisting=*/false); 82 ASSERT_FALSE(EC); 83 } 84 }; 85 86 std::string eventKindToString(const EventKind K) { 87 switch (K) { 88 case EventKind::Removed: 89 return "Removed"; 90 case EventKind::Modified: 91 return "Modified"; 92 case EventKind::WatchedDirRemoved: 93 return "WatchedDirRemoved"; 94 case EventKind::WatcherGotInvalidated: 95 return "WatcherGotInvalidated"; 96 } 97 llvm_unreachable("unknown event kind"); 98 } 99 100 struct VerifyingConsumer { 101 std::vector<DirectoryWatcher::Event> ExpectedInitial; 102 const std::vector<DirectoryWatcher::Event> ExpectedInitialCopy; 103 std::vector<DirectoryWatcher::Event> ExpectedNonInitial; 104 const std::vector<DirectoryWatcher::Event> ExpectedNonInitialCopy; 105 std::vector<DirectoryWatcher::Event> OptionalNonInitial; 106 std::vector<DirectoryWatcher::Event> UnexpectedInitial; 107 std::vector<DirectoryWatcher::Event> UnexpectedNonInitial; 108 std::mutex Mtx; 109 std::condition_variable ResultIsReady; 110 111 VerifyingConsumer( 112 const std::vector<DirectoryWatcher::Event> &ExpectedInitial, 113 const std::vector<DirectoryWatcher::Event> &ExpectedNonInitial, 114 const std::vector<DirectoryWatcher::Event> &OptionalNonInitial = {}) 115 : ExpectedInitial(ExpectedInitial), ExpectedInitialCopy(ExpectedInitial), 116 ExpectedNonInitial(ExpectedNonInitial), ExpectedNonInitialCopy(ExpectedNonInitial), 117 OptionalNonInitial(OptionalNonInitial) {} 118 119 // This method is used by DirectoryWatcher. 120 void consume(DirectoryWatcher::Event E, bool IsInitial) { 121 if (IsInitial) 122 consumeInitial(E); 123 else 124 consumeNonInitial(E); 125 } 126 127 void consumeInitial(DirectoryWatcher::Event E) { 128 std::unique_lock<std::mutex> L(Mtx); 129 auto It = std::find(ExpectedInitial.begin(), ExpectedInitial.end(), E); 130 if (It == ExpectedInitial.end()) { 131 UnexpectedInitial.push_back(E); 132 } else { 133 ExpectedInitial.erase(It); 134 } 135 if (result()) { 136 L.unlock(); 137 ResultIsReady.notify_one(); 138 } 139 } 140 141 void consumeNonInitial(DirectoryWatcher::Event E) { 142 std::unique_lock<std::mutex> L(Mtx); 143 auto It = 144 std::find(ExpectedNonInitial.begin(), ExpectedNonInitial.end(), E); 145 if (It == ExpectedNonInitial.end()) { 146 auto OptIt = 147 std::find(OptionalNonInitial.begin(), OptionalNonInitial.end(), E); 148 if (OptIt != OptionalNonInitial.end()) { 149 OptionalNonInitial.erase(OptIt); 150 } else { 151 UnexpectedNonInitial.push_back(E); 152 } 153 } else { 154 ExpectedNonInitial.erase(It); 155 } 156 if (result()) { 157 L.unlock(); 158 ResultIsReady.notify_one(); 159 } 160 } 161 162 // This method is used by DirectoryWatcher. 163 void consume(llvm::ArrayRef<DirectoryWatcher::Event> Es, bool IsInitial) { 164 for (const auto &E : Es) 165 consume(E, IsInitial); 166 } 167 168 // Not locking - caller has to lock Mtx. 169 llvm::Optional<bool> result() const { 170 if (ExpectedInitial.empty() && ExpectedNonInitial.empty() && 171 UnexpectedInitial.empty() && UnexpectedNonInitial.empty()) 172 return true; 173 if (!UnexpectedInitial.empty() || !UnexpectedNonInitial.empty()) 174 return false; 175 return llvm::None; 176 } 177 178 // This method is used by tests. 179 // \returns true on success 180 bool blockUntilResult() { 181 std::unique_lock<std::mutex> L(Mtx); 182 while (true) { 183 if (result()) 184 return *result(); 185 186 ResultIsReady.wait(L, [this]() { return result().hasValue(); }); 187 } 188 return false; // Just to make compiler happy. 189 } 190 191 void printUnmetExpectations(llvm::raw_ostream &OS) { 192 // If there was any issue, print the expected state 193 if ( 194 !ExpectedInitial.empty() 195 || 196 !ExpectedNonInitial.empty() 197 || 198 !UnexpectedInitial.empty() 199 || 200 !UnexpectedNonInitial.empty() 201 ) { 202 OS << "Expected initial events: \n"; 203 for (const auto &E : ExpectedInitialCopy) { 204 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 205 } 206 OS << "Expected non-initial events: \n"; 207 for (const auto &E : ExpectedNonInitialCopy) { 208 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 209 } 210 } 211 212 if (!ExpectedInitial.empty()) { 213 OS << "Expected but not seen initial events: \n"; 214 for (const auto &E : ExpectedInitial) { 215 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 216 } 217 } 218 if (!ExpectedNonInitial.empty()) { 219 OS << "Expected but not seen non-initial events: \n"; 220 for (const auto &E : ExpectedNonInitial) { 221 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 222 } 223 } 224 if (!UnexpectedInitial.empty()) { 225 OS << "Unexpected initial events seen: \n"; 226 for (const auto &E : UnexpectedInitial) { 227 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 228 } 229 } 230 if (!UnexpectedNonInitial.empty()) { 231 OS << "Unexpected non-initial events seen: \n"; 232 for (const auto &E : UnexpectedNonInitial) { 233 OS << eventKindToString(E.Kind) << " " << E.Filename << "\n"; 234 } 235 } 236 } 237 }; 238 239 void checkEventualResultWithTimeout(VerifyingConsumer &TestConsumer) { 240 std::packaged_task<int(void)> task( 241 [&TestConsumer]() { return TestConsumer.blockUntilResult(); }); 242 std::future<int> WaitForExpectedStateResult = task.get_future(); 243 std::thread worker(std::move(task)); 244 worker.detach(); 245 246 EXPECT_TRUE(WaitForExpectedStateResult.wait_for(std::chrono::seconds(3)) == 247 std::future_status::ready) 248 << "The expected result state wasn't reached before the time-out."; 249 std::unique_lock<std::mutex> L(TestConsumer.Mtx); 250 EXPECT_TRUE(TestConsumer.result().hasValue()); 251 if (TestConsumer.result().hasValue()) { 252 EXPECT_TRUE(*TestConsumer.result()); 253 } 254 if ((TestConsumer.result().hasValue() && !TestConsumer.result().getValue()) || 255 !TestConsumer.result().hasValue()) 256 TestConsumer.printUnmetExpectations(llvm::outs()); 257 } 258 } // namespace 259 260 TEST(DirectoryWatcherTest, InitialScanSync) { 261 DirectoryWatcherTestFixture fixture; 262 263 fixture.addFile("a"); 264 fixture.addFile("b"); 265 fixture.addFile("c"); 266 267 VerifyingConsumer TestConsumer{ 268 {{EventKind::Modified, "a"}, 269 {EventKind::Modified, "b"}, 270 {EventKind::Modified, "c"}}, 271 {}, 272 // We have to ignore these as it's a race between the test process 273 // which is scanning the directory and kernel which is sending 274 // notification. 275 {{EventKind::Modified, "a"}, 276 {EventKind::Modified, "b"}, 277 {EventKind::Modified, "c"}} 278 }; 279 280 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 281 DirectoryWatcher::create( 282 fixture.TestWatchedDir, 283 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 284 bool IsInitial) { 285 TestConsumer.consume(Events, IsInitial); 286 }, 287 /*waitForInitialSync=*/true); 288 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 289 290 checkEventualResultWithTimeout(TestConsumer); 291 } 292 293 TEST(DirectoryWatcherTest, InitialScanAsync) { 294 DirectoryWatcherTestFixture fixture; 295 296 fixture.addFile("a"); 297 fixture.addFile("b"); 298 fixture.addFile("c"); 299 300 VerifyingConsumer TestConsumer{ 301 {{EventKind::Modified, "a"}, 302 {EventKind::Modified, "b"}, 303 {EventKind::Modified, "c"}}, 304 {}, 305 // We have to ignore these as it's a race between the test process 306 // which is scanning the directory and kernel which is sending 307 // notification. 308 {{EventKind::Modified, "a"}, 309 {EventKind::Modified, "b"}, 310 {EventKind::Modified, "c"}} 311 }; 312 313 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 314 DirectoryWatcher::create( 315 fixture.TestWatchedDir, 316 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 317 bool IsInitial) { 318 TestConsumer.consume(Events, IsInitial); 319 }, 320 /*waitForInitialSync=*/false); 321 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 322 323 checkEventualResultWithTimeout(TestConsumer); 324 } 325 326 TEST(DirectoryWatcherTest, AddFiles) { 327 DirectoryWatcherTestFixture fixture; 328 329 VerifyingConsumer TestConsumer{ 330 {}, 331 {{EventKind::Modified, "a"}, 332 {EventKind::Modified, "b"}, 333 {EventKind::Modified, "c"}}}; 334 335 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 336 DirectoryWatcher::create( 337 fixture.TestWatchedDir, 338 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 339 bool IsInitial) { 340 TestConsumer.consume(Events, IsInitial); 341 }, 342 /*waitForInitialSync=*/true); 343 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 344 345 fixture.addFile("a"); 346 fixture.addFile("b"); 347 fixture.addFile("c"); 348 349 checkEventualResultWithTimeout(TestConsumer); 350 } 351 352 TEST(DirectoryWatcherTest, ModifyFile) { 353 DirectoryWatcherTestFixture fixture; 354 355 fixture.addFile("a"); 356 357 VerifyingConsumer TestConsumer{ 358 {{EventKind::Modified, "a"}}, 359 {{EventKind::Modified, "a"}}, 360 {{EventKind::Modified, "a"}}}; 361 362 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 363 DirectoryWatcher::create( 364 fixture.TestWatchedDir, 365 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 366 bool IsInitial) { 367 TestConsumer.consume(Events, IsInitial); 368 }, 369 /*waitForInitialSync=*/true); 370 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 371 372 // modify the file 373 { 374 std::error_code error; 375 llvm::raw_fd_ostream bStream(fixture.getPathInWatched("a"), error, 376 CD_OpenExisting); 377 assert(!error); 378 bStream << "foo"; 379 } 380 381 checkEventualResultWithTimeout(TestConsumer); 382 } 383 384 TEST(DirectoryWatcherTest, DeleteFile) { 385 DirectoryWatcherTestFixture fixture; 386 387 fixture.addFile("a"); 388 389 VerifyingConsumer TestConsumer{ 390 {{EventKind::Modified, "a"}}, 391 {{EventKind::Removed, "a"}}, 392 {{EventKind::Modified, "a"}, {EventKind::Removed, "a"}}}; 393 394 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 395 DirectoryWatcher::create( 396 fixture.TestWatchedDir, 397 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 398 bool IsInitial) { 399 TestConsumer.consume(Events, IsInitial); 400 }, 401 /*waitForInitialSync=*/true); 402 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 403 404 fixture.deleteFile("a"); 405 406 checkEventualResultWithTimeout(TestConsumer); 407 } 408 409 TEST(DirectoryWatcherTest, DeleteWatchedDir) { 410 DirectoryWatcherTestFixture fixture; 411 412 VerifyingConsumer TestConsumer{ 413 {}, 414 {{EventKind::WatchedDirRemoved, ""}, 415 {EventKind::WatcherGotInvalidated, ""}}}; 416 417 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 418 DirectoryWatcher::create( 419 fixture.TestWatchedDir, 420 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 421 bool IsInitial) { 422 TestConsumer.consume(Events, IsInitial); 423 }, 424 /*waitForInitialSync=*/true); 425 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 426 427 remove_directories(fixture.TestWatchedDir); 428 429 checkEventualResultWithTimeout(TestConsumer); 430 } 431 432 TEST(DirectoryWatcherTest, InvalidatedWatcher) { 433 DirectoryWatcherTestFixture fixture; 434 435 VerifyingConsumer TestConsumer{ 436 {}, {{EventKind::WatcherGotInvalidated, ""}}}; 437 438 { 439 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW = 440 DirectoryWatcher::create( 441 fixture.TestWatchedDir, 442 [&TestConsumer](llvm::ArrayRef<DirectoryWatcher::Event> Events, 443 bool IsInitial) { 444 TestConsumer.consume(Events, IsInitial); 445 }, 446 /*waitForInitialSync=*/true); 447 ASSERT_THAT_ERROR(DW.takeError(), Succeeded()); 448 } // DW is destructed here. 449 450 checkEventualResultWithTimeout(TestConsumer); 451 } 452