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