• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ClangdServer.h"
11 #include "Diagnostics.h"
12 #include "Matchers.h"
13 #include "ParsedAST.h"
14 #include "Preamble.h"
15 #include "TUScheduler.h"
16 #include "TestFS.h"
17 #include "support/Cancellation.h"
18 #include "support/Context.h"
19 #include "support/Path.h"
20 #include "support/TestTracer.h"
21 #include "support/Threading.h"
22 #include "support/ThreadsafeFS.h"
23 #include "clang/Basic/DiagnosticDriver.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/ADT/FunctionExtras.h"
26 #include "llvm/ADT/STLExtras.h"
27 #include "llvm/ADT/ScopeExit.h"
28 #include "llvm/ADT/StringExtras.h"
29 #include "llvm/ADT/StringMap.h"
30 #include "llvm/ADT/StringRef.h"
31 #include "gmock/gmock.h"
32 #include "gtest/gtest.h"
33 #include <algorithm>
34 #include <atomic>
35 #include <chrono>
36 #include <cstdint>
37 #include <memory>
38 #include <string>
39 #include <utility>
40 
41 namespace clang {
42 namespace clangd {
43 namespace {
44 
45 using ::testing::AnyOf;
46 using ::testing::Each;
47 using ::testing::ElementsAre;
48 using ::testing::Eq;
49 using ::testing::Field;
50 using ::testing::IsEmpty;
51 using ::testing::Pointee;
52 using ::testing::SizeIs;
53 using ::testing::UnorderedElementsAre;
54 
55 MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
56   if (arg.PreambleActivity != PreambleActivity) {
57     *result_listener << "preamblestate is "
58                      << static_cast<uint8_t>(arg.PreambleActivity);
59     return false;
60   }
61   if (arg.ASTActivity.K != ASTActivity) {
62     *result_listener << "aststate is " << arg.ASTActivity.K;
63     return false;
64   }
65   return true;
66 }
67 
68 // Dummy ContextProvider to verify the provider is invoked & contexts are used.
69 static Key<std::string> BoundPath;
bindPath(PathRef F)70 Context bindPath(PathRef F) {
71   return Context::current().derive(BoundPath, F.str());
72 }
boundPath()73 llvm::StringRef boundPath() {
74   const std::string *V = Context::current().get(BoundPath);
75   return V ? *V : llvm::StringRef("");
76 }
77 
optsForTest()78 TUScheduler::Options optsForTest() {
79   TUScheduler::Options Opts(ClangdServer::optsForTest());
80   Opts.ContextProvider = bindPath;
81   return Opts;
82 }
83 
84 class TUSchedulerTests : public ::testing::Test {
85 protected:
getInputs(PathRef File,std::string Contents)86   ParseInputs getInputs(PathRef File, std::string Contents) {
87     ParseInputs Inputs;
88     Inputs.CompileCommand = *CDB.getCompileCommand(File);
89     Inputs.TFS = &FS;
90     Inputs.Contents = std::move(Contents);
91     Inputs.Opts = ParseOptions();
92     return Inputs;
93   }
94 
updateWithCallback(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void ()> CB)95   void updateWithCallback(TUScheduler &S, PathRef File,
96                           llvm::StringRef Contents, WantDiagnostics WD,
97                           llvm::unique_function<void()> CB) {
98     updateWithCallback(S, File, getInputs(File, std::string(Contents)), WD,
99                        std::move(CB));
100   }
101 
updateWithCallback(TUScheduler & S,PathRef File,ParseInputs Inputs,WantDiagnostics WD,llvm::unique_function<void ()> CB)102   void updateWithCallback(TUScheduler &S, PathRef File, ParseInputs Inputs,
103                           WantDiagnostics WD,
104                           llvm::unique_function<void()> CB) {
105     WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
106     S.update(File, Inputs, WD);
107   }
108 
109   static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
110       DiagsCallbackKey;
111 
112   /// A diagnostics callback that should be passed to TUScheduler when it's used
113   /// in updateWithDiags.
captureDiags()114   static std::unique_ptr<ParsingCallbacks> captureDiags() {
115     class CaptureDiags : public ParsingCallbacks {
116     public:
117       void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
118         reportDiagnostics(File, AST.getDiagnostics(), Publish);
119       }
120 
121       void onFailedAST(PathRef File, llvm::StringRef Version,
122                        std::vector<Diag> Diags, PublishFn Publish) override {
123         reportDiagnostics(File, Diags, Publish);
124       }
125 
126     private:
127       void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
128                              PublishFn Publish) {
129         auto D = Context::current().get(DiagsCallbackKey);
130         if (!D)
131           return;
132         Publish([&]() {
133           const_cast<
134               llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
135               File, std::move(Diags));
136         });
137       }
138     };
139     return std::make_unique<CaptureDiags>();
140   }
141 
142   /// Schedule an update and call \p CB with the diagnostics it produces, if
143   /// any. The TUScheduler should be created with captureDiags as a
144   /// DiagsCallback for this to work.
updateWithDiags(TUScheduler & S,PathRef File,ParseInputs Inputs,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)145   void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
146                        WantDiagnostics WD,
147                        llvm::unique_function<void(std::vector<Diag>)> CB) {
148     Path OrigFile = File.str();
149     WithContextValue Ctx(DiagsCallbackKey,
150                          [OrigFile, CB = std::move(CB)](
151                              PathRef File, std::vector<Diag> Diags) mutable {
152                            assert(File == OrigFile);
153                            CB(std::move(Diags));
154                          });
155     S.update(File, std::move(Inputs), WD);
156   }
157 
updateWithDiags(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)158   void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
159                        WantDiagnostics WD,
160                        llvm::unique_function<void(std::vector<Diag>)> CB) {
161     return updateWithDiags(S, File, getInputs(File, std::string(Contents)), WD,
162                            std::move(CB));
163   }
164 
165   MockFS FS;
166   MockCompilationDatabase CDB;
167 };
168 
169 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
170     TUSchedulerTests::DiagsCallbackKey;
171 
TEST_F(TUSchedulerTests,MissingFiles)172 TEST_F(TUSchedulerTests, MissingFiles) {
173   TUScheduler S(CDB, optsForTest());
174 
175   auto Added = testPath("added.cpp");
176   FS.Files[Added] = "x";
177 
178   auto Missing = testPath("missing.cpp");
179   FS.Files[Missing] = "";
180 
181   S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
182 
183   // Assert each operation for missing file is an error (even if it's
184   // available in VFS).
185   S.runWithAST("", Missing,
186                [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
187   S.runWithPreamble(
188       "", Missing, TUScheduler::Stale,
189       [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
190   // remove() shouldn't crash on missing files.
191   S.remove(Missing);
192 
193   // Assert there aren't any errors for added file.
194   S.runWithAST("", Added,
195                [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
196   S.runWithPreamble("", Added, TUScheduler::Stale,
197                     [&](Expected<InputsAndPreamble> Preamble) {
198                       EXPECT_TRUE(bool(Preamble));
199                     });
200   S.remove(Added);
201 
202   // Assert that all operations fail after removing the file.
203   S.runWithAST("", Added,
204                [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
205   S.runWithPreamble("", Added, TUScheduler::Stale,
206                     [&](Expected<InputsAndPreamble> Preamble) {
207                       ASSERT_FALSE(bool(Preamble));
208                       llvm::consumeError(Preamble.takeError());
209                     });
210   // remove() shouldn't crash on missing files.
211   S.remove(Added);
212 }
213 
TEST_F(TUSchedulerTests,WantDiagnostics)214 TEST_F(TUSchedulerTests, WantDiagnostics) {
215   std::atomic<int> CallbackCount(0);
216   {
217     // To avoid a racy test, don't allow tasks to actually run on the worker
218     // thread until we've scheduled them all.
219     Notification Ready;
220     TUScheduler S(CDB, optsForTest(), captureDiags());
221     auto Path = testPath("foo.cpp");
222     updateWithDiags(S, Path, "", WantDiagnostics::Yes,
223                     [&](std::vector<Diag>) { Ready.wait(); });
224     updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
225                     [&](std::vector<Diag>) { ++CallbackCount; });
226     updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
227                     [&](std::vector<Diag>) {
228                       ADD_FAILURE()
229                           << "auto should have been cancelled by auto";
230                     });
231     updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
232                     [&](std::vector<Diag>) {
233                       ADD_FAILURE() << "no diags should not be called back";
234                     });
235     updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
236                     [&](std::vector<Diag>) { ++CallbackCount; });
237     Ready.notify();
238 
239     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
240   }
241   EXPECT_EQ(2, CallbackCount);
242 }
243 
TEST_F(TUSchedulerTests,Debounce)244 TEST_F(TUSchedulerTests, Debounce) {
245   std::atomic<int> CallbackCount(0);
246   {
247     auto Opts = optsForTest();
248     Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::seconds(1));
249     TUScheduler S(CDB, Opts, captureDiags());
250     // FIXME: we could probably use timeouts lower than 1 second here.
251     auto Path = testPath("foo.cpp");
252     updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
253                     [&](std::vector<Diag>) {
254                       ADD_FAILURE()
255                           << "auto should have been debounced and canceled";
256                     });
257     std::this_thread::sleep_for(std::chrono::milliseconds(200));
258     updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
259                     [&](std::vector<Diag>) { ++CallbackCount; });
260     std::this_thread::sleep_for(std::chrono::seconds(2));
261     updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto,
262                     [&](std::vector<Diag>) { ++CallbackCount; });
263 
264     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
265   }
266   EXPECT_EQ(2, CallbackCount);
267 }
268 
TEST_F(TUSchedulerTests,Cancellation)269 TEST_F(TUSchedulerTests, Cancellation) {
270   // We have the following update/read sequence
271   //   U0
272   //   U1(WantDiags=Yes) <-- cancelled
273   //    R1               <-- cancelled
274   //   U2(WantDiags=Yes) <-- cancelled
275   //    R2A              <-- cancelled
276   //    R2B
277   //   U3(WantDiags=Yes)
278   //    R3               <-- cancelled
279   std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled;
280   {
281     Notification Proceed; // Ensure we schedule everything.
282     TUScheduler S(CDB, optsForTest(), captureDiags());
283     auto Path = testPath("foo.cpp");
284     // Helper to schedule a named update and return a function to cancel it.
285     auto Update = [&](std::string ID) -> Canceler {
286       auto T = cancelableTask();
287       WithContext C(std::move(T.first));
288       updateWithDiags(
289           S, Path, "//" + ID, WantDiagnostics::Yes,
290           [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
291       return std::move(T.second);
292     };
293     // Helper to schedule a named read and return a function to cancel it.
294     auto Read = [&](std::string ID) -> Canceler {
295       auto T = cancelableTask();
296       WithContext C(std::move(T.first));
297       S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
298         if (auto Err = E.takeError()) {
299           if (Err.isA<CancelledError>()) {
300             ReadsCanceled.push_back(ID);
301             consumeError(std::move(Err));
302           } else {
303             ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
304                           << llvm::toString(std::move(Err));
305           }
306         } else {
307           ReadsSeen.push_back(ID);
308         }
309       });
310       return std::move(T.second);
311     };
312 
313     updateWithCallback(S, Path, "", WantDiagnostics::Yes,
314                        [&]() { Proceed.wait(); });
315     // The second parens indicate cancellation, where present.
316     Update("U1")();
317     Read("R1")();
318     Update("U2")();
319     Read("R2A")();
320     Read("R2B");
321     Update("U3");
322     Read("R3")();
323     Proceed.notify();
324 
325     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
326   }
327   EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
328       << "U1 and all dependent reads were cancelled. "
329          "U2 has a dependent read R2A. "
330          "U3 was not cancelled.";
331   EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
332       << "All reads other than R2B were cancelled";
333   EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
334       << "All reads other than R2B were cancelled";
335 }
336 
TEST_F(TUSchedulerTests,InvalidationNoCrash)337 TEST_F(TUSchedulerTests, InvalidationNoCrash) {
338   auto Path = testPath("foo.cpp");
339   TUScheduler S(CDB, optsForTest(), captureDiags());
340 
341   Notification StartedRunning;
342   Notification ScheduledChange;
343   // We expect invalidation logic to not crash by trying to invalidate a running
344   // request.
345   S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
346   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
347   S.runWithAST(
348       "invalidatable-but-running", Path,
349       [&](llvm::Expected<InputsAndAST> AST) {
350         StartedRunning.notify();
351         ScheduledChange.wait();
352         ASSERT_TRUE(bool(AST));
353       },
354       TUScheduler::InvalidateOnUpdate);
355   StartedRunning.wait();
356   S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
357   ScheduledChange.notify();
358   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
359 }
360 
TEST_F(TUSchedulerTests,Invalidation)361 TEST_F(TUSchedulerTests, Invalidation) {
362   auto Path = testPath("foo.cpp");
363   TUScheduler S(CDB, optsForTest(), captureDiags());
364   std::atomic<int> Builds(0), Actions(0);
365 
366   Notification Start;
367   updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
368     ++Builds;
369     Start.wait();
370   });
371   S.runWithAST(
372       "invalidatable", Path,
373       [&](llvm::Expected<InputsAndAST> AST) {
374         ++Actions;
375         EXPECT_FALSE(bool(AST));
376         llvm::Error E = AST.takeError();
377         EXPECT_TRUE(E.isA<CancelledError>());
378         handleAllErrors(std::move(E), [&](const CancelledError &E) {
379           EXPECT_EQ(E.Reason, static_cast<int>(ErrorCode::ContentModified));
380         });
381       },
382       TUScheduler::InvalidateOnUpdate);
383   S.runWithAST(
384       "not-invalidatable", Path,
385       [&](llvm::Expected<InputsAndAST> AST) {
386         ++Actions;
387         EXPECT_TRUE(bool(AST));
388       },
389       TUScheduler::NoInvalidation);
390   updateWithDiags(S, Path, "b", WantDiagnostics::Auto, [&](std::vector<Diag>) {
391     ++Builds;
392     ADD_FAILURE() << "Shouldn't build, all dependents invalidated";
393   });
394   S.runWithAST(
395       "invalidatable", Path,
396       [&](llvm::Expected<InputsAndAST> AST) {
397         ++Actions;
398         EXPECT_FALSE(bool(AST));
399         llvm::Error E = AST.takeError();
400         EXPECT_TRUE(E.isA<CancelledError>());
401         consumeError(std::move(E));
402       },
403       TUScheduler::InvalidateOnUpdate);
404   updateWithDiags(S, Path, "c", WantDiagnostics::Auto,
405                   [&](std::vector<Diag>) { ++Builds; });
406   S.runWithAST(
407       "invalidatable", Path,
408       [&](llvm::Expected<InputsAndAST> AST) {
409         ++Actions;
410         EXPECT_TRUE(bool(AST)) << "Shouldn't be invalidated, no update follows";
411       },
412       TUScheduler::InvalidateOnUpdate);
413   Start.notify();
414   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
415 
416   EXPECT_EQ(2, Builds.load()) << "Middle build should be skipped";
417   EXPECT_EQ(4, Actions.load()) << "All actions should run (some with error)";
418 }
419 
TEST_F(TUSchedulerTests,ManyUpdates)420 TEST_F(TUSchedulerTests, ManyUpdates) {
421   const int FilesCount = 3;
422   const int UpdatesPerFile = 10;
423 
424   std::mutex Mut;
425   int TotalASTReads = 0;
426   int TotalPreambleReads = 0;
427   int TotalUpdates = 0;
428   llvm::StringMap<int> LatestDiagVersion;
429 
430   // Run TUScheduler and collect some stats.
431   {
432     auto Opts = optsForTest();
433     Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::milliseconds(50));
434     TUScheduler S(CDB, Opts, captureDiags());
435 
436     std::vector<std::string> Files;
437     for (int I = 0; I < FilesCount; ++I) {
438       std::string Name = "foo" + std::to_string(I) + ".cpp";
439       Files.push_back(testPath(Name));
440       this->FS.Files[Files.back()] = "";
441     }
442 
443     StringRef Contents1 = R"cpp(int a;)cpp";
444     StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
445     StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
446 
447     StringRef AllContents[] = {Contents1, Contents2, Contents3};
448     const int AllContentsSize = 3;
449 
450     // Scheduler may run tasks asynchronously, but should propagate the
451     // context. We stash a nonce in the context, and verify it in the task.
452     static Key<int> NonceKey;
453     int Nonce = 0;
454 
455     for (int FileI = 0; FileI < FilesCount; ++FileI) {
456       for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
457         auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
458 
459         auto File = Files[FileI];
460         auto Inputs = getInputs(File, Contents.str());
461         {
462           WithContextValue WithNonce(NonceKey, ++Nonce);
463           Inputs.Version = std::to_string(UpdateI);
464           updateWithDiags(
465               S, File, Inputs, WantDiagnostics::Auto,
466               [File, Nonce, Version(Inputs.Version), &Mut, &TotalUpdates,
467                &LatestDiagVersion](std::vector<Diag>) {
468                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
469                 EXPECT_EQ(File, boundPath());
470 
471                 std::lock_guard<std::mutex> Lock(Mut);
472                 ++TotalUpdates;
473                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
474                 // Make sure Diags are for a newer version.
475                 auto It = LatestDiagVersion.try_emplace(File, -1);
476                 const int PrevVersion = It.first->second;
477                 int CurVersion;
478                 ASSERT_TRUE(llvm::to_integer(Version, CurVersion, 10));
479                 EXPECT_LT(PrevVersion, CurVersion);
480                 It.first->getValue() = CurVersion;
481               });
482         }
483         {
484           WithContextValue WithNonce(NonceKey, ++Nonce);
485           S.runWithAST(
486               "CheckAST", File,
487               [File, Inputs, Nonce, &Mut,
488                &TotalASTReads](Expected<InputsAndAST> AST) {
489                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
490                 EXPECT_EQ(File, boundPath());
491 
492                 ASSERT_TRUE((bool)AST);
493                 EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
494                 EXPECT_EQ(AST->Inputs.Version, Inputs.Version);
495                 EXPECT_EQ(AST->AST.version(), Inputs.Version);
496 
497                 std::lock_guard<std::mutex> Lock(Mut);
498                 ++TotalASTReads;
499                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
500               });
501         }
502 
503         {
504           WithContextValue WithNonce(NonceKey, ++Nonce);
505           S.runWithPreamble(
506               "CheckPreamble", File, TUScheduler::Stale,
507               [File, Inputs, Nonce, &Mut,
508                &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
509                 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
510                 EXPECT_EQ(File, boundPath());
511 
512                 ASSERT_TRUE((bool)Preamble);
513                 EXPECT_EQ(Preamble->Contents, Inputs.Contents);
514 
515                 std::lock_guard<std::mutex> Lock(Mut);
516                 ++TotalPreambleReads;
517                 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
518               });
519         }
520       }
521     }
522     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
523   } // TUScheduler destructor waits for all operations to finish.
524 
525   std::lock_guard<std::mutex> Lock(Mut);
526   // Updates might get coalesced in preamble thread and result in dropping
527   // diagnostics for intermediate snapshots.
528   EXPECT_GE(TotalUpdates, FilesCount);
529   EXPECT_LE(TotalUpdates, FilesCount * UpdatesPerFile);
530   // We should receive diags for last update.
531   for (const auto &Entry : LatestDiagVersion)
532     EXPECT_EQ(Entry.second, UpdatesPerFile - 1);
533   EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
534   EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
535 }
536 
TEST_F(TUSchedulerTests,EvictedAST)537 TEST_F(TUSchedulerTests, EvictedAST) {
538   std::atomic<int> BuiltASTCounter(0);
539   auto Opts = optsForTest();
540   Opts.AsyncThreadsCount = 1;
541   Opts.RetentionPolicy.MaxRetainedASTs = 2;
542   trace::TestTracer Tracer;
543   TUScheduler S(CDB, Opts);
544 
545   llvm::StringLiteral SourceContents = R"cpp(
546     int* a;
547     double* b = a;
548   )cpp";
549   llvm::StringLiteral OtherSourceContents = R"cpp(
550     int* a;
551     double* b = a + 0;
552   )cpp";
553 
554   auto Foo = testPath("foo.cpp");
555   auto Bar = testPath("bar.cpp");
556   auto Baz = testPath("baz.cpp");
557 
558   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
559   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
560   // Build one file in advance. We will not access it later, so it will be the
561   // one that the cache will evict.
562   updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
563                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
564   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
565   ASSERT_EQ(BuiltASTCounter.load(), 1);
566   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
567   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
568 
569   // Build two more files. Since we can retain only 2 ASTs, these should be
570   // the ones we see in the cache later.
571   updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
572                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
573   updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
574                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
575   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
576   ASSERT_EQ(BuiltASTCounter.load(), 3);
577   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
578   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(2));
579 
580   // Check only the last two ASTs are retained.
581   ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
582 
583   // Access the old file again.
584   updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
585                      [&BuiltASTCounter]() { ++BuiltASTCounter; });
586   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
587   ASSERT_EQ(BuiltASTCounter.load(), 4);
588   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
589   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
590 
591   // Check the AST for foo.cpp is retained now and one of the others got
592   // evicted.
593   EXPECT_THAT(S.getFilesWithCachedAST(),
594               UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
595 }
596 
597 // We send "empty" changes to TUScheduler when we think some external event
598 // *might* have invalidated current state (e.g. a header was edited).
599 // Verify that this doesn't evict our cache entries.
TEST_F(TUSchedulerTests,NoopChangesDontThrashCache)600 TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
601   auto Opts = optsForTest();
602   Opts.RetentionPolicy.MaxRetainedASTs = 1;
603   TUScheduler S(CDB, Opts);
604 
605   auto Foo = testPath("foo.cpp");
606   auto FooInputs = getInputs(Foo, "int x=1;");
607   auto Bar = testPath("bar.cpp");
608   auto BarInputs = getInputs(Bar, "int x=2;");
609 
610   // After opening Foo then Bar, AST cache contains Bar.
611   S.update(Foo, FooInputs, WantDiagnostics::Auto);
612   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
613   S.update(Bar, BarInputs, WantDiagnostics::Auto);
614   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
615   ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
616 
617   // Any number of no-op updates to Foo don't dislodge Bar from the cache.
618   S.update(Foo, FooInputs, WantDiagnostics::Auto);
619   S.update(Foo, FooInputs, WantDiagnostics::Auto);
620   S.update(Foo, FooInputs, WantDiagnostics::Auto);
621   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
622   ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
623   // In fact each file has been built only once.
624   ASSERT_EQ(S.fileStats().lookup(Foo).ASTBuilds, 1u);
625   ASSERT_EQ(S.fileStats().lookup(Bar).ASTBuilds, 1u);
626 }
627 
TEST_F(TUSchedulerTests,EmptyPreamble)628 TEST_F(TUSchedulerTests, EmptyPreamble) {
629   TUScheduler S(CDB, optsForTest());
630 
631   auto Foo = testPath("foo.cpp");
632   auto Header = testPath("foo.h");
633 
634   FS.Files[Header] = "void foo()";
635   FS.Timestamps[Header] = time_t(0);
636   auto WithPreamble = R"cpp(
637     #include "foo.h"
638     int main() {}
639   )cpp";
640   auto WithEmptyPreamble = R"cpp(int main() {})cpp";
641   S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
642   S.runWithPreamble(
643       "getNonEmptyPreamble", Foo, TUScheduler::Stale,
644       [&](Expected<InputsAndPreamble> Preamble) {
645         // We expect to get a non-empty preamble.
646         EXPECT_GT(
647             cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
648             0u);
649       });
650   // Wait for the preamble is being built.
651   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
652 
653   // Update the file which results in an empty preamble.
654   S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
655   // Wait for the preamble is being built.
656   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
657   S.runWithPreamble(
658       "getEmptyPreamble", Foo, TUScheduler::Stale,
659       [&](Expected<InputsAndPreamble> Preamble) {
660         // We expect to get an empty preamble.
661         EXPECT_EQ(
662             cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
663             0u);
664       });
665 }
666 
TEST_F(TUSchedulerTests,RunWaitsForPreamble)667 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
668   // Testing strategy: we update the file and schedule a few preamble reads at
669   // the same time. All reads should get the same non-null preamble.
670   TUScheduler S(CDB, optsForTest());
671   auto Foo = testPath("foo.cpp");
672   auto NonEmptyPreamble = R"cpp(
673     #define FOO 1
674     #define BAR 2
675 
676     int main() {}
677   )cpp";
678   constexpr int ReadsToSchedule = 10;
679   std::mutex PreamblesMut;
680   std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
681   S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
682   for (int I = 0; I < ReadsToSchedule; ++I) {
683     S.runWithPreamble(
684         "test", Foo, TUScheduler::Stale,
685         [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
686           std::lock_guard<std::mutex> Lock(PreamblesMut);
687           Preambles[I] = cantFail(std::move(IP)).Preamble;
688         });
689   }
690   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
691   // Check all actions got the same non-null preamble.
692   std::lock_guard<std::mutex> Lock(PreamblesMut);
693   ASSERT_NE(Preambles[0], nullptr);
694   ASSERT_THAT(Preambles, Each(Preambles[0]));
695 }
696 
TEST_F(TUSchedulerTests,NoopOnEmptyChanges)697 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
698   TUScheduler S(CDB, optsForTest(), captureDiags());
699 
700   auto Source = testPath("foo.cpp");
701   auto Header = testPath("foo.h");
702 
703   FS.Files[Header] = "int a;";
704   FS.Timestamps[Header] = time_t(0);
705 
706   std::string SourceContents = R"cpp(
707       #include "foo.h"
708       int b = a;
709     )cpp";
710 
711   // Return value indicates if the updated callback was received.
712   auto DoUpdate = [&](std::string Contents) -> bool {
713     std::atomic<bool> Updated(false);
714     Updated = false;
715     updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
716                     [&Updated](std::vector<Diag>) { Updated = true; });
717     bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
718     if (!UpdateFinished)
719       ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
720     return Updated;
721   };
722 
723   // Test that subsequent updates with the same inputs do not cause rebuilds.
724   ASSERT_TRUE(DoUpdate(SourceContents));
725   ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
726   ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
727   ASSERT_FALSE(DoUpdate(SourceContents));
728   ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
729   ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
730 
731   // Update to a header should cause a rebuild, though.
732   FS.Timestamps[Header] = time_t(1);
733   ASSERT_TRUE(DoUpdate(SourceContents));
734   ASSERT_FALSE(DoUpdate(SourceContents));
735   ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
736   ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
737 
738   // Update to the contents should cause a rebuild.
739   SourceContents += "\nint c = b;";
740   ASSERT_TRUE(DoUpdate(SourceContents));
741   ASSERT_FALSE(DoUpdate(SourceContents));
742   ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
743   ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
744 
745   // Update to the compile commands should also cause a rebuild.
746   CDB.ExtraClangFlags.push_back("-DSOMETHING");
747   ASSERT_TRUE(DoUpdate(SourceContents));
748   ASSERT_FALSE(DoUpdate(SourceContents));
749   ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 4u);
750   ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
751 }
752 
753 // We rebuild if a completely missing header exists, but not if one is added
754 // on a higher-priority include path entry (for performance).
755 // (Previously we wouldn't automatically rebuild when files were added).
TEST_F(TUSchedulerTests,MissingHeader)756 TEST_F(TUSchedulerTests, MissingHeader) {
757   CDB.ExtraClangFlags.push_back("-I" + testPath("a"));
758   CDB.ExtraClangFlags.push_back("-I" + testPath("b"));
759   // Force both directories to exist so they don't get pruned.
760   FS.Files.try_emplace("a/__unused__");
761   FS.Files.try_emplace("b/__unused__");
762   TUScheduler S(CDB, optsForTest(), captureDiags());
763 
764   auto Source = testPath("foo.cpp");
765   auto HeaderA = testPath("a/foo.h");
766   auto HeaderB = testPath("b/foo.h");
767 
768   auto SourceContents = R"cpp(
769       #include "foo.h"
770       int c = b;
771     )cpp";
772 
773   ParseInputs Inputs = getInputs(Source, SourceContents);
774   std::atomic<size_t> DiagCount(0);
775 
776   // Update the source contents, which should trigger an initial build with
777   // the header file missing.
778   updateWithDiags(
779       S, Source, Inputs, WantDiagnostics::Yes,
780       [&DiagCount](std::vector<Diag> Diags) {
781         ++DiagCount;
782         EXPECT_THAT(Diags,
783                     ElementsAre(Field(&Diag::Message, "'foo.h' file not found"),
784                                 Field(&Diag::Message,
785                                       "use of undeclared identifier 'b'")));
786       });
787   S.blockUntilIdle(timeoutSeconds(10));
788 
789   FS.Files[HeaderB] = "int b;";
790   FS.Timestamps[HeaderB] = time_t(1);
791 
792   // The addition of the missing header file triggers a rebuild, no errors.
793   updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
794                   [&DiagCount](std::vector<Diag> Diags) {
795                     ++DiagCount;
796                     EXPECT_THAT(Diags, IsEmpty());
797                   });
798 
799   // Ensure previous assertions are done before we touch the FS again.
800   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
801   // Add the high-priority header file, which should reintroduce the error.
802   FS.Files[HeaderA] = "int a;";
803   FS.Timestamps[HeaderA] = time_t(1);
804 
805   // This isn't detected: we don't stat a/foo.h to validate the preamble.
806   updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
807                   [&DiagCount](std::vector<Diag> Diags) {
808                     ++DiagCount;
809                     ADD_FAILURE()
810                         << "Didn't expect new diagnostics when adding a/foo.h";
811                   });
812 
813   // Forcing the reload should should cause a rebuild.
814   Inputs.ForceRebuild = true;
815   updateWithDiags(
816       S, Source, Inputs, WantDiagnostics::Yes,
817       [&DiagCount](std::vector<Diag> Diags) {
818         ++DiagCount;
819         ElementsAre(Field(&Diag::Message, "use of undeclared identifier 'b'"));
820       });
821 
822   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
823   EXPECT_EQ(DiagCount, 3U);
824 }
825 
TEST_F(TUSchedulerTests,NoChangeDiags)826 TEST_F(TUSchedulerTests, NoChangeDiags) {
827   trace::TestTracer Tracer;
828   TUScheduler S(CDB, optsForTest(), captureDiags());
829 
830   auto FooCpp = testPath("foo.cpp");
831   const auto *Contents = "int a; int b;";
832 
833   EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
834   EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(0));
835   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
836   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
837   updateWithDiags(
838       S, FooCpp, Contents, WantDiagnostics::No,
839       [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
840   S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
841     // Make sure the AST was actually built.
842     cantFail(std::move(IA));
843   });
844   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
845   EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
846   EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(1));
847 
848   // Even though the inputs didn't change and AST can be reused, we need to
849   // report the diagnostics, as they were not reported previously.
850   std::atomic<bool> SeenDiags(false);
851   updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
852                   [&](std::vector<Diag>) { SeenDiags = true; });
853   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
854   ASSERT_TRUE(SeenDiags);
855   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(1));
856   EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
857 
858   // Subsequent request does not get any diagnostics callback because the same
859   // diags have previously been reported and the inputs didn't change.
860   updateWithDiags(
861       S, FooCpp, Contents, WantDiagnostics::Auto,
862       [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
863   ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
864 }
865 
TEST_F(TUSchedulerTests,Run)866 TEST_F(TUSchedulerTests, Run) {
867   for (bool Sync : {false, true}) {
868     auto Opts = optsForTest();
869     if (Sync)
870       Opts.AsyncThreadsCount = 0;
871     TUScheduler S(CDB, Opts);
872     std::atomic<int> Counter(0);
873     S.run("add 1", /*Path=*/"", [&] { ++Counter; });
874     S.run("add 2", /*Path=*/"", [&] { Counter += 2; });
875     ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
876     EXPECT_EQ(Counter.load(), 3);
877 
878     Notification TaskRun;
879     Key<int> TestKey;
880     WithContextValue CtxWithKey(TestKey, 10);
881     const char *Path = "somepath";
882     S.run("props context", Path, [&] {
883       EXPECT_EQ(Context::current().getExisting(TestKey), 10);
884       EXPECT_EQ(Path, boundPath());
885       TaskRun.notify();
886     });
887     TaskRun.wait();
888   }
889 }
890 
TEST_F(TUSchedulerTests,TUStatus)891 TEST_F(TUSchedulerTests, TUStatus) {
892   class CaptureTUStatus : public ClangdServer::Callbacks {
893   public:
894     void onFileUpdated(PathRef File, const TUStatus &Status) override {
895       auto ASTAction = Status.ASTActivity.K;
896       auto PreambleAction = Status.PreambleActivity;
897       std::lock_guard<std::mutex> Lock(Mutex);
898       // Only push the action if it has changed. Since TUStatus can be published
899       // from either Preamble or AST thread and when one changes the other stays
900       // the same.
901       // Note that this can result in missing some updates when something other
902       // than action kind changes, e.g. when AST is built/reused the action kind
903       // stays as Building.
904       if (ASTActions.empty() || ASTActions.back() != ASTAction)
905         ASTActions.push_back(ASTAction);
906       if (PreambleActions.empty() || PreambleActions.back() != PreambleAction)
907         PreambleActions.push_back(PreambleAction);
908     }
909 
910     std::vector<PreambleAction> preambleStatuses() {
911       std::lock_guard<std::mutex> Lock(Mutex);
912       return PreambleActions;
913     }
914 
915     std::vector<ASTAction::Kind> astStatuses() {
916       std::lock_guard<std::mutex> Lock(Mutex);
917       return ASTActions;
918     }
919 
920   private:
921     std::mutex Mutex;
922     std::vector<ASTAction::Kind> ASTActions;
923     std::vector<PreambleAction> PreambleActions;
924   } CaptureTUStatus;
925   MockFS FS;
926   MockCompilationDatabase CDB;
927   ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &CaptureTUStatus);
928   Annotations Code("int m^ain () {}");
929 
930   // We schedule the following tasks in the queue:
931   //   [Update] [GoToDefinition]
932   Server.addDocument(testPath("foo.cpp"), Code.code(), "1",
933                      WantDiagnostics::Auto);
934   ASSERT_TRUE(Server.blockUntilIdleForTest());
935   Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
936                         [](Expected<std::vector<LocatedSymbol>> Result) {
937                           ASSERT_TRUE((bool)Result);
938                         });
939   ASSERT_TRUE(Server.blockUntilIdleForTest());
940 
941   EXPECT_THAT(CaptureTUStatus.preambleStatuses(),
942               ElementsAre(
943                   // PreambleThread starts idle, as the update is first handled
944                   // by ASTWorker.
945                   PreambleAction::Idle,
946                   // Then it starts building first preamble and releases that to
947                   // ASTWorker.
948                   PreambleAction::Building,
949                   // Then goes idle and stays that way as we don't receive any
950                   // more update requests.
951                   PreambleAction::Idle));
952   EXPECT_THAT(CaptureTUStatus.astStatuses(),
953               ElementsAre(
954                   // Starts handling the update action and blocks until the
955                   // first preamble is built.
956                   ASTAction::RunningAction,
957                   // Afterwqards it builds an AST for that preamble to publish
958                   // diagnostics.
959                   ASTAction::Building,
960                   // Then goes idle.
961                   ASTAction::Idle,
962                   // Afterwards we start executing go-to-def.
963                   ASTAction::RunningAction,
964                   // Then go idle.
965                   ASTAction::Idle));
966 }
967 
TEST_F(TUSchedulerTests,CommandLineErrors)968 TEST_F(TUSchedulerTests, CommandLineErrors) {
969   // We should see errors from command-line parsing inside the main file.
970   CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
971 
972   // (!) 'Ready' must live longer than TUScheduler.
973   Notification Ready;
974 
975   TUScheduler S(CDB, optsForTest(), captureDiags());
976   std::vector<Diag> Diagnostics;
977   updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
978                   WantDiagnostics::Yes, [&](std::vector<Diag> D) {
979                     Diagnostics = std::move(D);
980                     Ready.notify();
981                   });
982   Ready.wait();
983 
984   EXPECT_THAT(
985       Diagnostics,
986       ElementsAre(AllOf(
987           Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
988           Field(&Diag::Name, Eq("drv_unknown_argument")),
989           Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
990 }
991 
TEST_F(TUSchedulerTests,CommandLineWarnings)992 TEST_F(TUSchedulerTests, CommandLineWarnings) {
993   // We should not see warnings from command-line parsing.
994   CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
995 
996   // (!) 'Ready' must live longer than TUScheduler.
997   Notification Ready;
998 
999   TUScheduler S(CDB, optsForTest(), captureDiags());
1000   std::vector<Diag> Diagnostics;
1001   updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
1002                   WantDiagnostics::Yes, [&](std::vector<Diag> D) {
1003                     Diagnostics = std::move(D);
1004                     Ready.notify();
1005                   });
1006   Ready.wait();
1007 
1008   EXPECT_THAT(Diagnostics, IsEmpty());
1009 }
1010 
TEST(DebouncePolicy,Compute)1011 TEST(DebouncePolicy, Compute) {
1012   namespace c = std::chrono;
1013   std::vector<DebouncePolicy::clock::duration> History = {
1014       c::seconds(0),
1015       c::seconds(5),
1016       c::seconds(10),
1017       c::seconds(20),
1018   };
1019   DebouncePolicy Policy;
1020   Policy.Min = c::seconds(3);
1021   Policy.Max = c::seconds(25);
1022   // Call Policy.compute(History) and return seconds as a float.
1023   auto Compute = [&](llvm::ArrayRef<DebouncePolicy::clock::duration> History) {
1024     using FloatingSeconds = c::duration<float, c::seconds::period>;
1025     return static_cast<float>(Policy.compute(History) / FloatingSeconds(1));
1026   };
1027   EXPECT_NEAR(10, Compute(History), 0.01) << "(upper) median = 10";
1028   Policy.RebuildRatio = 1.5;
1029   EXPECT_NEAR(15, Compute(History), 0.01) << "median = 10, ratio = 1.5";
1030   Policy.RebuildRatio = 3;
1031   EXPECT_NEAR(25, Compute(History), 0.01) << "constrained by max";
1032   Policy.RebuildRatio = 0;
1033   EXPECT_NEAR(3, Compute(History), 0.01) << "constrained by min";
1034   EXPECT_NEAR(25, Compute({}), 0.01) << "no history -> max";
1035 }
1036 
TEST_F(TUSchedulerTests,AsyncPreambleThread)1037 TEST_F(TUSchedulerTests, AsyncPreambleThread) {
1038   // Blocks preamble thread while building preamble with \p BlockVersion until
1039   // \p N is notified.
1040   class BlockPreambleThread : public ParsingCallbacks {
1041   public:
1042     BlockPreambleThread(llvm::StringRef BlockVersion, Notification &N)
1043         : BlockVersion(BlockVersion), N(N) {}
1044     void onPreambleAST(PathRef Path, llvm::StringRef Version, ASTContext &Ctx,
1045                        std::shared_ptr<clang::Preprocessor> PP,
1046                        const CanonicalIncludes &) override {
1047       if (Version == BlockVersion)
1048         N.wait();
1049     }
1050 
1051   private:
1052     llvm::StringRef BlockVersion;
1053     Notification &N;
1054   };
1055 
1056   static constexpr llvm::StringLiteral InputsV0 = "v0";
1057   static constexpr llvm::StringLiteral InputsV1 = "v1";
1058   Notification Ready;
1059   TUScheduler S(CDB, optsForTest(),
1060                 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1061 
1062   Path File = testPath("foo.cpp");
1063   auto PI = getInputs(File, "");
1064   PI.Version = InputsV0.str();
1065   S.update(File, PI, WantDiagnostics::Auto);
1066   S.blockUntilIdle(timeoutSeconds(10));
1067 
1068   // Block preamble builds.
1069   PI.Version = InputsV1.str();
1070   // Issue second update which will block preamble thread.
1071   S.update(File, PI, WantDiagnostics::Auto);
1072 
1073   Notification RunASTAction;
1074   // Issue an AST read, which shouldn't be blocked and see latest version of the
1075   // file.
1076   S.runWithAST("test", File, [&](Expected<InputsAndAST> AST) {
1077     ASSERT_TRUE(bool(AST));
1078     // Make sure preamble is built with stale inputs, but AST was built using
1079     // new ones.
1080     EXPECT_THAT(AST->AST.preambleVersion(), InputsV0);
1081     EXPECT_THAT(AST->Inputs.Version, InputsV1.str());
1082     RunASTAction.notify();
1083   });
1084   RunASTAction.wait();
1085   Ready.notify();
1086 }
1087 
1088 } // namespace
1089 } // namespace clangd
1090 } // namespace clang
1091