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 {
operator ==(const DirectoryWatcher::Event & lhs,const DirectoryWatcher::Event & rhs)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
DirectoryWatcherTestFixture__anonbd9a897b0111::DirectoryWatcherTestFixture41 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
~DirectoryWatcherTestFixture__anonbd9a897b0111::DirectoryWatcherTestFixture58 ~DirectoryWatcherTestFixture() { remove_directories(TestRootDir); }
59
getPathInWatched__anonbd9a897b0111::DirectoryWatcherTestFixture60 SmallString<128> getPathInWatched(const std::string &testFile) {
61 SmallString<128> pathBuf;
62 pathBuf = TestWatchedDir;
63 path::append(pathBuf, testFile);
64 return pathBuf;
65 }
66
addFile__anonbd9a897b0111::DirectoryWatcherTestFixture67 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
deleteFile__anonbd9a897b0111::DirectoryWatcherTestFixture79 void deleteFile(const std::string &testFile) {
80 std::error_code EC =
81 remove(getPathInWatched(testFile), /*IgnoreNonExisting=*/false);
82 ASSERT_FALSE(EC);
83 }
84 };
85
eventKindToString(const EventKind K)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
VerifyingConsumer__anonbd9a897b0111::VerifyingConsumer111 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.
consume__anonbd9a897b0111::VerifyingConsumer120 void consume(DirectoryWatcher::Event E, bool IsInitial) {
121 if (IsInitial)
122 consumeInitial(E);
123 else
124 consumeNonInitial(E);
125 }
126
consumeInitial__anonbd9a897b0111::VerifyingConsumer127 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
consumeNonInitial__anonbd9a897b0111::VerifyingConsumer141 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.
consume__anonbd9a897b0111::VerifyingConsumer163 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.
result__anonbd9a897b0111::VerifyingConsumer169 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
blockUntilResult__anonbd9a897b0111::VerifyingConsumer180 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
printUnmetExpectations__anonbd9a897b0111::VerifyingConsumer191 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
checkEventualResultWithTimeout(VerifyingConsumer & TestConsumer)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
TEST(DirectoryWatcherTest,InitialScanSync)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
TEST(DirectoryWatcherTest,InitialScanAsync)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
TEST(DirectoryWatcherTest,AddFiles)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
TEST(DirectoryWatcherTest,ModifyFile)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
TEST(DirectoryWatcherTest,DeleteFile)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
TEST(DirectoryWatcherTest,DeleteWatchedDir)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
TEST(DirectoryWatcherTest,InvalidatedWatcher)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
TEST(DirectoryWatcherTest,InvalidatedWatcherAsync)453 TEST(DirectoryWatcherTest, InvalidatedWatcherAsync) {
454 DirectoryWatcherTestFixture fixture;
455 fixture.addFile("a");
456
457 // This test is checking that we get the initial notification for 'a' before
458 // the final 'invalidated'. Strictly speaking, we do not care whether 'a' is
459 // processed or not, only that it is neither racing with, nor after
460 // 'invalidated'. In practice, it is always processed in our implementations.
461 VerifyingConsumer TestConsumer{
462 {{EventKind::Modified, "a"}},
463 {{EventKind::WatcherGotInvalidated, ""}},
464 // We have to ignore these as it's a race between the test process
465 // which is scanning the directory and kernel which is sending
466 // notification.
467 {{EventKind::Modified, "a"}},
468 };
469
470 // A counter that can help detect data races on the event receiver,
471 // particularly if used with TSan. Expected count will be 2 or 3 depending on
472 // whether we get the kernel event or not (see comment above).
473 unsigned Count = 0;
474 {
475 llvm::Expected<std::unique_ptr<DirectoryWatcher>> DW =
476 DirectoryWatcher::create(
477 fixture.TestWatchedDir,
478 [&TestConsumer,
479 &Count](llvm::ArrayRef<DirectoryWatcher::Event> Events,
480 bool IsInitial) {
481 Count += 1;
482 TestConsumer.consume(Events, IsInitial);
483 },
484 /*waitForInitialSync=*/false);
485 ASSERT_THAT_ERROR(DW.takeError(), Succeeded());
486 } // DW is destructed here.
487
488 checkEventualResultWithTimeout(TestConsumer);
489 ASSERT_TRUE(Count == 2u || Count == 3u);
490 }
491