[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clangd / unittests / TUSchedulerTests.cpp
blob4e95fcfea91c194e13960e15461a238a61d29a91
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 //===----------------------------------------------------------------------===//
9 #include "Annotations.h"
10 #include "ClangdServer.h"
11 #include "Diagnostics.h"
12 #include "GlobalCompilationDatabase.h"
13 #include "Matchers.h"
14 #include "ParsedAST.h"
15 #include "Preamble.h"
16 #include "TUScheduler.h"
17 #include "TestFS.h"
18 #include "TestIndex.h"
19 #include "support/Cancellation.h"
20 #include "support/Context.h"
21 #include "support/Path.h"
22 #include "support/TestTracer.h"
23 #include "support/Threading.h"
24 #include "support/ThreadsafeFS.h"
25 #include "clang/Basic/DiagnosticDriver.h"
26 #include "llvm/ADT/ArrayRef.h"
27 #include "llvm/ADT/FunctionExtras.h"
28 #include "llvm/ADT/ScopeExit.h"
29 #include "llvm/ADT/StringExtras.h"
30 #include "llvm/ADT/StringMap.h"
31 #include "llvm/ADT/StringRef.h"
32 #include "gmock/gmock.h"
33 #include "gtest/gtest.h"
34 #include <algorithm>
35 #include <atomic>
36 #include <chrono>
37 #include <cstdint>
38 #include <memory>
39 #include <string>
40 #include <utility>
42 namespace clang {
43 namespace clangd {
44 namespace {
46 using ::testing::AllOf;
47 using ::testing::AnyOf;
48 using ::testing::Contains;
49 using ::testing::Each;
50 using ::testing::ElementsAre;
51 using ::testing::Eq;
52 using ::testing::Field;
53 using ::testing::IsEmpty;
54 using ::testing::Not;
55 using ::testing::Pair;
56 using ::testing::Pointee;
57 using ::testing::SizeIs;
58 using ::testing::UnorderedElementsAre;
60 MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
61 if (arg.PreambleActivity != PreambleActivity) {
62 *result_listener << "preamblestate is "
63 << static_cast<uint8_t>(arg.PreambleActivity);
64 return false;
66 if (arg.ASTActivity.K != ASTActivity) {
67 *result_listener << "aststate is " << arg.ASTActivity.K;
68 return false;
70 return true;
73 // Simple ContextProvider to verify the provider is invoked & contexts are used.
74 static Key<std::string> BoundPath;
75 Context bindPath(PathRef F) {
76 return Context::current().derive(BoundPath, F.str());
78 llvm::StringRef boundPath() {
79 const std::string *V = Context::current().get(BoundPath);
80 return V ? *V : llvm::StringRef("");
83 TUScheduler::Options optsForTest() {
84 TUScheduler::Options Opts(ClangdServer::optsForTest());
85 Opts.ContextProvider = bindPath;
86 return Opts;
89 class TUSchedulerTests : public ::testing::Test {
90 protected:
91 ParseInputs getInputs(PathRef File, std::string Contents) {
92 ParseInputs Inputs;
93 Inputs.CompileCommand = *CDB.getCompileCommand(File);
94 Inputs.TFS = &FS;
95 Inputs.Contents = std::move(Contents);
96 Inputs.Opts = ParseOptions();
97 return Inputs;
100 void updateWithCallback(TUScheduler &S, PathRef File,
101 llvm::StringRef Contents, WantDiagnostics WD,
102 llvm::unique_function<void()> CB) {
103 updateWithCallback(S, File, getInputs(File, std::string(Contents)), WD,
104 std::move(CB));
107 void updateWithCallback(TUScheduler &S, PathRef File, ParseInputs Inputs,
108 WantDiagnostics WD,
109 llvm::unique_function<void()> CB) {
110 WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
111 S.update(File, Inputs, WD);
114 static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
115 DiagsCallbackKey;
117 /// A diagnostics callback that should be passed to TUScheduler when it's used
118 /// in updateWithDiags.
119 static std::unique_ptr<ParsingCallbacks> captureDiags() {
120 class CaptureDiags : public ParsingCallbacks {
121 public:
122 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
123 reportDiagnostics(File, *AST.getDiagnostics(), Publish);
126 void onFailedAST(PathRef File, llvm::StringRef Version,
127 std::vector<Diag> Diags, PublishFn Publish) override {
128 reportDiagnostics(File, Diags, Publish);
131 private:
132 void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
133 PublishFn Publish) {
134 auto *D = Context::current().get(DiagsCallbackKey);
135 if (!D)
136 return;
137 Publish([&]() {
138 const_cast<
139 llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
140 File, std::move(Diags));
144 return std::make_unique<CaptureDiags>();
147 /// Schedule an update and call \p CB with the diagnostics it produces, if
148 /// any. The TUScheduler should be created with captureDiags as a
149 /// DiagsCallback for this to work.
150 void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
151 WantDiagnostics WD,
152 llvm::unique_function<void(std::vector<Diag>)> CB) {
153 Path OrigFile = File.str();
154 WithContextValue Ctx(DiagsCallbackKey,
155 [OrigFile, CB = std::move(CB)](
156 PathRef File, std::vector<Diag> Diags) mutable {
157 assert(File == OrigFile);
158 CB(std::move(Diags));
160 S.update(File, std::move(Inputs), WD);
163 void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
164 WantDiagnostics WD,
165 llvm::unique_function<void(std::vector<Diag>)> CB) {
166 return updateWithDiags(S, File, getInputs(File, std::string(Contents)), WD,
167 std::move(CB));
170 MockFS FS;
171 MockCompilationDatabase CDB;
174 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
175 TUSchedulerTests::DiagsCallbackKey;
177 TEST_F(TUSchedulerTests, MissingFiles) {
178 TUScheduler S(CDB, optsForTest());
180 auto Added = testPath("added.cpp");
181 FS.Files[Added] = "x";
183 auto Missing = testPath("missing.cpp");
184 FS.Files[Missing] = "";
186 S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
188 // Assert each operation for missing file is an error (even if it's
189 // available in VFS).
190 S.runWithAST("", Missing,
191 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
192 S.runWithPreamble(
193 "", Missing, TUScheduler::Stale,
194 [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
195 // remove() shouldn't crash on missing files.
196 S.remove(Missing);
198 // Assert there aren't any errors for added file.
199 S.runWithAST("", Added,
200 [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
201 S.runWithPreamble("", Added, TUScheduler::Stale,
202 [&](Expected<InputsAndPreamble> Preamble) {
203 EXPECT_TRUE(bool(Preamble));
205 S.remove(Added);
207 // Assert that all operations fail after removing the file.
208 S.runWithAST("", Added,
209 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
210 S.runWithPreamble("", Added, TUScheduler::Stale,
211 [&](Expected<InputsAndPreamble> Preamble) {
212 ASSERT_FALSE(bool(Preamble));
213 llvm::consumeError(Preamble.takeError());
215 // remove() shouldn't crash on missing files.
216 S.remove(Added);
219 TEST_F(TUSchedulerTests, WantDiagnostics) {
220 std::atomic<int> CallbackCount(0);
222 // To avoid a racy test, don't allow tasks to actually run on the worker
223 // thread until we've scheduled them all.
224 Notification Ready;
225 TUScheduler S(CDB, optsForTest(), captureDiags());
226 auto Path = testPath("foo.cpp");
227 updateWithDiags(S, Path, "", WantDiagnostics::Yes,
228 [&](std::vector<Diag>) { Ready.wait(); });
229 updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
230 [&](std::vector<Diag>) { ++CallbackCount; });
231 updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
232 [&](std::vector<Diag>) {
233 ADD_FAILURE()
234 << "auto should have been cancelled by auto";
236 updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
237 [&](std::vector<Diag>) {
238 ADD_FAILURE() << "no diags should not be called back";
240 updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
241 [&](std::vector<Diag>) { ++CallbackCount; });
242 Ready.notify();
244 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
246 EXPECT_EQ(2, CallbackCount);
249 TEST_F(TUSchedulerTests, Debounce) {
250 auto Opts = optsForTest();
251 Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::milliseconds(500));
252 TUScheduler S(CDB, Opts, captureDiags());
253 auto Path = testPath("foo.cpp");
254 // Issue a write that's going to be debounced away.
255 updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
256 [&](std::vector<Diag>) {
257 ADD_FAILURE()
258 << "auto should have been debounced and canceled";
260 // Sleep a bit to verify that it's really debounce that's holding diagnostics.
261 std::this_thread::sleep_for(std::chrono::milliseconds(50));
263 // Issue another write, this time we'll wait for its diagnostics.
264 Notification N;
265 updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
266 [&](std::vector<Diag>) { N.notify(); });
267 EXPECT_TRUE(N.wait(timeoutSeconds(5)));
269 // Once we start shutting down the TUScheduler, this one becomes a dead write.
270 updateWithDiags(S, Path, "auto (discarded)", WantDiagnostics::Auto,
271 [&](std::vector<Diag>) {
272 ADD_FAILURE()
273 << "auto should have been discarded (dead write)";
277 TEST_F(TUSchedulerTests, Cancellation) {
278 // We have the following update/read sequence
279 // U0
280 // U1(WantDiags=Yes) <-- cancelled
281 // R1 <-- cancelled
282 // U2(WantDiags=Yes) <-- cancelled
283 // R2A <-- cancelled
284 // R2B
285 // U3(WantDiags=Yes)
286 // R3 <-- cancelled
287 std::vector<StringRef> DiagsSeen, ReadsSeen, ReadsCanceled;
289 Notification Proceed; // Ensure we schedule everything.
290 TUScheduler S(CDB, optsForTest(), captureDiags());
291 auto Path = testPath("foo.cpp");
292 // Helper to schedule a named update and return a function to cancel it.
293 auto Update = [&](StringRef ID) -> Canceler {
294 auto T = cancelableTask();
295 WithContext C(std::move(T.first));
296 updateWithDiags(
297 S, Path, ("//" + ID).str(), WantDiagnostics::Yes,
298 [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
299 return std::move(T.second);
301 // Helper to schedule a named read and return a function to cancel it.
302 auto Read = [&](StringRef ID) -> Canceler {
303 auto T = cancelableTask();
304 WithContext C(std::move(T.first));
305 S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
306 if (auto Err = E.takeError()) {
307 if (Err.isA<CancelledError>()) {
308 ReadsCanceled.push_back(ID);
309 consumeError(std::move(Err));
310 } else {
311 ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
312 << llvm::toString(std::move(Err));
314 } else {
315 ReadsSeen.push_back(ID);
318 return std::move(T.second);
321 updateWithCallback(S, Path, "", WantDiagnostics::Yes,
322 [&]() { Proceed.wait(); });
323 // The second parens indicate cancellation, where present.
324 Update("U1")();
325 Read("R1")();
326 Update("U2")();
327 Read("R2A")();
328 Read("R2B");
329 Update("U3");
330 Read("R3")();
331 Proceed.notify();
333 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
335 EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
336 << "U1 and all dependent reads were cancelled. "
337 "U2 has a dependent read R2A. "
338 "U3 was not cancelled.";
339 EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
340 << "All reads other than R2B were cancelled";
341 EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
342 << "All reads other than R2B were cancelled";
345 TEST_F(TUSchedulerTests, InvalidationNoCrash) {
346 auto Path = testPath("foo.cpp");
347 TUScheduler S(CDB, optsForTest(), captureDiags());
349 Notification StartedRunning;
350 Notification ScheduledChange;
351 // We expect invalidation logic to not crash by trying to invalidate a running
352 // request.
353 S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
354 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
355 S.runWithAST(
356 "invalidatable-but-running", Path,
357 [&](llvm::Expected<InputsAndAST> AST) {
358 StartedRunning.notify();
359 ScheduledChange.wait();
360 ASSERT_TRUE(bool(AST));
362 TUScheduler::InvalidateOnUpdate);
363 StartedRunning.wait();
364 S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
365 ScheduledChange.notify();
366 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
369 TEST_F(TUSchedulerTests, Invalidation) {
370 auto Path = testPath("foo.cpp");
371 TUScheduler S(CDB, optsForTest(), captureDiags());
372 std::atomic<int> Builds(0), Actions(0);
374 Notification Start;
375 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
376 ++Builds;
377 Start.wait();
379 S.runWithAST(
380 "invalidatable", Path,
381 [&](llvm::Expected<InputsAndAST> AST) {
382 ++Actions;
383 EXPECT_FALSE(bool(AST));
384 llvm::Error E = AST.takeError();
385 EXPECT_TRUE(E.isA<CancelledError>());
386 handleAllErrors(std::move(E), [&](const CancelledError &E) {
387 EXPECT_EQ(E.Reason, static_cast<int>(ErrorCode::ContentModified));
390 TUScheduler::InvalidateOnUpdate);
391 S.runWithAST(
392 "not-invalidatable", Path,
393 [&](llvm::Expected<InputsAndAST> AST) {
394 ++Actions;
395 EXPECT_TRUE(bool(AST));
397 TUScheduler::NoInvalidation);
398 updateWithDiags(S, Path, "b", WantDiagnostics::Auto, [&](std::vector<Diag>) {
399 ++Builds;
400 ADD_FAILURE() << "Shouldn't build, all dependents invalidated";
402 S.runWithAST(
403 "invalidatable", Path,
404 [&](llvm::Expected<InputsAndAST> AST) {
405 ++Actions;
406 EXPECT_FALSE(bool(AST));
407 llvm::Error E = AST.takeError();
408 EXPECT_TRUE(E.isA<CancelledError>());
409 consumeError(std::move(E));
411 TUScheduler::InvalidateOnUpdate);
412 updateWithDiags(S, Path, "c", WantDiagnostics::Auto,
413 [&](std::vector<Diag>) { ++Builds; });
414 S.runWithAST(
415 "invalidatable", Path,
416 [&](llvm::Expected<InputsAndAST> AST) {
417 ++Actions;
418 EXPECT_TRUE(bool(AST)) << "Shouldn't be invalidated, no update follows";
420 TUScheduler::InvalidateOnUpdate);
421 Start.notify();
422 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
424 EXPECT_EQ(2, Builds.load()) << "Middle build should be skipped";
425 EXPECT_EQ(4, Actions.load()) << "All actions should run (some with error)";
428 // We don't invalidate requests for updates that don't change the file content.
429 // These are mostly "refresh this file" events synthesized inside clangd itself.
430 // (Usually the AST rebuild is elided after verifying that all inputs are
431 // unchanged, but invalidation decisions happen earlier and so independently).
432 // See https://github.com/clangd/clangd/issues/620
433 TEST_F(TUSchedulerTests, InvalidationUnchanged) {
434 auto Path = testPath("foo.cpp");
435 TUScheduler S(CDB, optsForTest(), captureDiags());
436 std::atomic<int> Actions(0);
438 Notification Start;
439 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
440 Start.wait();
442 S.runWithAST(
443 "invalidatable", Path,
444 [&](llvm::Expected<InputsAndAST> AST) {
445 ++Actions;
446 EXPECT_TRUE(bool(AST))
447 << "Should not invalidate based on an update with same content: "
448 << llvm::toString(AST.takeError());
450 TUScheduler::InvalidateOnUpdate);
451 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
452 ADD_FAILURE() << "Shouldn't build, identical to previous";
454 Start.notify();
455 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
457 EXPECT_EQ(1, Actions.load()) << "All actions should run";
460 TEST_F(TUSchedulerTests, ManyUpdates) {
461 const int FilesCount = 3;
462 const int UpdatesPerFile = 10;
464 std::mutex Mut;
465 int TotalASTReads = 0;
466 int TotalPreambleReads = 0;
467 int TotalUpdates = 0;
468 llvm::StringMap<int> LatestDiagVersion;
470 // Run TUScheduler and collect some stats.
472 auto Opts = optsForTest();
473 Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::milliseconds(50));
474 TUScheduler S(CDB, Opts, captureDiags());
476 std::vector<std::string> Files;
477 for (int I = 0; I < FilesCount; ++I) {
478 std::string Name = "foo" + std::to_string(I) + ".cpp";
479 Files.push_back(testPath(Name));
480 this->FS.Files[Files.back()] = "";
483 StringRef Contents1 = R"cpp(int a;)cpp";
484 StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
485 StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
487 StringRef AllContents[] = {Contents1, Contents2, Contents3};
488 const int AllContentsSize = 3;
490 // Scheduler may run tasks asynchronously, but should propagate the
491 // context. We stash a nonce in the context, and verify it in the task.
492 static Key<int> NonceKey;
493 int Nonce = 0;
495 for (int FileI = 0; FileI < FilesCount; ++FileI) {
496 for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
497 auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
499 auto File = Files[FileI];
500 auto Inputs = getInputs(File, Contents.str());
502 WithContextValue WithNonce(NonceKey, ++Nonce);
503 Inputs.Version = std::to_string(UpdateI);
504 updateWithDiags(
505 S, File, Inputs, WantDiagnostics::Auto,
506 [File, Nonce, Version(Inputs.Version), &Mut, &TotalUpdates,
507 &LatestDiagVersion](std::vector<Diag>) {
508 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
509 EXPECT_EQ(File, boundPath());
511 std::lock_guard<std::mutex> Lock(Mut);
512 ++TotalUpdates;
513 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
514 // Make sure Diags are for a newer version.
515 auto It = LatestDiagVersion.try_emplace(File, -1);
516 const int PrevVersion = It.first->second;
517 int CurVersion;
518 ASSERT_TRUE(llvm::to_integer(Version, CurVersion, 10));
519 EXPECT_LT(PrevVersion, CurVersion);
520 It.first->getValue() = CurVersion;
524 WithContextValue WithNonce(NonceKey, ++Nonce);
525 S.runWithAST(
526 "CheckAST", File,
527 [File, Inputs, Nonce, &Mut,
528 &TotalASTReads](Expected<InputsAndAST> AST) {
529 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
530 EXPECT_EQ(File, boundPath());
532 ASSERT_TRUE((bool)AST);
533 EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
534 EXPECT_EQ(AST->Inputs.Version, Inputs.Version);
535 EXPECT_EQ(AST->AST.version(), Inputs.Version);
537 std::lock_guard<std::mutex> Lock(Mut);
538 ++TotalASTReads;
539 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
544 WithContextValue WithNonce(NonceKey, ++Nonce);
545 S.runWithPreamble(
546 "CheckPreamble", File, TUScheduler::Stale,
547 [File, Inputs, Nonce, &Mut,
548 &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
549 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
550 EXPECT_EQ(File, boundPath());
552 ASSERT_TRUE((bool)Preamble);
553 EXPECT_EQ(Preamble->Contents, Inputs.Contents);
555 std::lock_guard<std::mutex> Lock(Mut);
556 ++TotalPreambleReads;
557 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
562 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
563 } // TUScheduler destructor waits for all operations to finish.
565 std::lock_guard<std::mutex> Lock(Mut);
566 // Updates might get coalesced in preamble thread and result in dropping
567 // diagnostics for intermediate snapshots.
568 EXPECT_GE(TotalUpdates, FilesCount);
569 EXPECT_LE(TotalUpdates, FilesCount * UpdatesPerFile);
570 // We should receive diags for last update.
571 for (const auto &Entry : LatestDiagVersion)
572 EXPECT_EQ(Entry.second, UpdatesPerFile - 1);
573 EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
574 EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
577 TEST_F(TUSchedulerTests, EvictedAST) {
578 std::atomic<int> BuiltASTCounter(0);
579 auto Opts = optsForTest();
580 Opts.AsyncThreadsCount = 1;
581 Opts.RetentionPolicy.MaxRetainedASTs = 2;
582 trace::TestTracer Tracer;
583 TUScheduler S(CDB, Opts);
585 llvm::StringLiteral SourceContents = R"cpp(
586 int* a;
587 double* b = a;
588 )cpp";
589 llvm::StringLiteral OtherSourceContents = R"cpp(
590 int* a;
591 double* b = a + 0;
592 )cpp";
594 auto Foo = testPath("foo.cpp");
595 auto Bar = testPath("bar.cpp");
596 auto Baz = testPath("baz.cpp");
598 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
599 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
600 // Build one file in advance. We will not access it later, so it will be the
601 // one that the cache will evict.
602 updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
603 [&BuiltASTCounter]() { ++BuiltASTCounter; });
604 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
605 ASSERT_EQ(BuiltASTCounter.load(), 1);
606 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
607 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
609 // Build two more files. Since we can retain only 2 ASTs, these should be
610 // the ones we see in the cache later.
611 updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
612 [&BuiltASTCounter]() { ++BuiltASTCounter; });
613 updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
614 [&BuiltASTCounter]() { ++BuiltASTCounter; });
615 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
616 ASSERT_EQ(BuiltASTCounter.load(), 3);
617 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
618 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(2));
620 // Check only the last two ASTs are retained.
621 ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
623 // Access the old file again.
624 updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
625 [&BuiltASTCounter]() { ++BuiltASTCounter; });
626 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
627 ASSERT_EQ(BuiltASTCounter.load(), 4);
628 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
629 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
631 // Check the AST for foo.cpp is retained now and one of the others got
632 // evicted.
633 EXPECT_THAT(S.getFilesWithCachedAST(),
634 UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
637 // We send "empty" changes to TUScheduler when we think some external event
638 // *might* have invalidated current state (e.g. a header was edited).
639 // Verify that this doesn't evict our cache entries.
640 TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
641 auto Opts = optsForTest();
642 Opts.RetentionPolicy.MaxRetainedASTs = 1;
643 TUScheduler S(CDB, Opts);
645 auto Foo = testPath("foo.cpp");
646 auto FooInputs = getInputs(Foo, "int x=1;");
647 auto Bar = testPath("bar.cpp");
648 auto BarInputs = getInputs(Bar, "int x=2;");
650 // After opening Foo then Bar, AST cache contains Bar.
651 S.update(Foo, FooInputs, WantDiagnostics::Auto);
652 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
653 S.update(Bar, BarInputs, WantDiagnostics::Auto);
654 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
655 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
657 // Any number of no-op updates to Foo don't dislodge Bar from the cache.
658 S.update(Foo, FooInputs, WantDiagnostics::Auto);
659 S.update(Foo, FooInputs, WantDiagnostics::Auto);
660 S.update(Foo, FooInputs, WantDiagnostics::Auto);
661 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
662 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
663 // In fact each file has been built only once.
664 ASSERT_EQ(S.fileStats().lookup(Foo).ASTBuilds, 1u);
665 ASSERT_EQ(S.fileStats().lookup(Bar).ASTBuilds, 1u);
668 TEST_F(TUSchedulerTests, EmptyPreamble) {
669 TUScheduler S(CDB, optsForTest());
671 auto Foo = testPath("foo.cpp");
672 auto Header = testPath("foo.h");
674 FS.Files[Header] = "void foo()";
675 FS.Timestamps[Header] = time_t(0);
676 auto *WithPreamble = R"cpp(
677 #include "foo.h"
678 int main() {}
679 )cpp";
680 auto *WithEmptyPreamble = R"cpp(int main() {})cpp";
681 S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
682 S.runWithPreamble(
683 "getNonEmptyPreamble", Foo, TUScheduler::Stale,
684 [&](Expected<InputsAndPreamble> Preamble) {
685 // We expect to get a non-empty preamble.
686 EXPECT_GT(
687 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
688 0u);
690 // Wait while the preamble is being built.
691 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
693 // Update the file which results in an empty preamble.
694 S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
695 // Wait while the preamble is being built.
696 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
697 S.runWithPreamble(
698 "getEmptyPreamble", Foo, TUScheduler::Stale,
699 [&](Expected<InputsAndPreamble> Preamble) {
700 // We expect to get an empty preamble.
701 EXPECT_EQ(
702 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
703 0u);
707 TEST_F(TUSchedulerTests, ASTSignalsSmokeTests) {
708 TUScheduler S(CDB, optsForTest());
709 auto Foo = testPath("foo.cpp");
710 auto Header = testPath("foo.h");
712 FS.Files[Header] = "namespace tar { int foo(); }";
713 const char *Contents = R"cpp(
714 #include "foo.h"
715 namespace ns {
716 int func() {
717 return tar::foo());
719 } // namespace ns
720 )cpp";
721 // Update the file which results in an empty preamble.
722 S.update(Foo, getInputs(Foo, Contents), WantDiagnostics::Yes);
723 // Wait while the preamble is being built.
724 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
725 Notification TaskRun;
726 S.runWithPreamble(
727 "ASTSignals", Foo, TUScheduler::Stale,
728 [&](Expected<InputsAndPreamble> IP) {
729 ASSERT_FALSE(!IP);
730 std::vector<std::pair<StringRef, int>> NS;
731 for (const auto &P : IP->Signals->RelatedNamespaces)
732 NS.emplace_back(P.getKey(), P.getValue());
733 EXPECT_THAT(NS,
734 UnorderedElementsAre(Pair("ns::", 1), Pair("tar::", 1)));
736 std::vector<std::pair<SymbolID, int>> Sym;
737 for (const auto &P : IP->Signals->ReferencedSymbols)
738 Sym.emplace_back(P.getFirst(), P.getSecond());
739 EXPECT_THAT(Sym, UnorderedElementsAre(Pair(ns("tar").ID, 1),
740 Pair(ns("ns").ID, 1),
741 Pair(func("tar::foo").ID, 1),
742 Pair(func("ns::func").ID, 1)));
743 TaskRun.notify();
745 TaskRun.wait();
748 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
749 // Testing strategy: we update the file and schedule a few preamble reads at
750 // the same time. All reads should get the same non-null preamble.
751 TUScheduler S(CDB, optsForTest());
752 auto Foo = testPath("foo.cpp");
753 auto *NonEmptyPreamble = R"cpp(
754 #define FOO 1
755 #define BAR 2
757 int main() {}
758 )cpp";
759 constexpr int ReadsToSchedule = 10;
760 std::mutex PreamblesMut;
761 std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
762 S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
763 for (int I = 0; I < ReadsToSchedule; ++I) {
764 S.runWithPreamble(
765 "test", Foo, TUScheduler::Stale,
766 [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
767 std::lock_guard<std::mutex> Lock(PreamblesMut);
768 Preambles[I] = cantFail(std::move(IP)).Preamble;
771 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
772 // Check all actions got the same non-null preamble.
773 std::lock_guard<std::mutex> Lock(PreamblesMut);
774 ASSERT_NE(Preambles[0], nullptr);
775 ASSERT_THAT(Preambles, Each(Preambles[0]));
778 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
779 TUScheduler S(CDB, optsForTest(), captureDiags());
781 auto Source = testPath("foo.cpp");
782 auto Header = testPath("foo.h");
784 FS.Files[Header] = "int a;";
785 FS.Timestamps[Header] = time_t(0);
787 std::string SourceContents = R"cpp(
788 #include "foo.h"
789 int b = a;
790 )cpp";
792 // Return value indicates if the updated callback was received.
793 auto DoUpdate = [&](std::string Contents) -> bool {
794 std::atomic<bool> Updated(false);
795 Updated = false;
796 updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
797 [&Updated](std::vector<Diag>) { Updated = true; });
798 bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
799 if (!UpdateFinished)
800 ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
801 return Updated;
804 // Test that subsequent updates with the same inputs do not cause rebuilds.
805 ASSERT_TRUE(DoUpdate(SourceContents));
806 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
807 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
808 ASSERT_FALSE(DoUpdate(SourceContents));
809 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
810 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
812 // Update to a header should cause a rebuild, though.
813 FS.Timestamps[Header] = time_t(1);
814 ASSERT_TRUE(DoUpdate(SourceContents));
815 ASSERT_FALSE(DoUpdate(SourceContents));
816 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
817 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
819 // Update to the contents should cause a rebuild.
820 SourceContents += "\nint c = b;";
821 ASSERT_TRUE(DoUpdate(SourceContents));
822 ASSERT_FALSE(DoUpdate(SourceContents));
823 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
824 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
826 // Update to the compile commands should also cause a rebuild.
827 CDB.ExtraClangFlags.push_back("-DSOMETHING");
828 ASSERT_TRUE(DoUpdate(SourceContents));
829 ASSERT_FALSE(DoUpdate(SourceContents));
830 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 4u);
831 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
834 // We rebuild if a completely missing header exists, but not if one is added
835 // on a higher-priority include path entry (for performance).
836 // (Previously we wouldn't automatically rebuild when files were added).
837 TEST_F(TUSchedulerTests, MissingHeader) {
838 CDB.ExtraClangFlags.push_back("-I" + testPath("a"));
839 CDB.ExtraClangFlags.push_back("-I" + testPath("b"));
840 // Force both directories to exist so they don't get pruned.
841 FS.Files.try_emplace("a/__unused__");
842 FS.Files.try_emplace("b/__unused__");
843 TUScheduler S(CDB, optsForTest(), captureDiags());
845 auto Source = testPath("foo.cpp");
846 auto HeaderA = testPath("a/foo.h");
847 auto HeaderB = testPath("b/foo.h");
849 auto *SourceContents = R"cpp(
850 #include "foo.h"
851 int c = b;
852 )cpp";
854 ParseInputs Inputs = getInputs(Source, SourceContents);
855 std::atomic<size_t> DiagCount(0);
857 // Update the source contents, which should trigger an initial build with
858 // the header file missing.
859 updateWithDiags(
860 S, Source, Inputs, WantDiagnostics::Yes,
861 [&DiagCount](std::vector<Diag> Diags) {
862 ++DiagCount;
863 EXPECT_THAT(Diags,
864 ElementsAre(Field(&Diag::Message, "'foo.h' file not found"),
865 Field(&Diag::Message,
866 "use of undeclared identifier 'b'")));
868 S.blockUntilIdle(timeoutSeconds(10));
870 FS.Files[HeaderB] = "int b;";
871 FS.Timestamps[HeaderB] = time_t(1);
873 // The addition of the missing header file triggers a rebuild, no errors.
874 updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
875 [&DiagCount](std::vector<Diag> Diags) {
876 ++DiagCount;
877 EXPECT_THAT(Diags, IsEmpty());
880 // Ensure previous assertions are done before we touch the FS again.
881 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
882 // Add the high-priority header file, which should reintroduce the error.
883 FS.Files[HeaderA] = "int a;";
884 FS.Timestamps[HeaderA] = time_t(1);
886 // This isn't detected: we don't stat a/foo.h to validate the preamble.
887 updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
888 [&DiagCount](std::vector<Diag> Diags) {
889 ++DiagCount;
890 ADD_FAILURE()
891 << "Didn't expect new diagnostics when adding a/foo.h";
894 // Forcing the reload should should cause a rebuild.
895 Inputs.ForceRebuild = true;
896 updateWithDiags(
897 S, Source, Inputs, WantDiagnostics::Yes,
898 [&DiagCount](std::vector<Diag> Diags) {
899 ++DiagCount;
900 ElementsAre(Field(&Diag::Message, "use of undeclared identifier 'b'"));
903 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
904 EXPECT_EQ(DiagCount, 3U);
907 TEST_F(TUSchedulerTests, NoChangeDiags) {
908 trace::TestTracer Tracer;
909 TUScheduler S(CDB, optsForTest(), captureDiags());
911 auto FooCpp = testPath("foo.cpp");
912 const auto *Contents = "int a; int b;";
914 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
915 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(0));
916 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
917 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
918 updateWithDiags(
919 S, FooCpp, Contents, WantDiagnostics::No,
920 [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
921 S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
922 // Make sure the AST was actually built.
923 cantFail(std::move(IA));
925 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
926 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
927 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(1));
929 // Even though the inputs didn't change and AST can be reused, we need to
930 // report the diagnostics, as they were not reported previously.
931 std::atomic<bool> SeenDiags(false);
932 updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
933 [&](std::vector<Diag>) { SeenDiags = true; });
934 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
935 ASSERT_TRUE(SeenDiags);
936 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(1));
937 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
939 // Subsequent request does not get any diagnostics callback because the same
940 // diags have previously been reported and the inputs didn't change.
941 updateWithDiags(
942 S, FooCpp, Contents, WantDiagnostics::Auto,
943 [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
944 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
947 TEST_F(TUSchedulerTests, Run) {
948 for (bool Sync : {false, true}) {
949 auto Opts = optsForTest();
950 if (Sync)
951 Opts.AsyncThreadsCount = 0;
952 TUScheduler S(CDB, Opts);
953 std::atomic<int> Counter(0);
954 S.run("add 1", /*Path=*/"", [&] { ++Counter; });
955 S.run("add 2", /*Path=*/"", [&] { Counter += 2; });
956 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
957 EXPECT_EQ(Counter.load(), 3);
959 Notification TaskRun;
960 Key<int> TestKey;
961 WithContextValue CtxWithKey(TestKey, 10);
962 const char *Path = "somepath";
963 S.run("props context", Path, [&] {
964 EXPECT_EQ(Context::current().getExisting(TestKey), 10);
965 EXPECT_EQ(Path, boundPath());
966 TaskRun.notify();
968 TaskRun.wait();
972 TEST_F(TUSchedulerTests, TUStatus) {
973 class CaptureTUStatus : public ClangdServer::Callbacks {
974 public:
975 void onFileUpdated(PathRef File, const TUStatus &Status) override {
976 auto ASTAction = Status.ASTActivity.K;
977 auto PreambleAction = Status.PreambleActivity;
978 std::lock_guard<std::mutex> Lock(Mutex);
979 // Only push the action if it has changed. Since TUStatus can be published
980 // from either Preamble or AST thread and when one changes the other stays
981 // the same.
982 // Note that this can result in missing some updates when something other
983 // than action kind changes, e.g. when AST is built/reused the action kind
984 // stays as Building.
985 if (ASTActions.empty() || ASTActions.back() != ASTAction)
986 ASTActions.push_back(ASTAction);
987 if (PreambleActions.empty() || PreambleActions.back() != PreambleAction)
988 PreambleActions.push_back(PreambleAction);
991 std::vector<PreambleAction> preambleStatuses() {
992 std::lock_guard<std::mutex> Lock(Mutex);
993 return PreambleActions;
996 std::vector<ASTAction::Kind> astStatuses() {
997 std::lock_guard<std::mutex> Lock(Mutex);
998 return ASTActions;
1001 private:
1002 std::mutex Mutex;
1003 std::vector<ASTAction::Kind> ASTActions;
1004 std::vector<PreambleAction> PreambleActions;
1005 } CaptureTUStatus;
1006 MockFS FS;
1007 MockCompilationDatabase CDB;
1008 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &CaptureTUStatus);
1009 Annotations Code("int m^ain () {}");
1011 // We schedule the following tasks in the queue:
1012 // [Update] [GoToDefinition]
1013 Server.addDocument(testPath("foo.cpp"), Code.code(), "1",
1014 WantDiagnostics::Auto);
1015 ASSERT_TRUE(Server.blockUntilIdleForTest());
1016 Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
1017 [](Expected<std::vector<LocatedSymbol>> Result) {
1018 ASSERT_TRUE((bool)Result);
1020 ASSERT_TRUE(Server.blockUntilIdleForTest());
1022 EXPECT_THAT(CaptureTUStatus.preambleStatuses(),
1023 ElementsAre(
1024 // PreambleThread starts idle, as the update is first handled
1025 // by ASTWorker.
1026 PreambleAction::Idle,
1027 // Then it starts building first preamble and releases that to
1028 // ASTWorker.
1029 PreambleAction::Building,
1030 // Then goes idle and stays that way as we don't receive any
1031 // more update requests.
1032 PreambleAction::Idle));
1033 EXPECT_THAT(CaptureTUStatus.astStatuses(),
1034 ElementsAre(
1035 // Starts handling the update action and blocks until the
1036 // first preamble is built.
1037 ASTAction::RunningAction,
1038 // Afterwards it builds an AST for that preamble to publish
1039 // diagnostics.
1040 ASTAction::Building,
1041 // Then goes idle.
1042 ASTAction::Idle,
1043 // Afterwards we start executing go-to-def.
1044 ASTAction::RunningAction,
1045 // Then go idle.
1046 ASTAction::Idle));
1049 TEST_F(TUSchedulerTests, CommandLineErrors) {
1050 // We should see errors from command-line parsing inside the main file.
1051 CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
1053 // (!) 'Ready' must live longer than TUScheduler.
1054 Notification Ready;
1056 TUScheduler S(CDB, optsForTest(), captureDiags());
1057 std::vector<Diag> Diagnostics;
1058 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
1059 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
1060 Diagnostics = std::move(D);
1061 Ready.notify();
1063 Ready.wait();
1065 EXPECT_THAT(
1066 Diagnostics,
1067 ElementsAre(AllOf(
1068 Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
1069 Field(&Diag::Name, Eq("drv_unknown_argument")),
1070 Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
1073 TEST_F(TUSchedulerTests, CommandLineWarnings) {
1074 // We should not see warnings from command-line parsing.
1075 CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
1077 // (!) 'Ready' must live longer than TUScheduler.
1078 Notification Ready;
1080 TUScheduler S(CDB, optsForTest(), captureDiags());
1081 std::vector<Diag> Diagnostics;
1082 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
1083 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
1084 Diagnostics = std::move(D);
1085 Ready.notify();
1087 Ready.wait();
1089 EXPECT_THAT(Diagnostics, IsEmpty());
1092 TEST(DebouncePolicy, Compute) {
1093 namespace c = std::chrono;
1094 DebouncePolicy::clock::duration History[] = {
1095 c::seconds(0),
1096 c::seconds(5),
1097 c::seconds(10),
1098 c::seconds(20),
1100 DebouncePolicy Policy;
1101 Policy.Min = c::seconds(3);
1102 Policy.Max = c::seconds(25);
1103 // Call Policy.compute(History) and return seconds as a float.
1104 auto Compute = [&](llvm::ArrayRef<DebouncePolicy::clock::duration> History) {
1105 return c::duration_cast<c::duration<float, c::seconds::period>>(
1106 Policy.compute(History))
1107 .count();
1109 EXPECT_NEAR(10, Compute(History), 0.01) << "(upper) median = 10";
1110 Policy.RebuildRatio = 1.5;
1111 EXPECT_NEAR(15, Compute(History), 0.01) << "median = 10, ratio = 1.5";
1112 Policy.RebuildRatio = 3;
1113 EXPECT_NEAR(25, Compute(History), 0.01) << "constrained by max";
1114 Policy.RebuildRatio = 0;
1115 EXPECT_NEAR(3, Compute(History), 0.01) << "constrained by min";
1116 EXPECT_NEAR(25, Compute({}), 0.01) << "no history -> max";
1119 TEST_F(TUSchedulerTests, AsyncPreambleThread) {
1120 // Blocks preamble thread while building preamble with \p BlockVersion until
1121 // \p N is notified.
1122 class BlockPreambleThread : public ParsingCallbacks {
1123 public:
1124 BlockPreambleThread(llvm::StringRef BlockVersion, Notification &N)
1125 : BlockVersion(BlockVersion), N(N) {}
1126 void onPreambleAST(PathRef Path, llvm::StringRef Version,
1127 const CompilerInvocation &, ASTContext &Ctx,
1128 Preprocessor &, const CanonicalIncludes &) override {
1129 if (Version == BlockVersion)
1130 N.wait();
1133 private:
1134 llvm::StringRef BlockVersion;
1135 Notification &N;
1138 static constexpr llvm::StringLiteral InputsV0 = "v0";
1139 static constexpr llvm::StringLiteral InputsV1 = "v1";
1140 Notification Ready;
1141 TUScheduler S(CDB, optsForTest(),
1142 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1144 Path File = testPath("foo.cpp");
1145 auto PI = getInputs(File, "");
1146 PI.Version = InputsV0.str();
1147 S.update(File, PI, WantDiagnostics::Auto);
1148 S.blockUntilIdle(timeoutSeconds(10));
1150 // Block preamble builds.
1151 PI.Version = InputsV1.str();
1152 // Issue second update which will block preamble thread.
1153 S.update(File, PI, WantDiagnostics::Auto);
1155 Notification RunASTAction;
1156 // Issue an AST read, which shouldn't be blocked and see latest version of the
1157 // file.
1158 S.runWithAST("test", File, [&](Expected<InputsAndAST> AST) {
1159 ASSERT_TRUE(bool(AST));
1160 // Make sure preamble is built with stale inputs, but AST was built using
1161 // new ones.
1162 EXPECT_THAT(AST->AST.preambleVersion(), InputsV0);
1163 EXPECT_THAT(AST->Inputs.Version, InputsV1.str());
1164 RunASTAction.notify();
1166 RunASTAction.wait();
1167 Ready.notify();
1170 TEST_F(TUSchedulerTests, OnlyPublishWhenPreambleIsBuilt) {
1171 struct PreamblePublishCounter : public ParsingCallbacks {
1172 PreamblePublishCounter(int &PreamblePublishCount)
1173 : PreamblePublishCount(PreamblePublishCount) {}
1174 void onPreamblePublished(PathRef File) override { ++PreamblePublishCount; }
1175 int &PreamblePublishCount;
1178 int PreamblePublishCount = 0;
1179 TUScheduler S(CDB, optsForTest(),
1180 std::make_unique<PreamblePublishCounter>(PreamblePublishCount));
1182 Path File = testPath("foo.cpp");
1183 S.update(File, getInputs(File, ""), WantDiagnostics::Auto);
1184 S.blockUntilIdle(timeoutSeconds(10));
1185 EXPECT_EQ(PreamblePublishCount, 1);
1186 // Same contents, no publish.
1187 S.update(File, getInputs(File, ""), WantDiagnostics::Auto);
1188 S.blockUntilIdle(timeoutSeconds(10));
1189 EXPECT_EQ(PreamblePublishCount, 1);
1190 // New contents, should publish.
1191 S.update(File, getInputs(File, "#define FOO"), WantDiagnostics::Auto);
1192 S.blockUntilIdle(timeoutSeconds(10));
1193 EXPECT_EQ(PreamblePublishCount, 2);
1196 // If a header file is missing from the CDB (or inferred using heuristics), and
1197 // it's included by another open file, then we parse it using that files flags.
1198 TEST_F(TUSchedulerTests, IncluderCache) {
1199 static std::string Main = testPath("main.cpp"), Main2 = testPath("main2.cpp"),
1200 Main3 = testPath("main3.cpp"),
1201 NoCmd = testPath("no_cmd.h"),
1202 Unreliable = testPath("unreliable.h"),
1203 OK = testPath("ok.h"),
1204 NotIncluded = testPath("not_included.h");
1205 struct NoHeadersCDB : public GlobalCompilationDatabase {
1206 llvm::Optional<tooling::CompileCommand>
1207 getCompileCommand(PathRef File) const override {
1208 if (File == NoCmd || File == NotIncluded || FailAll)
1209 return llvm::None;
1210 auto Basic = getFallbackCommand(File);
1211 Basic.Heuristic.clear();
1212 if (File == Unreliable) {
1213 Basic.Heuristic = "not reliable";
1214 } else if (File == Main) {
1215 Basic.CommandLine.push_back("-DMAIN");
1216 } else if (File == Main2) {
1217 Basic.CommandLine.push_back("-DMAIN2");
1218 } else if (File == Main3) {
1219 Basic.CommandLine.push_back("-DMAIN3");
1221 return Basic;
1224 std::atomic<bool> FailAll{false};
1225 } CDB;
1226 TUScheduler S(CDB, optsForTest());
1227 auto GetFlags = [&](PathRef Header) {
1228 S.update(Header, getInputs(Header, ";"), WantDiagnostics::Yes);
1229 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1230 tooling::CompileCommand Cmd;
1231 S.runWithPreamble("GetFlags", Header, TUScheduler::StaleOrAbsent,
1232 [&](llvm::Expected<InputsAndPreamble> Inputs) {
1233 ASSERT_FALSE(!Inputs) << Inputs.takeError();
1234 Cmd = std::move(Inputs->Command);
1236 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1237 return Cmd.CommandLine;
1240 for (const auto &Path : {NoCmd, Unreliable, OK, NotIncluded})
1241 FS.Files[Path] = ";";
1243 // Initially these files have normal commands from the CDB.
1244 EXPECT_THAT(GetFlags(Main), Contains("-DMAIN")) << "sanity check";
1245 EXPECT_THAT(GetFlags(NoCmd), Not(Contains("-DMAIN"))) << "no includes yet";
1247 // Now make Main include the others, and some should pick up its flags.
1248 const char *AllIncludes = R"cpp(
1249 #include "no_cmd.h"
1250 #include "ok.h"
1251 #include "unreliable.h"
1252 )cpp";
1253 S.update(Main, getInputs(Main, AllIncludes), WantDiagnostics::Yes);
1254 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1255 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN"))
1256 << "Included from main file, has no own command";
1257 EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN"))
1258 << "Included from main file, own command is heuristic";
1259 EXPECT_THAT(GetFlags(OK), Not(Contains("-DMAIN")))
1260 << "Included from main file, but own command is used";
1261 EXPECT_THAT(GetFlags(NotIncluded), Not(Contains("-DMAIN")))
1262 << "Not included from main file";
1264 // Open another file - it won't overwrite the associations with Main.
1265 std::string SomeIncludes = R"cpp(
1266 #include "no_cmd.h"
1267 #include "not_included.h"
1268 )cpp";
1269 S.update(Main2, getInputs(Main2, SomeIncludes), WantDiagnostics::Yes);
1270 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1271 EXPECT_THAT(GetFlags(NoCmd),
1272 AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2"))))
1273 << "mainfile association is stable";
1274 EXPECT_THAT(GetFlags(NotIncluded),
1275 AllOf(Contains("-DMAIN2"), Not(Contains("-DMAIN"))))
1276 << "new headers are associated with new mainfile";
1278 // Remove includes from main - this marks the associations as invalid but
1279 // doesn't actually remove them until another preamble claims them.
1280 S.update(Main, getInputs(Main, ""), WantDiagnostics::Yes);
1281 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1282 EXPECT_THAT(GetFlags(NoCmd),
1283 AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2"))))
1284 << "mainfile association not updated yet!";
1286 // Open yet another file - this time it claims the associations.
1287 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1288 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1289 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN3"))
1290 << "association invalidated and then claimed by main3";
1291 EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN"))
1292 << "association invalidated but not reclaimed";
1293 EXPECT_THAT(GetFlags(NotIncluded), Contains("-DMAIN2"))
1294 << "association still valid";
1296 // Delete the file from CDB, it should invalidate the associations.
1297 CDB.FailAll = true;
1298 EXPECT_THAT(GetFlags(NoCmd), Not(Contains("-DMAIN3")))
1299 << "association should've been invalidated.";
1300 // Also run update for Main3 to invalidate the preeamble to make sure next
1301 // update populates include cache associations.
1302 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1303 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1304 // Re-add the file and make sure nothing crashes.
1305 CDB.FailAll = false;
1306 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1307 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1308 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN3"))
1309 << "association invalidated and then claimed by main3";
1312 TEST_F(TUSchedulerTests, PreservesLastActiveFile) {
1313 for (bool Sync : {false, true}) {
1314 auto Opts = optsForTest();
1315 if (Sync)
1316 Opts.AsyncThreadsCount = 0;
1317 TUScheduler S(CDB, Opts);
1319 auto CheckNoFileActionsSeesLastActiveFile =
1320 [&](llvm::StringRef LastActiveFile) {
1321 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1322 std::atomic<int> Counter(0);
1323 // We only check for run and runQuick as runWithAST and
1324 // runWithPreamble is always bound to a file.
1325 S.run("run-UsesLastActiveFile", /*Path=*/"", [&] {
1326 ++Counter;
1327 EXPECT_EQ(LastActiveFile, boundPath());
1329 S.runQuick("runQuick-UsesLastActiveFile", /*Path=*/"", [&] {
1330 ++Counter;
1331 EXPECT_EQ(LastActiveFile, boundPath());
1333 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1334 EXPECT_EQ(2, Counter.load());
1337 // Check that we see no file initially
1338 CheckNoFileActionsSeesLastActiveFile("");
1340 // Now check that every action scheduled with a particular file changes the
1341 // LastActiveFile.
1342 auto Path = testPath("run.cc");
1343 S.run(Path, Path, [] {});
1344 CheckNoFileActionsSeesLastActiveFile(Path);
1346 Path = testPath("runQuick.cc");
1347 S.runQuick(Path, Path, [] {});
1348 CheckNoFileActionsSeesLastActiveFile(Path);
1350 Path = testPath("runWithAST.cc");
1351 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1352 S.runWithAST(Path, Path, [](llvm::Expected<InputsAndAST> Inp) {
1353 EXPECT_TRUE(bool(Inp));
1355 CheckNoFileActionsSeesLastActiveFile(Path);
1357 Path = testPath("runWithPreamble.cc");
1358 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1359 S.runWithPreamble(
1360 Path, Path, TUScheduler::Stale,
1361 [](llvm::Expected<InputsAndPreamble> Inp) { EXPECT_TRUE(bool(Inp)); });
1362 CheckNoFileActionsSeesLastActiveFile(Path);
1364 Path = testPath("update.cc");
1365 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1366 CheckNoFileActionsSeesLastActiveFile(Path);
1368 // An update with the same contents should not change LastActiveFile.
1369 auto LastActive = Path;
1370 Path = testPath("runWithAST.cc");
1371 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1372 CheckNoFileActionsSeesLastActiveFile(LastActive);
1376 TEST_F(TUSchedulerTests, PreambleThrottle) {
1377 const int NumRequests = 4;
1378 // Silly throttler that waits for 4 requests, and services them in reverse.
1379 // Doesn't honor cancellation but records it.
1380 struct : public PreambleThrottler {
1381 std::mutex Mu;
1382 std::vector<std::string> Acquires;
1383 std::vector<RequestID> Releases;
1384 llvm::DenseMap<RequestID, Callback> Callbacks;
1385 // If set, the notification is signalled after acquiring the specified ID.
1386 llvm::Optional<std::pair<RequestID, Notification *>> Notify;
1388 RequestID acquire(llvm::StringRef Filename, Callback CB) override {
1389 RequestID ID;
1390 Callback Invoke;
1392 std::lock_guard<std::mutex> Lock(Mu);
1393 ID = Acquires.size();
1394 Acquires.emplace_back(Filename);
1395 // If we're full, satisfy this request immediately.
1396 if (Acquires.size() == NumRequests) {
1397 Invoke = std::move(CB);
1398 } else {
1399 Callbacks.try_emplace(ID, std::move(CB));
1402 if (Invoke)
1403 Invoke();
1405 std::lock_guard<std::mutex> Lock(Mu);
1406 if (Notify && ID == Notify->first) {
1407 Notify->second->notify();
1408 Notify.reset();
1411 return ID;
1414 void release(RequestID ID) override {
1415 Callback SatisfyNext;
1417 std::lock_guard<std::mutex> Lock(Mu);
1418 Releases.push_back(ID);
1419 if (ID > 0 && Acquires.size() == NumRequests)
1420 SatisfyNext = std::move(Callbacks[ID - 1]);
1422 if (SatisfyNext)
1423 SatisfyNext();
1426 void reset() {
1427 Acquires.clear();
1428 Releases.clear();
1429 Callbacks.clear();
1431 } Throttler;
1433 struct CaptureBuiltFilenames : public ParsingCallbacks {
1434 std::vector<std::string> &Filenames;
1435 CaptureBuiltFilenames(std::vector<std::string> &Filenames)
1436 : Filenames(Filenames) {}
1437 void onPreambleAST(PathRef Path, llvm::StringRef Version,
1438 const CompilerInvocation &CI, ASTContext &Ctx,
1439 Preprocessor &PP, const CanonicalIncludes &) override {
1440 // Deliberately no synchronization.
1441 // The PreambleThrottler should serialize these calls, if not then tsan
1442 // will find a bug here.
1443 Filenames.emplace_back(Path);
1447 auto Opts = optsForTest();
1448 Opts.AsyncThreadsCount = 2 * NumRequests; // throttler is the bottleneck
1449 Opts.PreambleThrottler = &Throttler;
1451 std::vector<std::string> Filenames;
1454 std::vector<std::string> BuiltFilenames;
1455 TUScheduler S(CDB, Opts,
1456 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1457 for (unsigned I = 0; I < NumRequests; ++I) {
1458 auto Path = testPath(std::to_string(I) + ".cc");
1459 Filenames.push_back(Path);
1460 S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes);
1462 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
1464 // The throttler saw all files, and we built them.
1465 EXPECT_THAT(Throttler.Acquires,
1466 testing::UnorderedElementsAreArray(Filenames));
1467 EXPECT_THAT(BuiltFilenames,
1468 testing::UnorderedElementsAreArray(Filenames));
1469 // We built the files in reverse order that the throttler saw them.
1470 EXPECT_THAT(BuiltFilenames,
1471 testing::ElementsAreArray(Throttler.Acquires.rbegin(),
1472 Throttler.Acquires.rend()));
1473 // Resources for each file were correctly released.
1474 EXPECT_THAT(Throttler.Releases, ElementsAre(3, 2, 1, 0));
1477 Throttler.reset();
1479 // This time, enqueue 2 files, then cancel one of them while still waiting.
1480 // Finally shut down the server. Observe that everything gets cleaned up.
1481 Notification AfterAcquire2;
1482 Notification AfterFinishA;
1483 Throttler.Notify = {1, &AfterAcquire2};
1484 std::vector<std::string> BuiltFilenames;
1485 auto A = testPath("a.cc");
1486 auto B = testPath("b.cc");
1487 Filenames = {A, B};
1489 TUScheduler S(CDB, Opts,
1490 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1491 updateWithCallback(S, A, getInputs(A, ""), WantDiagnostics::Yes,
1492 [&] { AfterFinishA.notify(); });
1493 S.update(B, getInputs(B, ""), WantDiagnostics::Yes);
1494 AfterAcquire2.wait();
1496 // The throttler saw all files, but we built none.
1497 EXPECT_THAT(Throttler.Acquires,
1498 testing::UnorderedElementsAreArray(Filenames));
1499 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1500 // We haven't released anything yet, we're still waiting.
1501 EXPECT_THAT(Throttler.Releases, testing::IsEmpty());
1503 // FIXME: This is flaky, becaues the request can be destroyed after shutdown
1504 // if it hasn't been dequeued yet (stop() resets NextRequest).
1505 #if 0
1506 // Now close file A, which will shut down its AST worker.
1507 S.remove(A);
1508 // Request is destroyed after the queue shutdown, so release() has happened.
1509 AfterFinishA.wait();
1510 // We still didn't build anything.
1511 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1512 // But we've cancelled the request to build A (not sure which its ID is).
1513 EXPECT_THAT(Throttler.Releases, ElementsAre(AnyOf(1, 0)));
1514 #endif
1516 // Now shut down the TU Scheduler.
1518 // The throttler saw all files, but we built none.
1519 EXPECT_THAT(Throttler.Acquires,
1520 testing::UnorderedElementsAreArray(Filenames));
1521 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1522 // We gave up waiting and everything got released (in some order).
1523 EXPECT_THAT(Throttler.Releases, UnorderedElementsAre(1, 0));
1526 } // namespace
1527 } // namespace clangd
1528 } // namespace clang