1 //===-- TUSchedulerTests.cpp ------------------------------------*- C++ -*-===//
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 "Annotations.h"
10 #include "Context.h"
11 #include "Diagnostics.h"
12 #include "Matchers.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Preamble.h"
16 #include "TUScheduler.h"
17 #include "TestFS.h"
18 #include "Threading.h"
19 #include "clang/Basic/DiagnosticDriver.h"
20 #include "llvm/ADT/ArrayRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 #include "llvm/ADT/ScopeExit.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include <algorithm>
26 #include <utility>
27 
28 namespace clang {
29 namespace clangd {
30 namespace {
31 
32 using ::testing::AnyOf;
33 using ::testing::Each;
34 using ::testing::ElementsAre;
35 using ::testing::Eq;
36 using ::testing::Field;
37 using ::testing::IsEmpty;
38 using ::testing::Pointee;
39 using ::testing::UnorderedElementsAre;
40 
41 MATCHER_P2(TUState, State, ActionName, "") {
42   return arg.Action.S == State && arg.Action.Name == ActionName;
43 }
44 
45 class TUSchedulerTests : public ::testing::Test {
46 protected:
getInputs(PathRef File,std::string Contents)47   ParseInputs getInputs(PathRef File, std::string Contents) {
48     ParseInputs Inputs;
49     Inputs.CompileCommand = *CDB.getCompileCommand(File);
50     Inputs.FS = buildTestFS(Files, Timestamps);
51     Inputs.Contents = std::move(Contents);
52     Inputs.Opts = ParseOptions();
53     return Inputs;
54   }
55 
updateWithCallback(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void ()> CB)56   void updateWithCallback(TUScheduler &S, PathRef File,
57                           llvm::StringRef Contents, WantDiagnostics WD,
58                           llvm::unique_function<void()> CB) {
59     WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
60     S.update(File, getInputs(File, Contents), WD);
61   }
62 
63   static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
64       DiagsCallbackKey;
65 
66   /// A diagnostics callback that should be passed to TUScheduler when it's used
67   /// in updateWithDiags.
captureDiags()68   static std::unique_ptr<ParsingCallbacks> captureDiags() {
69     class CaptureDiags : public ParsingCallbacks {
70     public:
71       void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
72         reportDiagnostics(File, AST.getDiagnostics(), Publish);
73       }
74 
75       void onFailedAST(PathRef File, std::vector<Diag> Diags,
76                        PublishFn Publish) override {
77         reportDiagnostics(File, Diags, Publish);
78       }
79 
80     private:
81       void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
82                              PublishFn Publish) {
83         auto D = Context::current().get(DiagsCallbackKey);
84         if (!D)
85           return;
86         Publish([&]() {
87           const_cast<
88               llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
89               File, std::move(Diags));
90         });
91       }
92     };
93     return std::make_unique<CaptureDiags>();
94   }
95 
96   /// Schedule an update and call \p CB with the diagnostics it produces, if
97   /// any. The TUScheduler should be created with captureDiags as a
98   /// DiagsCallback for this to work.
updateWithDiags(TUScheduler & S,PathRef File,ParseInputs Inputs,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)99   void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
100                        WantDiagnostics WD,
101                        llvm::unique_function<void(std::vector<Diag>)> CB) {
102     Path OrigFile = File.str();
103     WithContextValue Ctx(DiagsCallbackKey,
104                          [OrigFile, CB = std::move(CB)](
105                              PathRef File, std::vector<Diag> Diags) mutable {
106                            assert(File == OrigFile);
107                            CB(std::move(Diags));
108                          });
109     S.update(File, std::move(Inputs), WD);
110   }
111 
updateWithDiags(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)112   void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
113                        WantDiagnostics WD,
114                        llvm::unique_function<void(std::vector<Diag>)> CB) {
115     return updateWithDiags(S, File, getInputs(File, Contents), WD,
116                            std::move(CB));
117   }
118 
119   llvm::StringMap<std::string> Files;
120   llvm::StringMap<time_t> Timestamps;
121   MockCompilationDatabase CDB;
122 };
123 
124 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
125     TUSchedulerTests::DiagsCallbackKey;
126 
TEST_F(TUSchedulerTests,MissingFiles)127 TEST_F(TUSchedulerTests, MissingFiles) {
128   TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
129                 /*StorePreamblesInMemory=*/true, /*ASTCallbacks=*/nullptr,
130                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
131                 ASTRetentionPolicy());
132 
133   auto Added = testPath("added.cpp");
134   Files[Added] = "x";
135 
136   auto Missing = testPath("missing.cpp");
137   Files[Missing] = "";
138 
139   EXPECT_EQ(S.getContents(Added), "");
140   S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
141   EXPECT_EQ(S.getContents(Added), "x");
142 
143   // Assert each operation for missing file is an error (even if it's
144   // available in VFS).
145   S.runWithAST("", Missing,
146                [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
147   S.runWithPreamble(
148       "", Missing, TUScheduler::Stale,
149       [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
150   // remove() shouldn't crash on missing files.
151   S.remove(Missing);
152 
153   // Assert there aren't any errors for added file.
154   S.runWithAST("", Added,
155                [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
156   S.runWithPreamble("", Added, TUScheduler::Stale,
157                     [&](Expected<InputsAndPreamble> Preamble) {
158                       EXPECT_TRUE(bool(Preamble));
159                     });
160   EXPECT_EQ(S.getContents(Added), "x");
161   S.remove(Added);
162   EXPECT_EQ(S.getContents(Added), "");
163 
164   // Assert that all operations fail after removing the file.
165   S.runWithAST("", Added,
166                [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
167   S.runWithPreamble("", Added, TUScheduler::Stale,
168                     [&](Expected<InputsAndPreamble> Preamble) {
169                       ASSERT_FALSE(bool(Preamble));
170                       llvm::consumeError(Preamble.takeError());
171                     });
172   // remove() shouldn't crash on missing files.
173   S.remove(Added);
174 }
175 
TEST_F(TUSchedulerTests,WantDiagnostics)176 TEST_F(TUSchedulerTests, WantDiagnostics) {
177   std::atomic<int> CallbackCount(0);
178   {
179     // To avoid a racy test, don't allow tasks to actually run on the worker
180     // thread until we've scheduled them all.
181     Notification Ready;
182     TUScheduler S(
183         CDB, getDefaultAsyncThreadsCount(),
184         /*StorePreamblesInMemory=*/true, captureDiags(),
185         /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
186         ASTRetentionPolicy());
187     auto Path = testPath("foo.cpp");
188     updateWithDiags(S, Path, "", WantDiagnostics::Yes,
189                     [&](std::vector<Diag>) { Ready.wait(); });
190     updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
191                     [&](std::vector<Diag>) { ++CallbackCount; });
192     updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
193                     [&](std::vector<Diag>) {
194                       ADD_FAILURE()
195                           << "auto should have been cancelled by auto";
196                     });
197     updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
198                     [&](std::vector<Diag>) {
199                       ADD_FAILURE() << "no diags should not be called back";
200                     });
201     updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
202                     [&](std::vector<Diag>) { ++CallbackCount; });
203     Ready.notify();
204 
205     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
206   }
207   EXPECT_EQ(2, CallbackCount);
208 }
209 
TEST_F(TUSchedulerTests,Debounce)210 TEST_F(TUSchedulerTests, Debounce) {
211   std::atomic<int> CallbackCount(0);
212   {
213     TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
214                   /*StorePreamblesInMemory=*/true, captureDiags(),
215                   /*UpdateDebounce=*/std::chrono::seconds(1),
216                   ASTRetentionPolicy());
217     // FIXME: we could probably use timeouts lower than 1 second here.
218     auto Path = testPath("foo.cpp");
219     updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
220                     [&](std::vector<Diag>) {
221                       ADD_FAILURE()
222                           << "auto should have been debounced and canceled";
223                     });
224     std::this_thread::sleep_for(std::chrono::milliseconds(200));
225     updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
226                     [&](std::vector<Diag>) { ++CallbackCount; });
227     std::this_thread::sleep_for(std::chrono::seconds(2));
228     updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto,
229                     [&](std::vector<Diag>) { ++CallbackCount; });
230 
231     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
232   }
233   EXPECT_EQ(2, CallbackCount);
234 }
235 
includes(const PreambleData * Preamble)236 static std::vector<std::string> includes(const PreambleData *Preamble) {
237   std::vector<std::string> Result;
238   if (Preamble)
239     for (const auto &Inclusion : Preamble->Includes.MainFileIncludes)
240       Result.push_back(Inclusion.Written);
241   return Result;
242 }
243 
TEST_F(TUSchedulerTests,PreambleConsistency)244 TEST_F(TUSchedulerTests, PreambleConsistency) {
245   std::atomic<int> CallbackCount(0);
246   {
247     Notification InconsistentReadDone; // Must live longest.
248     TUScheduler S(
249         CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
250         /*ASTCallbacks=*/nullptr,
251         /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
252         ASTRetentionPolicy());
253     auto Path = testPath("foo.cpp");
254     // Schedule two updates (A, B) and two preamble reads (stale, consistent).
255     // The stale read should see A, and the consistent read should see B.
256     // (We recognize the preambles by their included files).
257     updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() {
258       // This callback runs in between the two preamble updates.
259 
260       // This blocks update B, preventing it from winning the race
261       // against the stale read.
262       // If the first read was instead consistent, this would deadlock.
263       InconsistentReadDone.wait();
264       // This delays update B, preventing it from winning a race
265       // against the consistent read. The consistent read sees B
266       // only because it waits for it.
267       // If the second read was stale, it would usually see A.
268       std::this_thread::sleep_for(std::chrono::milliseconds(100));
269     });
270     S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes);
271 
272     S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
273                       [&](Expected<InputsAndPreamble> Pre) {
274                         ASSERT_TRUE(bool(Pre));
275                         assert(bool(Pre));
276                         EXPECT_THAT(includes(Pre->Preamble),
277                                     ElementsAre("<A>"));
278                         InconsistentReadDone.notify();
279                         ++CallbackCount;
280                       });
281     S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
282                       [&](Expected<InputsAndPreamble> Pre) {
283                         ASSERT_TRUE(bool(Pre));
284                         EXPECT_THAT(includes(Pre->Preamble),
285                                     ElementsAre("<B>"));
286                         ++CallbackCount;
287                       });
288   }
289   EXPECT_EQ(2, CallbackCount);
290 }
291 
TEST_F(TUSchedulerTests,Cancellation)292 TEST_F(TUSchedulerTests, Cancellation) {
293   // We have the following update/read sequence
294   //   U0
295   //   U1(WantDiags=Yes) <-- cancelled
296   //    R1               <-- cancelled
297   //   U2(WantDiags=Yes) <-- cancelled
298   //    R2A              <-- cancelled
299   //    R2B
300   //   U3(WantDiags=Yes)
301   //    R3               <-- cancelled
302   std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled;
303   {
304     Notification Proceed; // Ensure we schedule everything.
305     TUScheduler S(
306         CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
307         /*ASTCallbacks=*/captureDiags(),
308         /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
309         ASTRetentionPolicy());
310     auto Path = testPath("foo.cpp");
311     // Helper to schedule a named update and return a function to cancel it.
312     auto Update = [&](std::string ID) -> Canceler {
313       auto T = cancelableTask();
314       WithContext C(std::move(T.first));
315       updateWithDiags(
316           S, Path, "//" + ID, WantDiagnostics::Yes,
317           [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
318       return std::move(T.second);
319     };
320     // Helper to schedule a named read and return a function to cancel it.
321     auto Read = [&](std::string ID) -> Canceler {
322       auto T = cancelableTask();
323       WithContext C(std::move(T.first));
324       S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
325         if (auto Err = E.takeError()) {
326           if (Err.isA<CancelledError>()) {
327             ReadsCanceled.push_back(ID);
328             consumeError(std::move(Err));
329           } else {
330             ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
331                           << llvm::toString(std::move(Err));
332           }
333         } else {
334           ReadsSeen.push_back(ID);
335         }
336       });
337       return std::move(T.second);
338     };
339 
340     updateWithCallback(S, Path, "", WantDiagnostics::Yes,
341                        [&]() { Proceed.wait(); });
342     // The second parens indicate cancellation, where present.
343     Update("U1")();
344     Read("R1")();
345     Update("U2")();
346     Read("R2A")();
347     Read("R2B");
348     Update("U3");
349     Read("R3")();
350     Proceed.notify();
351 
352     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
353   }
354   EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
355       << "U1 and all dependent reads were cancelled. "
356          "U2 has a dependent read R2A. "
357          "U3 was not cancelled.";
358   EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
359       << "All reads other than R2B were cancelled";
360   EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
361       << "All reads other than R2B were cancelled";
362 }
363 
TEST_F(TUSchedulerTests,ManyUpdates)364 TEST_F(TUSchedulerTests, ManyUpdates) {
365   const int FilesCount = 3;
366   const int UpdatesPerFile = 10;
367 
368   std::mutex Mut;
369   int TotalASTReads = 0;
370   int TotalPreambleReads = 0;
371   int TotalUpdates = 0;
372 
373   // Run TUScheduler and collect some stats.
374   {
375     TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
376                   /*StorePreamblesInMemory=*/true, captureDiags(),
377                   /*UpdateDebounce=*/std::chrono::milliseconds(50),
378                   ASTRetentionPolicy());
379 
380     std::vector<std::string> Files;
381     for (int I = 0; I < FilesCount; ++I) {
382       std::string Name = "foo" + std::to_string(I) + ".cpp";
383       Files.push_back(testPath(Name));
384       this->Files[Files.back()] = "";
385     }
386 
387     StringRef Contents1 = R"cpp(int a;)cpp";
388     StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
389     StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
390 
391     StringRef AllContents[] = {Contents1, Contents2, Contents3};
392     const int AllContentsSize = 3;
393 
394     // Scheduler may run tasks asynchronously, but should propagate the
395     // context. We stash a nonce in the context, and verify it in the task.
396     static Key<int> NonceKey;
397     int Nonce = 0;
398 
399     for (int FileI = 0; FileI < FilesCount; ++FileI) {
400       for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
401         auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
402 
403         auto File = Files[FileI];
404         auto Inputs = getInputs(File, Contents.str());
405         {
406           WithContextValue WithNonce(NonceKey, ++Nonce);
407           updateWithDiags(
408               S, File, Inputs, WantDiagnostics::Auto,
409               [File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) {
410                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
411 
412                 std::lock_guard<std::mutex> Lock(Mut);
413                 ++TotalUpdates;
414                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
415               });
416         }
417         {
418           WithContextValue WithNonce(NonceKey, ++Nonce);
419           S.runWithAST(
420               "CheckAST", File,
421               [File, Inputs, Nonce, &Mut,
422                &TotalASTReads](Expected<InputsAndAST> AST) {
423                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
424 
425                 ASSERT_TRUE((bool)AST);
426                 EXPECT_EQ(AST->Inputs.FS, Inputs.FS);
427                 EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
428 
429                 std::lock_guard<std::mutex> Lock(Mut);
430                 ++TotalASTReads;
431                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
432               });
433         }
434 
435         {
436           WithContextValue WithNonce(NonceKey, ++Nonce);
437           S.runWithPreamble(
438               "CheckPreamble", File, TUScheduler::Stale,
439               [File, Inputs, Nonce, &Mut,
440                &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
441                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
442 
443                 ASSERT_TRUE((bool)Preamble);
444                 EXPECT_EQ(Preamble->Contents, Inputs.Contents);
445 
446                 std::lock_guard<std::mutex> Lock(Mut);
447                 ++TotalPreambleReads;
448                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
449               });
450         }
451       }
452     }
453     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
454   } // TUScheduler destructor waits for all operations to finish.
455 
456   std::lock_guard<std::mutex> Lock(Mut);
457   EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile);
458   EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
459   EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
460 }
461 
TEST_F(TUSchedulerTests,EvictedAST)462 TEST_F(TUSchedulerTests, EvictedAST) {
463   std::atomic<int> BuiltASTCounter(0);
464   ASTRetentionPolicy Policy;
465   Policy.MaxRetainedASTs = 2;
466   TUScheduler S(CDB,
467                 /*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true,
468                 /*ASTCallbacks=*/nullptr,
469                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
470                 Policy);
471 
472   llvm::StringLiteral SourceContents = R"cpp(
473     int* a;
474     double* b = a;
475   )cpp";
476   llvm::StringLiteral OtherSourceContents = R"cpp(
477     int* a;
478     double* b = a + 0;
479   )cpp";
480 
481   auto Foo = testPath("foo.cpp");
482   auto Bar = testPath("bar.cpp");
483   auto Baz = testPath("baz.cpp");
484 
485   // Build one file in advance. We will not access it later, so it will be the
486   // one that the cache will evict.
487   updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
488                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
489   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
490   ASSERT_EQ(BuiltASTCounter.load(), 1);
491 
492   // Build two more files. Since we can retain only 2 ASTs, these should be
493   // the ones we see in the cache later.
494   updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
495                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
496   updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
497                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
498   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
499   ASSERT_EQ(BuiltASTCounter.load(), 3);
500 
501   // Check only the last two ASTs are retained.
502   ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
503 
504   // Access the old file again.
505   updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
506                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
507   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
508   ASSERT_EQ(BuiltASTCounter.load(), 4);
509 
510   // Check the AST for foo.cpp is retained now and one of the others got
511   // evicted.
512   EXPECT_THAT(S.getFilesWithCachedAST(),
513               UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
514 }
515 
TEST_F(TUSchedulerTests,EmptyPreamble)516 TEST_F(TUSchedulerTests, EmptyPreamble) {
517   TUScheduler S(CDB,
518                 /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
519                 /*ASTCallbacks=*/nullptr,
520                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
521                 ASTRetentionPolicy());
522 
523   auto Foo = testPath("foo.cpp");
524   auto Header = testPath("foo.h");
525 
526   Files[Header] = "void foo()";
527   Timestamps[Header] = time_t(0);
528   auto WithPreamble = R"cpp(
529     #include "foo.h"
530     int main() {}
531   )cpp";
532   auto WithEmptyPreamble = R"cpp(int main() {})cpp";
533   S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
534   S.runWithPreamble(
535       "getNonEmptyPreamble", Foo, TUScheduler::Stale,
536       [&](Expected<InputsAndPreamble> Preamble) {
537         // We expect to get a non-empty preamble.
538         EXPECT_GT(
539             cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
540             0u);
541       });
542   // Wait for the preamble is being built.
543   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
544 
545   // Update the file which results in an empty preamble.
546   S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
547   // Wait for the preamble is being built.
548   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
549   S.runWithPreamble(
550       "getEmptyPreamble", Foo, TUScheduler::Stale,
551       [&](Expected<InputsAndPreamble> Preamble) {
552         // We expect to get an empty preamble.
553         EXPECT_EQ(
554             cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
555             0u);
556       });
557 }
558 
TEST_F(TUSchedulerTests,RunWaitsForPreamble)559 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
560   // Testing strategy: we update the file and schedule a few preamble reads at
561   // the same time. All reads should get the same non-null preamble.
562   TUScheduler S(CDB,
563                 /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
564                 /*ASTCallbacks=*/nullptr,
565                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
566                 ASTRetentionPolicy());
567   auto Foo = testPath("foo.cpp");
568   auto NonEmptyPreamble = R"cpp(
569     #define FOO 1
570     #define BAR 2
571 
572     int main() {}
573   )cpp";
574   constexpr int ReadsToSchedule = 10;
575   std::mutex PreamblesMut;
576   std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
577   S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
578   for (int I = 0; I < ReadsToSchedule; ++I) {
579     S.runWithPreamble(
580         "test", Foo, TUScheduler::Stale,
581         [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
582           std::lock_guard<std::mutex> Lock(PreamblesMut);
583           Preambles[I] = cantFail(std::move(IP)).Preamble;
584         });
585   }
586   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
587   // Check all actions got the same non-null preamble.
588   std::lock_guard<std::mutex> Lock(PreamblesMut);
589   ASSERT_NE(Preambles[0], nullptr);
590   ASSERT_THAT(Preambles, Each(Preambles[0]));
591 }
592 
TEST_F(TUSchedulerTests,NoopOnEmptyChanges)593 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
594   TUScheduler S(CDB,
595                 /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
596                 /*StorePreambleInMemory=*/true, captureDiags(),
597                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
598                 ASTRetentionPolicy());
599 
600   auto Source = testPath("foo.cpp");
601   auto Header = testPath("foo.h");
602 
603   Files[Header] = "int a;";
604   Timestamps[Header] = time_t(0);
605 
606   auto SourceContents = R"cpp(
607       #include "foo.h"
608       int b = a;
609     )cpp";
610 
611   // Return value indicates if the updated callback was received.
612   auto DoUpdate = [&](std::string Contents) -> bool {
613     std::atomic<bool> Updated(false);
614     Updated = false;
615     updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
616                     [&Updated](std::vector<Diag>) { Updated = true; });
617     bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
618     if (!UpdateFinished)
619       ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
620     return Updated;
621   };
622 
623   // Test that subsequent updates with the same inputs do not cause rebuilds.
624   ASSERT_TRUE(DoUpdate(SourceContents));
625   ASSERT_FALSE(DoUpdate(SourceContents));
626 
627   // Update to a header should cause a rebuild, though.
628   Timestamps[Header] = time_t(1);
629   ASSERT_TRUE(DoUpdate(SourceContents));
630   ASSERT_FALSE(DoUpdate(SourceContents));
631 
632   // Update to the contents should cause a rebuild.
633   auto OtherSourceContents = R"cpp(
634       #include "foo.h"
635       int c = d;
636     )cpp";
637   ASSERT_TRUE(DoUpdate(OtherSourceContents));
638   ASSERT_FALSE(DoUpdate(OtherSourceContents));
639 
640   // Update to the compile commands should also cause a rebuild.
641   CDB.ExtraClangFlags.push_back("-DSOMETHING");
642   ASSERT_TRUE(DoUpdate(OtherSourceContents));
643   ASSERT_FALSE(DoUpdate(OtherSourceContents));
644 }
645 
TEST_F(TUSchedulerTests,NoChangeDiags)646 TEST_F(TUSchedulerTests, NoChangeDiags) {
647   TUScheduler S(CDB,
648                 /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
649                 /*StorePreambleInMemory=*/true, captureDiags(),
650                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
651                 ASTRetentionPolicy());
652 
653   auto FooCpp = testPath("foo.cpp");
654   auto Contents = "int a; int b;";
655 
656   updateWithDiags(
657       S, FooCpp, Contents, WantDiagnostics::No,
658       [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
659   S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
660     // Make sure the AST was actually built.
661     cantFail(std::move(IA));
662   });
663   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
664 
665   // Even though the inputs didn't change and AST can be reused, we need to
666   // report the diagnostics, as they were not reported previously.
667   std::atomic<bool> SeenDiags(false);
668   updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
669                   [&](std::vector<Diag>) { SeenDiags = true; });
670   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
671   ASSERT_TRUE(SeenDiags);
672 
673   // Subsequent request does not get any diagnostics callback because the same
674   // diags have previously been reported and the inputs didn't change.
675   updateWithDiags(
676       S, FooCpp, Contents, WantDiagnostics::Auto,
677       [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
678   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
679 }
680 
TEST_F(TUSchedulerTests,Run)681 TEST_F(TUSchedulerTests, Run) {
682   TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
683                 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/nullptr,
684                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
685                 ASTRetentionPolicy());
686   std::atomic<int> Counter(0);
687   S.run("add 1", [&] { ++Counter; });
688   S.run("add 2", [&] { Counter += 2; });
689   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
690   EXPECT_EQ(Counter.load(), 3);
691 
692   Notification TaskRun;
693   Key<int> TestKey;
694   WithContextValue CtxWithKey(TestKey, 10);
695   S.run("props context", [&] {
696     EXPECT_EQ(Context::current().getExisting(TestKey), 10);
697     TaskRun.notify();
698   });
699   TaskRun.wait();
700 }
701 
TEST_F(TUSchedulerTests,TUStatus)702 TEST_F(TUSchedulerTests, TUStatus) {
703   class CaptureTUStatus : public DiagnosticsConsumer {
704   public:
705     void onDiagnosticsReady(PathRef File,
706                             std::vector<Diag> Diagnostics) override {}
707 
708     void onFileUpdated(PathRef File, const TUStatus &Status) override {
709       std::lock_guard<std::mutex> Lock(Mutex);
710       AllStatus.push_back(Status);
711     }
712 
713     std::vector<TUStatus> allStatus() {
714       std::lock_guard<std::mutex> Lock(Mutex);
715       return AllStatus;
716     }
717 
718   private:
719     std::mutex Mutex;
720     std::vector<TUStatus> AllStatus;
721   } CaptureTUStatus;
722   MockFSProvider FS;
723   MockCompilationDatabase CDB;
724   ClangdServer Server(CDB, FS, CaptureTUStatus, ClangdServer::optsForTest());
725   Annotations Code("int m^ain () {}");
726 
727   // We schedule the following tasks in the queue:
728   //   [Update] [GoToDefinition]
729   Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes);
730   Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
731                         [](Expected<std::vector<LocatedSymbol>> Result) {
732                           ASSERT_TRUE((bool)Result);
733                         });
734 
735   ASSERT_TRUE(Server.blockUntilIdleForTest());
736 
737   EXPECT_THAT(CaptureTUStatus.allStatus(),
738               ElementsAre(
739                   // Statuses of "Update" action.
740                   TUState(TUAction::RunningAction, "Update"),
741                   TUState(TUAction::BuildingPreamble, "Update"),
742                   TUState(TUAction::BuildingFile, "Update"),
743 
744                   // Statuses of "Definitions" action
745                   TUState(TUAction::RunningAction, "Definitions"),
746                   TUState(TUAction::Idle, /*No action*/ "")));
747 }
748 
TEST_F(TUSchedulerTests,CommandLineErrors)749 TEST_F(TUSchedulerTests, CommandLineErrors) {
750   // We should see errors from command-line parsing inside the main file.
751   CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
752 
753   // (!) 'Ready' must live longer than TUScheduler.
754   Notification Ready;
755 
756   TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
757                 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
758                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
759                 ASTRetentionPolicy());
760 
761   std::vector<Diag> Diagnostics;
762   updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
763                   WantDiagnostics::Yes, [&](std::vector<Diag> D) {
764                     Diagnostics = std::move(D);
765                     Ready.notify();
766                   });
767   Ready.wait();
768 
769   EXPECT_THAT(
770       Diagnostics,
771       ElementsAre(AllOf(
772           Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
773           Field(&Diag::Name, Eq("drv_unknown_argument")),
774           Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
775 }
776 
TEST_F(TUSchedulerTests,CommandLineWarnings)777 TEST_F(TUSchedulerTests, CommandLineWarnings) {
778   // We should not see warnings from command-line parsing.
779   CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
780 
781   // (!) 'Ready' must live longer than TUScheduler.
782   Notification Ready;
783 
784   TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
785                 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
786                 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
787                 ASTRetentionPolicy());
788 
789   std::vector<Diag> Diagnostics;
790   updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
791                   WantDiagnostics::Yes, [&](std::vector<Diag> D) {
792                     Diagnostics = std::move(D);
793                     Ready.notify();
794                   });
795   Ready.wait();
796 
797   EXPECT_THAT(Diagnostics, IsEmpty());
798 }
799 
800 } // namespace
801 } // namespace clangd
802 } // namespace clang
803