Bump version to 19.1.0git
[llvm-project.git] / clang-tools-extra / clangd / unittests / TUSchedulerTests.cpp
blob43f38e39c89523f30260514090c8a93c16b5f76d
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 "Compiler.h"
12 #include "Config.h"
13 #include "Diagnostics.h"
14 #include "GlobalCompilationDatabase.h"
15 #include "Matchers.h"
16 #include "ParsedAST.h"
17 #include "Preamble.h"
18 #include "TUScheduler.h"
19 #include "TestFS.h"
20 #include "TestIndex.h"
21 #include "clang-include-cleaner/Record.h"
22 #include "support/Cancellation.h"
23 #include "support/Context.h"
24 #include "support/Path.h"
25 #include "support/TestTracer.h"
26 #include "support/Threading.h"
27 #include "clang/Basic/DiagnosticDriver.h"
28 #include "llvm/ADT/ArrayRef.h"
29 #include "llvm/ADT/FunctionExtras.h"
30 #include "llvm/ADT/ScopeExit.h"
31 #include "llvm/ADT/StringExtras.h"
32 #include "llvm/ADT/StringMap.h"
33 #include "llvm/ADT/StringRef.h"
34 #include "gmock/gmock.h"
35 #include "gtest/gtest.h"
36 #include <atomic>
37 #include <chrono>
38 #include <condition_variable>
39 #include <cstdint>
40 #include <functional>
41 #include <memory>
42 #include <mutex>
43 #include <optional>
44 #include <string>
45 #include <utility>
46 #include <vector>
48 namespace clang {
49 namespace clangd {
50 namespace {
52 using ::testing::AllOf;
53 using ::testing::AnyOf;
54 using ::testing::Contains;
55 using ::testing::Each;
56 using ::testing::ElementsAre;
57 using ::testing::Eq;
58 using ::testing::Field;
59 using ::testing::IsEmpty;
60 using ::testing::Not;
61 using ::testing::Pair;
62 using ::testing::Pointee;
63 using ::testing::SizeIs;
64 using ::testing::UnorderedElementsAre;
66 MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
67 if (arg.PreambleActivity != PreambleActivity) {
68 *result_listener << "preamblestate is "
69 << static_cast<uint8_t>(arg.PreambleActivity);
70 return false;
72 if (arg.ASTActivity.K != ASTActivity) {
73 *result_listener << "aststate is " << arg.ASTActivity.K;
74 return false;
76 return true;
79 // Simple ContextProvider to verify the provider is invoked & contexts are used.
80 static Key<std::string> BoundPath;
81 Context bindPath(PathRef F) {
82 return Context::current().derive(BoundPath, F.str());
84 llvm::StringRef boundPath() {
85 const std::string *V = Context::current().get(BoundPath);
86 return V ? *V : llvm::StringRef("");
89 TUScheduler::Options optsForTest() {
90 TUScheduler::Options Opts(ClangdServer::optsForTest());
91 Opts.ContextProvider = bindPath;
92 return Opts;
95 class TUSchedulerTests : public ::testing::Test {
96 protected:
97 ParseInputs getInputs(PathRef File, std::string Contents) {
98 ParseInputs Inputs;
99 Inputs.CompileCommand = *CDB.getCompileCommand(File);
100 Inputs.TFS = &FS;
101 Inputs.Contents = std::move(Contents);
102 Inputs.Opts = ParseOptions();
103 return Inputs;
106 void updateWithCallback(TUScheduler &S, PathRef File,
107 llvm::StringRef Contents, WantDiagnostics WD,
108 llvm::unique_function<void()> CB) {
109 updateWithCallback(S, File, getInputs(File, std::string(Contents)), WD,
110 std::move(CB));
113 void updateWithCallback(TUScheduler &S, PathRef File, ParseInputs Inputs,
114 WantDiagnostics WD,
115 llvm::unique_function<void()> CB) {
116 WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
117 S.update(File, Inputs, WD);
120 static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
121 DiagsCallbackKey;
123 /// A diagnostics callback that should be passed to TUScheduler when it's used
124 /// in updateWithDiags.
125 static std::unique_ptr<ParsingCallbacks> captureDiags() {
126 class CaptureDiags : public ParsingCallbacks {
127 public:
128 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
129 reportDiagnostics(File, AST.getDiagnostics(), Publish);
132 void onFailedAST(PathRef File, llvm::StringRef Version,
133 std::vector<Diag> Diags, PublishFn Publish) override {
134 reportDiagnostics(File, Diags, Publish);
137 private:
138 void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
139 PublishFn Publish) {
140 auto *D = Context::current().get(DiagsCallbackKey);
141 if (!D)
142 return;
143 Publish([&]() {
144 const_cast<llvm::unique_function<void(PathRef, std::vector<Diag>)> &>(
145 *D)(File, Diags);
149 return std::make_unique<CaptureDiags>();
152 /// Schedule an update and call \p CB with the diagnostics it produces, if
153 /// any. The TUScheduler should be created with captureDiags as a
154 /// DiagsCallback for this to work.
155 void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
156 WantDiagnostics WD,
157 llvm::unique_function<void(std::vector<Diag>)> CB) {
158 Path OrigFile = File.str();
159 WithContextValue Ctx(DiagsCallbackKey,
160 [OrigFile, CB = std::move(CB)](
161 PathRef File, std::vector<Diag> Diags) mutable {
162 assert(File == OrigFile);
163 CB(std::move(Diags));
165 S.update(File, std::move(Inputs), WD);
168 void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
169 WantDiagnostics WD,
170 llvm::unique_function<void(std::vector<Diag>)> CB) {
171 return updateWithDiags(S, File, getInputs(File, std::string(Contents)), WD,
172 std::move(CB));
175 MockFS FS;
176 MockCompilationDatabase CDB;
179 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
180 TUSchedulerTests::DiagsCallbackKey;
182 TEST_F(TUSchedulerTests, MissingFiles) {
183 TUScheduler S(CDB, optsForTest());
185 auto Added = testPath("added.cpp");
186 FS.Files[Added] = "x";
188 auto Missing = testPath("missing.cpp");
189 FS.Files[Missing] = "";
191 S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
193 // Assert each operation for missing file is an error (even if it's
194 // available in VFS).
195 S.runWithAST("", Missing,
196 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
197 S.runWithPreamble(
198 "", Missing, TUScheduler::Stale,
199 [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
200 // remove() shouldn't crash on missing files.
201 S.remove(Missing);
203 // Assert there aren't any errors for added file.
204 S.runWithAST("", Added,
205 [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
206 S.runWithPreamble("", Added, TUScheduler::Stale,
207 [&](Expected<InputsAndPreamble> Preamble) {
208 EXPECT_TRUE(bool(Preamble));
210 S.remove(Added);
212 // Assert that all operations fail after removing the file.
213 S.runWithAST("", Added,
214 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
215 S.runWithPreamble("", Added, TUScheduler::Stale,
216 [&](Expected<InputsAndPreamble> Preamble) {
217 ASSERT_FALSE(bool(Preamble));
218 llvm::consumeError(Preamble.takeError());
220 // remove() shouldn't crash on missing files.
221 S.remove(Added);
224 TEST_F(TUSchedulerTests, WantDiagnostics) {
225 std::atomic<int> CallbackCount(0);
227 // To avoid a racy test, don't allow tasks to actually run on the worker
228 // thread until we've scheduled them all.
229 Notification Ready;
230 TUScheduler S(CDB, optsForTest(), captureDiags());
231 auto Path = testPath("foo.cpp");
232 // Semicolons here and in the following inputs are significant. They ensure
233 // preamble stays the same across runs. Otherwise we might get multiple
234 // diagnostics callbacks, once with the stale preamble and another with the
235 // fresh preamble.
236 updateWithDiags(S, Path, ";", WantDiagnostics::Yes,
237 [&](std::vector<Diag>) { Ready.wait(); });
238 updateWithDiags(S, Path, ";request diags", WantDiagnostics::Yes,
239 [&](std::vector<Diag>) { ++CallbackCount; });
240 updateWithDiags(S, Path, ";auto (clobbered)", WantDiagnostics::Auto,
241 [&](std::vector<Diag>) {
242 ADD_FAILURE()
243 << "auto should have been cancelled by auto";
245 updateWithDiags(S, Path, ";request no diags", WantDiagnostics::No,
246 [&](std::vector<Diag>) {
247 ADD_FAILURE() << "no diags should not be called back";
249 updateWithDiags(S, Path, ";auto (produces)", WantDiagnostics::Auto,
250 [&](std::vector<Diag>) { ++CallbackCount; });
251 Ready.notify();
253 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
255 EXPECT_EQ(2, CallbackCount);
258 TEST_F(TUSchedulerTests, Debounce) {
259 auto Opts = optsForTest();
260 Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::milliseconds(500));
261 TUScheduler S(CDB, Opts, captureDiags());
262 auto Path = testPath("foo.cpp");
263 // Issue a write that's going to be debounced away.
264 updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
265 [&](std::vector<Diag>) {
266 ADD_FAILURE()
267 << "auto should have been debounced and canceled";
269 // Sleep a bit to verify that it's really debounce that's holding diagnostics.
270 std::this_thread::sleep_for(std::chrono::milliseconds(50));
272 // Issue another write, this time we'll wait for its diagnostics.
273 Notification N;
274 updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
275 [&](std::vector<Diag>) { N.notify(); });
276 EXPECT_TRUE(N.wait(timeoutSeconds(60)));
278 // Once we start shutting down the TUScheduler, this one becomes a dead write.
279 updateWithDiags(S, Path, "auto (discarded)", WantDiagnostics::Auto,
280 [&](std::vector<Diag>) {
281 ADD_FAILURE()
282 << "auto should have been discarded (dead write)";
286 TEST_F(TUSchedulerTests, Cancellation) {
287 // We have the following update/read sequence
288 // U0
289 // U1(WantDiags=Yes) <-- cancelled
290 // R1 <-- cancelled
291 // U2(WantDiags=Yes) <-- cancelled
292 // R2A <-- cancelled
293 // R2B
294 // U3(WantDiags=Yes)
295 // R3 <-- cancelled
296 std::vector<StringRef> DiagsSeen, ReadsSeen, ReadsCanceled;
298 Notification Proceed; // Ensure we schedule everything.
299 TUScheduler S(CDB, optsForTest(), captureDiags());
300 auto Path = testPath("foo.cpp");
301 // Helper to schedule a named update and return a function to cancel it.
302 auto Update = [&](StringRef ID) -> Canceler {
303 auto T = cancelableTask();
304 WithContext C(std::move(T.first));
305 updateWithDiags(
306 S, Path, ("//" + ID).str(), WantDiagnostics::Yes,
307 [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
308 return std::move(T.second);
310 // Helper to schedule a named read and return a function to cancel it.
311 auto Read = [&](StringRef ID) -> Canceler {
312 auto T = cancelableTask();
313 WithContext C(std::move(T.first));
314 S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
315 if (auto Err = E.takeError()) {
316 if (Err.isA<CancelledError>()) {
317 ReadsCanceled.push_back(ID);
318 consumeError(std::move(Err));
319 } else {
320 ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
321 << llvm::toString(std::move(Err));
323 } else {
324 ReadsSeen.push_back(ID);
327 return std::move(T.second);
330 updateWithCallback(S, Path, "", WantDiagnostics::Yes,
331 [&]() { Proceed.wait(); });
332 // The second parens indicate cancellation, where present.
333 Update("U1")();
334 Read("R1")();
335 Update("U2")();
336 Read("R2A")();
337 Read("R2B");
338 Update("U3");
339 Read("R3")();
340 Proceed.notify();
342 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
344 EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
345 << "U1 and all dependent reads were cancelled. "
346 "U2 has a dependent read R2A. "
347 "U3 was not cancelled.";
348 EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
349 << "All reads other than R2B were cancelled";
350 EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
351 << "All reads other than R2B were cancelled";
354 TEST_F(TUSchedulerTests, InvalidationNoCrash) {
355 auto Path = testPath("foo.cpp");
356 TUScheduler S(CDB, optsForTest(), captureDiags());
358 Notification StartedRunning;
359 Notification ScheduledChange;
360 // We expect invalidation logic to not crash by trying to invalidate a running
361 // request.
362 S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
363 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
364 S.runWithAST(
365 "invalidatable-but-running", Path,
366 [&](llvm::Expected<InputsAndAST> AST) {
367 StartedRunning.notify();
368 ScheduledChange.wait();
369 ASSERT_TRUE(bool(AST));
371 TUScheduler::InvalidateOnUpdate);
372 StartedRunning.wait();
373 S.update(Path, getInputs(Path, ""), WantDiagnostics::Auto);
374 ScheduledChange.notify();
375 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
378 TEST_F(TUSchedulerTests, Invalidation) {
379 auto Path = testPath("foo.cpp");
380 TUScheduler S(CDB, optsForTest(), captureDiags());
381 std::atomic<int> Builds(0), Actions(0);
383 Notification Start;
384 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
385 ++Builds;
386 Start.wait();
388 S.runWithAST(
389 "invalidatable", Path,
390 [&](llvm::Expected<InputsAndAST> AST) {
391 ++Actions;
392 EXPECT_FALSE(bool(AST));
393 llvm::Error E = AST.takeError();
394 EXPECT_TRUE(E.isA<CancelledError>());
395 handleAllErrors(std::move(E), [&](const CancelledError &E) {
396 EXPECT_EQ(E.Reason, static_cast<int>(ErrorCode::ContentModified));
399 TUScheduler::InvalidateOnUpdate);
400 S.runWithAST(
401 "not-invalidatable", Path,
402 [&](llvm::Expected<InputsAndAST> AST) {
403 ++Actions;
404 EXPECT_TRUE(bool(AST));
406 TUScheduler::NoInvalidation);
407 updateWithDiags(S, Path, "b", WantDiagnostics::Auto, [&](std::vector<Diag>) {
408 ++Builds;
409 ADD_FAILURE() << "Shouldn't build, all dependents invalidated";
411 S.runWithAST(
412 "invalidatable", Path,
413 [&](llvm::Expected<InputsAndAST> AST) {
414 ++Actions;
415 EXPECT_FALSE(bool(AST));
416 llvm::Error E = AST.takeError();
417 EXPECT_TRUE(E.isA<CancelledError>());
418 consumeError(std::move(E));
420 TUScheduler::InvalidateOnUpdate);
421 updateWithDiags(S, Path, "c", WantDiagnostics::Auto,
422 [&](std::vector<Diag>) { ++Builds; });
423 S.runWithAST(
424 "invalidatable", Path,
425 [&](llvm::Expected<InputsAndAST> AST) {
426 ++Actions;
427 EXPECT_TRUE(bool(AST)) << "Shouldn't be invalidated, no update follows";
429 TUScheduler::InvalidateOnUpdate);
430 Start.notify();
431 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
433 EXPECT_EQ(2, Builds.load()) << "Middle build should be skipped";
434 EXPECT_EQ(4, Actions.load()) << "All actions should run (some with error)";
437 // We don't invalidate requests for updates that don't change the file content.
438 // These are mostly "refresh this file" events synthesized inside clangd itself.
439 // (Usually the AST rebuild is elided after verifying that all inputs are
440 // unchanged, but invalidation decisions happen earlier and so independently).
441 // See https://github.com/clangd/clangd/issues/620
442 TEST_F(TUSchedulerTests, InvalidationUnchanged) {
443 auto Path = testPath("foo.cpp");
444 TUScheduler S(CDB, optsForTest(), captureDiags());
445 std::atomic<int> Actions(0);
447 Notification Start;
448 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
449 Start.wait();
451 S.runWithAST(
452 "invalidatable", Path,
453 [&](llvm::Expected<InputsAndAST> AST) {
454 ++Actions;
455 EXPECT_TRUE(bool(AST))
456 << "Should not invalidate based on an update with same content: "
457 << llvm::toString(AST.takeError());
459 TUScheduler::InvalidateOnUpdate);
460 updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
461 ADD_FAILURE() << "Shouldn't build, identical to previous";
463 Start.notify();
464 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
466 EXPECT_EQ(1, Actions.load()) << "All actions should run";
469 TEST_F(TUSchedulerTests, ManyUpdates) {
470 const int FilesCount = 3;
471 const int UpdatesPerFile = 10;
473 std::mutex Mut;
474 int TotalASTReads = 0;
475 int TotalPreambleReads = 0;
476 int TotalUpdates = 0;
477 llvm::StringMap<int> LatestDiagVersion;
479 // Run TUScheduler and collect some stats.
481 auto Opts = optsForTest();
482 Opts.UpdateDebounce = DebouncePolicy::fixed(std::chrono::milliseconds(50));
483 TUScheduler S(CDB, Opts, captureDiags());
485 std::vector<std::string> Files;
486 for (int I = 0; I < FilesCount; ++I) {
487 std::string Name = "foo" + std::to_string(I) + ".cpp";
488 Files.push_back(testPath(Name));
489 this->FS.Files[Files.back()] = "";
492 StringRef Contents1 = R"cpp(int a;)cpp";
493 StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
494 StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
496 StringRef AllContents[] = {Contents1, Contents2, Contents3};
497 const int AllContentsSize = 3;
499 // Scheduler may run tasks asynchronously, but should propagate the
500 // context. We stash a nonce in the context, and verify it in the task.
501 static Key<int> NonceKey;
502 int Nonce = 0;
504 for (int FileI = 0; FileI < FilesCount; ++FileI) {
505 for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
506 auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
508 auto File = Files[FileI];
509 auto Inputs = getInputs(File, Contents.str());
511 WithContextValue WithNonce(NonceKey, ++Nonce);
512 Inputs.Version = std::to_string(UpdateI);
513 updateWithDiags(
514 S, File, Inputs, WantDiagnostics::Auto,
515 [File, Nonce, Version(Inputs.Version), &Mut, &TotalUpdates,
516 &LatestDiagVersion](std::vector<Diag>) {
517 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
518 EXPECT_EQ(File, boundPath());
520 std::lock_guard<std::mutex> Lock(Mut);
521 ++TotalUpdates;
522 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
523 // Make sure Diags are for a newer version.
524 auto It = LatestDiagVersion.try_emplace(File, -1);
525 const int PrevVersion = It.first->second;
526 int CurVersion;
527 ASSERT_TRUE(llvm::to_integer(Version, CurVersion, 10));
528 EXPECT_LT(PrevVersion, CurVersion);
529 It.first->getValue() = CurVersion;
533 WithContextValue WithNonce(NonceKey, ++Nonce);
534 S.runWithAST(
535 "CheckAST", File,
536 [File, Inputs, Nonce, &Mut,
537 &TotalASTReads](Expected<InputsAndAST> AST) {
538 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
539 EXPECT_EQ(File, boundPath());
541 ASSERT_TRUE((bool)AST);
542 EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
543 EXPECT_EQ(AST->Inputs.Version, Inputs.Version);
544 EXPECT_EQ(AST->AST.version(), Inputs.Version);
546 std::lock_guard<std::mutex> Lock(Mut);
547 ++TotalASTReads;
548 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
553 WithContextValue WithNonce(NonceKey, ++Nonce);
554 S.runWithPreamble(
555 "CheckPreamble", File, TUScheduler::Stale,
556 [File, Inputs, Nonce, &Mut,
557 &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
558 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
559 EXPECT_EQ(File, boundPath());
561 ASSERT_TRUE((bool)Preamble);
562 EXPECT_EQ(Preamble->Contents, Inputs.Contents);
564 std::lock_guard<std::mutex> Lock(Mut);
565 ++TotalPreambleReads;
566 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
571 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
572 } // TUScheduler destructor waits for all operations to finish.
574 std::lock_guard<std::mutex> Lock(Mut);
575 // Updates might get coalesced in preamble thread and result in dropping
576 // diagnostics for intermediate snapshots.
577 EXPECT_GE(TotalUpdates, FilesCount);
578 EXPECT_LE(TotalUpdates, FilesCount * UpdatesPerFile);
579 // We should receive diags for last update.
580 for (const auto &Entry : LatestDiagVersion)
581 EXPECT_EQ(Entry.second, UpdatesPerFile - 1);
582 EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
583 EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
586 TEST_F(TUSchedulerTests, EvictedAST) {
587 std::atomic<int> BuiltASTCounter(0);
588 auto Opts = optsForTest();
589 Opts.AsyncThreadsCount = 1;
590 Opts.RetentionPolicy.MaxRetainedASTs = 2;
591 trace::TestTracer Tracer;
592 TUScheduler S(CDB, Opts);
594 llvm::StringLiteral SourceContents = R"cpp(
595 int* a;
596 double* b = a;
597 )cpp";
598 llvm::StringLiteral OtherSourceContents = R"cpp(
599 int* a;
600 double* b = a + 0;
601 )cpp";
603 auto Foo = testPath("foo.cpp");
604 auto Bar = testPath("bar.cpp");
605 auto Baz = testPath("baz.cpp");
607 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
608 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
609 // Build one file in advance. We will not access it later, so it will be the
610 // one that the cache will evict.
611 updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
612 [&BuiltASTCounter]() { ++BuiltASTCounter; });
613 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
614 ASSERT_EQ(BuiltASTCounter.load(), 1);
615 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
616 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
618 // Build two more files. Since we can retain only 2 ASTs, these should be
619 // the ones we see in the cache later.
620 updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
621 [&BuiltASTCounter]() { ++BuiltASTCounter; });
622 updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
623 [&BuiltASTCounter]() { ++BuiltASTCounter; });
624 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
625 ASSERT_EQ(BuiltASTCounter.load(), 3);
626 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
627 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(2));
629 // Check only the last two ASTs are retained.
630 ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
632 // Access the old file again.
633 updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
634 [&BuiltASTCounter]() { ++BuiltASTCounter; });
635 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
636 ASSERT_EQ(BuiltASTCounter.load(), 4);
637 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
638 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(1));
640 // Check the AST for foo.cpp is retained now and one of the others got
641 // evicted.
642 EXPECT_THAT(S.getFilesWithCachedAST(),
643 UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
646 // We send "empty" changes to TUScheduler when we think some external event
647 // *might* have invalidated current state (e.g. a header was edited).
648 // Verify that this doesn't evict our cache entries.
649 TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
650 auto Opts = optsForTest();
651 Opts.RetentionPolicy.MaxRetainedASTs = 1;
652 TUScheduler S(CDB, Opts);
654 auto Foo = testPath("foo.cpp");
655 auto FooInputs = getInputs(Foo, "int x=1;");
656 auto Bar = testPath("bar.cpp");
657 auto BarInputs = getInputs(Bar, "int x=2;");
659 // After opening Foo then Bar, AST cache contains Bar.
660 S.update(Foo, FooInputs, WantDiagnostics::Auto);
661 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
662 S.update(Bar, BarInputs, WantDiagnostics::Auto);
663 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
664 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
666 // Any number of no-op updates to Foo don't dislodge Bar from the cache.
667 S.update(Foo, FooInputs, WantDiagnostics::Auto);
668 S.update(Foo, FooInputs, WantDiagnostics::Auto);
669 S.update(Foo, FooInputs, WantDiagnostics::Auto);
670 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
671 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
672 // In fact each file has been built only once.
673 ASSERT_EQ(S.fileStats().lookup(Foo).ASTBuilds, 1u);
674 ASSERT_EQ(S.fileStats().lookup(Bar).ASTBuilds, 1u);
677 TEST_F(TUSchedulerTests, EmptyPreamble) {
678 TUScheduler S(CDB, optsForTest());
680 auto Foo = testPath("foo.cpp");
681 auto Header = testPath("foo.h");
683 FS.Files[Header] = "void foo()";
684 FS.Timestamps[Header] = time_t(0);
685 auto *WithPreamble = R"cpp(
686 #include "foo.h"
687 int main() {}
688 )cpp";
689 auto *WithEmptyPreamble = R"cpp(int main() {})cpp";
690 S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
691 S.runWithPreamble(
692 "getNonEmptyPreamble", Foo, TUScheduler::Stale,
693 [&](Expected<InputsAndPreamble> Preamble) {
694 // We expect to get a non-empty preamble.
695 EXPECT_GT(
696 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
697 0u);
699 // Wait while the preamble is being built.
700 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
702 // Update the file which results in an empty preamble.
703 S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
704 // Wait while the preamble is being built.
705 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
706 S.runWithPreamble(
707 "getEmptyPreamble", Foo, TUScheduler::Stale,
708 [&](Expected<InputsAndPreamble> Preamble) {
709 // We expect to get an empty preamble.
710 EXPECT_EQ(
711 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
712 0u);
716 TEST_F(TUSchedulerTests, ASTSignalsSmokeTests) {
717 TUScheduler S(CDB, optsForTest());
718 auto Foo = testPath("foo.cpp");
719 auto Header = testPath("foo.h");
721 FS.Files[Header] = "namespace tar { int foo(); }";
722 const char *Contents = R"cpp(
723 #include "foo.h"
724 namespace ns {
725 int func() {
726 return tar::foo());
728 } // namespace ns
729 )cpp";
730 // Update the file which results in an empty preamble.
731 S.update(Foo, getInputs(Foo, Contents), WantDiagnostics::Yes);
732 // Wait while the preamble is being built.
733 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
734 Notification TaskRun;
735 S.runWithPreamble(
736 "ASTSignals", Foo, TUScheduler::Stale,
737 [&](Expected<InputsAndPreamble> IP) {
738 ASSERT_FALSE(!IP);
739 std::vector<std::pair<StringRef, int>> NS;
740 for (const auto &P : IP->Signals->RelatedNamespaces)
741 NS.emplace_back(P.getKey(), P.getValue());
742 EXPECT_THAT(NS,
743 UnorderedElementsAre(Pair("ns::", 1), Pair("tar::", 1)));
745 std::vector<std::pair<SymbolID, int>> Sym;
746 for (const auto &P : IP->Signals->ReferencedSymbols)
747 Sym.emplace_back(P.getFirst(), P.getSecond());
748 EXPECT_THAT(Sym, UnorderedElementsAre(Pair(ns("tar").ID, 1),
749 Pair(ns("ns").ID, 1),
750 Pair(func("tar::foo").ID, 1),
751 Pair(func("ns::func").ID, 1)));
752 TaskRun.notify();
754 TaskRun.wait();
757 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
758 // Testing strategy: we update the file and schedule a few preamble reads at
759 // the same time. All reads should get the same non-null preamble.
760 TUScheduler S(CDB, optsForTest());
761 auto Foo = testPath("foo.cpp");
762 auto *NonEmptyPreamble = R"cpp(
763 #define FOO 1
764 #define BAR 2
766 int main() {}
767 )cpp";
768 constexpr int ReadsToSchedule = 10;
769 std::mutex PreamblesMut;
770 std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
771 S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
772 for (int I = 0; I < ReadsToSchedule; ++I) {
773 S.runWithPreamble(
774 "test", Foo, TUScheduler::Stale,
775 [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
776 std::lock_guard<std::mutex> Lock(PreamblesMut);
777 Preambles[I] = cantFail(std::move(IP)).Preamble;
780 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
781 // Check all actions got the same non-null preamble.
782 std::lock_guard<std::mutex> Lock(PreamblesMut);
783 ASSERT_NE(Preambles[0], nullptr);
784 ASSERT_THAT(Preambles, Each(Preambles[0]));
787 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
788 TUScheduler S(CDB, optsForTest(), captureDiags());
790 auto Source = testPath("foo.cpp");
791 auto Header = testPath("foo.h");
793 FS.Files[Header] = "int a;";
794 FS.Timestamps[Header] = time_t(0);
796 std::string SourceContents = R"cpp(
797 #include "foo.h"
798 int b = a;
799 )cpp";
801 // Return value indicates if the updated callback was received.
802 auto DoUpdate = [&](std::string Contents) -> bool {
803 std::atomic<bool> Updated(false);
804 Updated = false;
805 updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
806 [&Updated](std::vector<Diag>) { Updated = true; });
807 bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(60));
808 if (!UpdateFinished)
809 ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
810 return Updated;
813 // Test that subsequent updates with the same inputs do not cause rebuilds.
814 ASSERT_TRUE(DoUpdate(SourceContents));
815 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
816 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
817 ASSERT_FALSE(DoUpdate(SourceContents));
818 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
819 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
821 // Update to a header should cause a rebuild, though.
822 FS.Timestamps[Header] = time_t(1);
823 ASSERT_TRUE(DoUpdate(SourceContents));
824 ASSERT_FALSE(DoUpdate(SourceContents));
825 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
826 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
828 // Update to the contents should cause a rebuild.
829 SourceContents += "\nint c = b;";
830 ASSERT_TRUE(DoUpdate(SourceContents));
831 ASSERT_FALSE(DoUpdate(SourceContents));
832 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
833 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
835 // Update to the compile commands should also cause a rebuild.
836 CDB.ExtraClangFlags.push_back("-DSOMETHING");
837 ASSERT_TRUE(DoUpdate(SourceContents));
838 ASSERT_FALSE(DoUpdate(SourceContents));
839 // This causes 2 AST builds always. We first build an AST with the stale
840 // preamble, and build a second AST once the fresh preamble is ready.
841 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 5u);
842 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
845 // We rebuild if a completely missing header exists, but not if one is added
846 // on a higher-priority include path entry (for performance).
847 // (Previously we wouldn't automatically rebuild when files were added).
848 TEST_F(TUSchedulerTests, MissingHeader) {
849 CDB.ExtraClangFlags.push_back("-I" + testPath("a"));
850 CDB.ExtraClangFlags.push_back("-I" + testPath("b"));
851 // Force both directories to exist so they don't get pruned.
852 FS.Files.try_emplace("a/__unused__");
853 FS.Files.try_emplace("b/__unused__");
854 TUScheduler S(CDB, optsForTest(), captureDiags());
856 auto Source = testPath("foo.cpp");
857 auto HeaderA = testPath("a/foo.h");
858 auto HeaderB = testPath("b/foo.h");
860 auto *SourceContents = R"cpp(
861 #include "foo.h"
862 int c = b;
863 )cpp";
865 ParseInputs Inputs = getInputs(Source, SourceContents);
866 std::atomic<size_t> DiagCount(0);
868 // Update the source contents, which should trigger an initial build with
869 // the header file missing.
870 updateWithDiags(
871 S, Source, Inputs, WantDiagnostics::Yes,
872 [&DiagCount](std::vector<Diag> Diags) {
873 ++DiagCount;
874 EXPECT_THAT(Diags,
875 ElementsAre(Field(&Diag::Message, "'foo.h' file not found"),
876 Field(&Diag::Message,
877 "use of undeclared identifier 'b'")));
879 S.blockUntilIdle(timeoutSeconds(60));
881 FS.Files[HeaderB] = "int b;";
882 FS.Timestamps[HeaderB] = time_t(1);
884 // The addition of the missing header file triggers a rebuild, no errors.
885 updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
886 [&DiagCount](std::vector<Diag> Diags) {
887 ++DiagCount;
888 EXPECT_THAT(Diags, IsEmpty());
891 // Ensure previous assertions are done before we touch the FS again.
892 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
893 // Add the high-priority header file, which should reintroduce the error.
894 FS.Files[HeaderA] = "int a;";
895 FS.Timestamps[HeaderA] = time_t(1);
897 // This isn't detected: we don't stat a/foo.h to validate the preamble.
898 updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
899 [&DiagCount](std::vector<Diag> Diags) {
900 ++DiagCount;
901 ADD_FAILURE()
902 << "Didn't expect new diagnostics when adding a/foo.h";
905 // Forcing the reload should cause a rebuild.
906 Inputs.ForceRebuild = true;
907 updateWithDiags(
908 S, Source, Inputs, WantDiagnostics::Yes,
909 [&DiagCount](std::vector<Diag> Diags) {
910 ++DiagCount;
911 ElementsAre(Field(&Diag::Message, "use of undeclared identifier 'b'"));
914 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
915 EXPECT_EQ(DiagCount, 3U);
918 TEST_F(TUSchedulerTests, NoChangeDiags) {
919 trace::TestTracer Tracer;
920 TUScheduler S(CDB, optsForTest(), captureDiags());
922 auto FooCpp = testPath("foo.cpp");
923 const auto *Contents = "int a; int b;";
925 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
926 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(0));
927 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(0));
928 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
929 updateWithDiags(
930 S, FooCpp, Contents, WantDiagnostics::No,
931 [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
932 S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
933 // Make sure the AST was actually built.
934 cantFail(std::move(IA));
936 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
937 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "hit"), SizeIs(0));
938 EXPECT_THAT(Tracer.takeMetric("ast_access_read", "miss"), SizeIs(1));
940 // Even though the inputs didn't change and AST can be reused, we need to
941 // report the diagnostics, as they were not reported previously.
942 std::atomic<bool> SeenDiags(false);
943 updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
944 [&](std::vector<Diag>) { SeenDiags = true; });
945 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
946 ASSERT_TRUE(SeenDiags);
947 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "hit"), SizeIs(1));
948 EXPECT_THAT(Tracer.takeMetric("ast_access_diag", "miss"), SizeIs(0));
950 // Subsequent request does not get any diagnostics callback because the same
951 // diags have previously been reported and the inputs didn't change.
952 updateWithDiags(
953 S, FooCpp, Contents, WantDiagnostics::Auto,
954 [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
955 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
958 TEST_F(TUSchedulerTests, Run) {
959 for (bool Sync : {false, true}) {
960 auto Opts = optsForTest();
961 if (Sync)
962 Opts.AsyncThreadsCount = 0;
963 TUScheduler S(CDB, Opts);
964 std::atomic<int> Counter(0);
965 S.run("add 1", /*Path=*/"", [&] { ++Counter; });
966 S.run("add 2", /*Path=*/"", [&] { Counter += 2; });
967 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
968 EXPECT_EQ(Counter.load(), 3);
970 Notification TaskRun;
971 Key<int> TestKey;
972 WithContextValue CtxWithKey(TestKey, 10);
973 const char *Path = "somepath";
974 S.run("props context", Path, [&] {
975 EXPECT_EQ(Context::current().getExisting(TestKey), 10);
976 EXPECT_EQ(Path, boundPath());
977 TaskRun.notify();
979 TaskRun.wait();
983 TEST_F(TUSchedulerTests, TUStatus) {
984 class CaptureTUStatus : public ClangdServer::Callbacks {
985 public:
986 void onFileUpdated(PathRef File, const TUStatus &Status) override {
987 auto ASTAction = Status.ASTActivity.K;
988 auto PreambleAction = Status.PreambleActivity;
989 std::lock_guard<std::mutex> Lock(Mutex);
990 // Only push the action if it has changed. Since TUStatus can be published
991 // from either Preamble or AST thread and when one changes the other stays
992 // the same.
993 // Note that this can result in missing some updates when something other
994 // than action kind changes, e.g. when AST is built/reused the action kind
995 // stays as Building.
996 if (ASTActions.empty() || ASTActions.back() != ASTAction)
997 ASTActions.push_back(ASTAction);
998 if (PreambleActions.empty() || PreambleActions.back() != PreambleAction)
999 PreambleActions.push_back(PreambleAction);
1002 std::vector<PreambleAction> preambleStatuses() {
1003 std::lock_guard<std::mutex> Lock(Mutex);
1004 return PreambleActions;
1007 std::vector<ASTAction::Kind> astStatuses() {
1008 std::lock_guard<std::mutex> Lock(Mutex);
1009 return ASTActions;
1012 private:
1013 std::mutex Mutex;
1014 std::vector<ASTAction::Kind> ASTActions;
1015 std::vector<PreambleAction> PreambleActions;
1016 } CaptureTUStatus;
1017 MockFS FS;
1018 MockCompilationDatabase CDB;
1019 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &CaptureTUStatus);
1020 Annotations Code("int m^ain () {}");
1022 // We schedule the following tasks in the queue:
1023 // [Update] [GoToDefinition]
1024 Server.addDocument(testPath("foo.cpp"), Code.code(), "1",
1025 WantDiagnostics::Auto);
1026 ASSERT_TRUE(Server.blockUntilIdleForTest());
1027 Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
1028 [](Expected<std::vector<LocatedSymbol>> Result) {
1029 ASSERT_TRUE((bool)Result);
1031 ASSERT_TRUE(Server.blockUntilIdleForTest());
1033 EXPECT_THAT(CaptureTUStatus.preambleStatuses(),
1034 ElementsAre(
1035 // PreambleThread starts idle, as the update is first handled
1036 // by ASTWorker.
1037 PreambleAction::Idle,
1038 // Then it starts building first preamble and releases that to
1039 // ASTWorker.
1040 PreambleAction::Building,
1041 // Then goes idle and stays that way as we don't receive any
1042 // more update requests.
1043 PreambleAction::Idle));
1044 EXPECT_THAT(CaptureTUStatus.astStatuses(),
1045 ElementsAre(
1046 // Starts handling the update action and blocks until the
1047 // first preamble is built.
1048 ASTAction::RunningAction,
1049 // Afterwards it builds an AST for that preamble to publish
1050 // diagnostics.
1051 ASTAction::Building,
1052 // Then goes idle.
1053 ASTAction::Idle,
1054 // Afterwards we start executing go-to-def.
1055 ASTAction::RunningAction,
1056 // Then go idle.
1057 ASTAction::Idle));
1060 TEST_F(TUSchedulerTests, CommandLineErrors) {
1061 // We should see errors from command-line parsing inside the main file.
1062 CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
1064 // (!) 'Ready' must live longer than TUScheduler.
1065 Notification Ready;
1067 TUScheduler S(CDB, optsForTest(), captureDiags());
1068 std::vector<Diag> Diagnostics;
1069 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
1070 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
1071 Diagnostics = std::move(D);
1072 Ready.notify();
1074 Ready.wait();
1076 EXPECT_THAT(
1077 Diagnostics,
1078 ElementsAre(AllOf(
1079 Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
1080 Field(&Diag::Name, Eq("drv_unknown_argument")),
1081 Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
1084 TEST_F(TUSchedulerTests, CommandLineWarnings) {
1085 // We should not see warnings from command-line parsing.
1086 CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
1088 // (!) 'Ready' must live longer than TUScheduler.
1089 Notification Ready;
1091 TUScheduler S(CDB, optsForTest(), captureDiags());
1092 std::vector<Diag> Diagnostics;
1093 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
1094 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
1095 Diagnostics = std::move(D);
1096 Ready.notify();
1098 Ready.wait();
1100 EXPECT_THAT(Diagnostics, IsEmpty());
1103 TEST(DebouncePolicy, Compute) {
1104 namespace c = std::chrono;
1105 DebouncePolicy::clock::duration History[] = {
1106 c::seconds(0),
1107 c::seconds(5),
1108 c::seconds(10),
1109 c::seconds(20),
1111 DebouncePolicy Policy;
1112 Policy.Min = c::seconds(3);
1113 Policy.Max = c::seconds(25);
1114 // Call Policy.compute(History) and return seconds as a float.
1115 auto Compute = [&](llvm::ArrayRef<DebouncePolicy::clock::duration> History) {
1116 return c::duration_cast<c::duration<float, c::seconds::period>>(
1117 Policy.compute(History))
1118 .count();
1120 EXPECT_NEAR(10, Compute(History), 0.01) << "(upper) median = 10";
1121 Policy.RebuildRatio = 1.5;
1122 EXPECT_NEAR(15, Compute(History), 0.01) << "median = 10, ratio = 1.5";
1123 Policy.RebuildRatio = 3;
1124 EXPECT_NEAR(25, Compute(History), 0.01) << "constrained by max";
1125 Policy.RebuildRatio = 0;
1126 EXPECT_NEAR(3, Compute(History), 0.01) << "constrained by min";
1127 EXPECT_NEAR(25, Compute({}), 0.01) << "no history -> max";
1130 TEST_F(TUSchedulerTests, AsyncPreambleThread) {
1131 // Blocks preamble thread while building preamble with \p BlockVersion until
1132 // \p N is notified.
1133 class BlockPreambleThread : public ParsingCallbacks {
1134 public:
1135 BlockPreambleThread(llvm::StringRef BlockVersion, Notification &N)
1136 : BlockVersion(BlockVersion), N(N) {}
1137 void onPreambleAST(
1138 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1139 std::shared_ptr<const include_cleaner::PragmaIncludes>) override {
1140 if (Version == BlockVersion)
1141 N.wait();
1144 private:
1145 llvm::StringRef BlockVersion;
1146 Notification &N;
1149 static constexpr llvm::StringLiteral InputsV0 = "v0";
1150 static constexpr llvm::StringLiteral InputsV1 = "v1";
1151 Notification Ready;
1152 TUScheduler S(CDB, optsForTest(),
1153 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1155 Path File = testPath("foo.cpp");
1156 auto PI = getInputs(File, "");
1157 PI.Version = InputsV0.str();
1158 S.update(File, PI, WantDiagnostics::Auto);
1159 S.blockUntilIdle(timeoutSeconds(60));
1161 // Block preamble builds.
1162 PI.Version = InputsV1.str();
1163 // Issue second update which will block preamble thread.
1164 S.update(File, PI, WantDiagnostics::Auto);
1166 Notification RunASTAction;
1167 // Issue an AST read, which shouldn't be blocked and see latest version of the
1168 // file.
1169 S.runWithAST("test", File, [&](Expected<InputsAndAST> AST) {
1170 ASSERT_TRUE(bool(AST));
1171 // Make sure preamble is built with stale inputs, but AST was built using
1172 // new ones.
1173 EXPECT_THAT(AST->AST.preambleVersion(), InputsV0);
1174 EXPECT_THAT(AST->Inputs.Version, InputsV1.str());
1175 RunASTAction.notify();
1177 RunASTAction.wait();
1178 Ready.notify();
1181 TEST_F(TUSchedulerTests, OnlyPublishWhenPreambleIsBuilt) {
1182 struct PreamblePublishCounter : public ParsingCallbacks {
1183 PreamblePublishCounter(int &PreamblePublishCount)
1184 : PreamblePublishCount(PreamblePublishCount) {}
1185 void onPreamblePublished(PathRef File) override { ++PreamblePublishCount; }
1186 int &PreamblePublishCount;
1189 int PreamblePublishCount = 0;
1190 TUScheduler S(CDB, optsForTest(),
1191 std::make_unique<PreamblePublishCounter>(PreamblePublishCount));
1193 Path File = testPath("foo.cpp");
1194 S.update(File, getInputs(File, ""), WantDiagnostics::Auto);
1195 S.blockUntilIdle(timeoutSeconds(60));
1196 EXPECT_EQ(PreamblePublishCount, 1);
1197 // Same contents, no publish.
1198 S.update(File, getInputs(File, ""), WantDiagnostics::Auto);
1199 S.blockUntilIdle(timeoutSeconds(60));
1200 EXPECT_EQ(PreamblePublishCount, 1);
1201 // New contents, should publish.
1202 S.update(File, getInputs(File, "#define FOO"), WantDiagnostics::Auto);
1203 S.blockUntilIdle(timeoutSeconds(60));
1204 EXPECT_EQ(PreamblePublishCount, 2);
1207 TEST_F(TUSchedulerTests, PublishWithStalePreamble) {
1208 // Callbacks that blocks the preamble thread after the first preamble is
1209 // built and stores preamble/main-file versions for diagnostics released.
1210 class BlockPreambleThread : public ParsingCallbacks {
1211 public:
1212 using DiagsCB = std::function<void(ParsedAST &)>;
1213 BlockPreambleThread(Notification &UnblockPreamble, DiagsCB CB)
1214 : UnblockPreamble(UnblockPreamble), CB(std::move(CB)) {}
1216 void onPreambleAST(
1217 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1218 std::shared_ptr<const include_cleaner::PragmaIncludes>) override {
1219 if (BuildBefore)
1220 ASSERT_TRUE(UnblockPreamble.wait(timeoutSeconds(60)))
1221 << "Expected notification";
1222 BuildBefore = true;
1225 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
1226 CB(AST);
1229 void onFailedAST(PathRef File, llvm::StringRef Version,
1230 std::vector<Diag> Diags, PublishFn Publish) override {
1231 ADD_FAILURE() << "Received failed ast for: " << File << " with version "
1232 << Version << '\n';
1235 private:
1236 bool BuildBefore = false;
1237 Notification &UnblockPreamble;
1238 std::function<void(ParsedAST &)> CB;
1241 // Helpers for issuing blocking update requests on a TUScheduler, whose
1242 // onMainAST callback would call onDiagnostics.
1243 class DiagCollector {
1244 public:
1245 void onDiagnostics(ParsedAST &AST) {
1246 std::scoped_lock<std::mutex> Lock(DiagMu);
1247 DiagVersions.emplace_back(
1248 std::make_pair(AST.preambleVersion()->str(), AST.version().str()));
1249 DiagsReceived.notify_all();
1252 std::pair<std::string, std::string>
1253 waitForNewDiags(TUScheduler &S, PathRef File, ParseInputs PI) {
1254 std::unique_lock<std::mutex> Lock(DiagMu);
1255 // Perform the update under the lock to make sure it isn't handled until
1256 // we're waiting for it.
1257 S.update(File, std::move(PI), WantDiagnostics::Auto);
1258 size_t OldSize = DiagVersions.size();
1259 bool ReceivedDiags = DiagsReceived.wait_for(
1260 Lock, std::chrono::seconds(5),
1261 [this, OldSize] { return OldSize + 1 == DiagVersions.size(); });
1262 if (!ReceivedDiags) {
1263 ADD_FAILURE() << "Timed out waiting for diags";
1264 return {"invalid", "version"};
1266 return DiagVersions.back();
1269 std::vector<std::pair<std::string, std::string>> diagVersions() {
1270 std::scoped_lock<std::mutex> Lock(DiagMu);
1271 return DiagVersions;
1274 private:
1275 std::condition_variable DiagsReceived;
1276 std::mutex DiagMu;
1277 std::vector<std::pair</*PreambleVersion*/ std::string,
1278 /*MainFileVersion*/ std::string>>
1279 DiagVersions;
1282 DiagCollector Collector;
1283 Notification UnblockPreamble;
1284 auto DiagCallbacks = std::make_unique<BlockPreambleThread>(
1285 UnblockPreamble,
1286 [&Collector](ParsedAST &AST) { Collector.onDiagnostics(AST); });
1287 TUScheduler S(CDB, optsForTest(), std::move(DiagCallbacks));
1288 Path File = testPath("foo.cpp");
1289 auto BlockForDiags = [&](ParseInputs PI) {
1290 return Collector.waitForNewDiags(S, File, std::move(PI));
1293 // Build first preamble.
1294 auto PI = getInputs(File, "");
1295 PI.Version = PI.Contents = "1";
1296 ASSERT_THAT(BlockForDiags(PI), testing::Pair("1", "1"));
1298 // Now preamble thread is blocked, so rest of the requests sees only the
1299 // stale preamble.
1300 PI.Version = "2";
1301 PI.Contents = "#define BAR\n" + PI.Version;
1302 ASSERT_THAT(BlockForDiags(PI), testing::Pair("1", "2"));
1304 PI.Version = "3";
1305 PI.Contents = "#define FOO\n" + PI.Version;
1306 ASSERT_THAT(BlockForDiags(PI), testing::Pair("1", "3"));
1308 UnblockPreamble.notify();
1309 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1311 // Make sure that we have eventual consistency.
1312 EXPECT_THAT(Collector.diagVersions().back(), Pair(PI.Version, PI.Version));
1314 // Check that WantDiagnostics::No doesn't emit any diags.
1315 PI.Version = "4";
1316 PI.Contents = "#define FOO\n" + PI.Version;
1317 S.update(File, PI, WantDiagnostics::No);
1318 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1319 EXPECT_THAT(Collector.diagVersions().back(), Pair("3", "3"));
1322 // If a header file is missing from the CDB (or inferred using heuristics), and
1323 // it's included by another open file, then we parse it using that files flags.
1324 TEST_F(TUSchedulerTests, IncluderCache) {
1325 static std::string Main = testPath("main.cpp"), Main2 = testPath("main2.cpp"),
1326 Main3 = testPath("main3.cpp"),
1327 NoCmd = testPath("no_cmd.h"),
1328 Unreliable = testPath("unreliable.h"),
1329 OK = testPath("ok.h"),
1330 NotIncluded = testPath("not_included.h");
1331 struct NoHeadersCDB : public GlobalCompilationDatabase {
1332 std::optional<tooling::CompileCommand>
1333 getCompileCommand(PathRef File) const override {
1334 if (File == NoCmd || File == NotIncluded || FailAll)
1335 return std::nullopt;
1336 auto Basic = getFallbackCommand(File);
1337 Basic.Heuristic.clear();
1338 if (File == Unreliable) {
1339 Basic.Heuristic = "not reliable";
1340 } else if (File == Main) {
1341 Basic.CommandLine.push_back("-DMAIN");
1342 } else if (File == Main2) {
1343 Basic.CommandLine.push_back("-DMAIN2");
1344 } else if (File == Main3) {
1345 Basic.CommandLine.push_back("-DMAIN3");
1347 return Basic;
1350 std::atomic<bool> FailAll{false};
1351 } CDB;
1352 TUScheduler S(CDB, optsForTest());
1353 auto GetFlags = [&](PathRef Header) {
1354 S.update(Header, getInputs(Header, ";"), WantDiagnostics::Yes);
1355 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1356 Notification CmdDone;
1357 tooling::CompileCommand Cmd;
1358 S.runWithPreamble("GetFlags", Header, TUScheduler::StaleOrAbsent,
1359 [&](llvm::Expected<InputsAndPreamble> Inputs) {
1360 ASSERT_FALSE(!Inputs) << Inputs.takeError();
1361 Cmd = std::move(Inputs->Command);
1362 CmdDone.notify();
1364 CmdDone.wait();
1365 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1366 return Cmd.CommandLine;
1369 for (const auto &Path : {NoCmd, Unreliable, OK, NotIncluded})
1370 FS.Files[Path] = ";";
1372 // Initially these files have normal commands from the CDB.
1373 EXPECT_THAT(GetFlags(Main), Contains("-DMAIN")) << "sanity check";
1374 EXPECT_THAT(GetFlags(NoCmd), Not(Contains("-DMAIN"))) << "no includes yet";
1376 // Now make Main include the others, and some should pick up its flags.
1377 const char *AllIncludes = R"cpp(
1378 #include "no_cmd.h"
1379 #include "ok.h"
1380 #include "unreliable.h"
1381 )cpp";
1382 S.update(Main, getInputs(Main, AllIncludes), WantDiagnostics::Yes);
1383 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1384 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN"))
1385 << "Included from main file, has no own command";
1386 EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN"))
1387 << "Included from main file, own command is heuristic";
1388 EXPECT_THAT(GetFlags(OK), Not(Contains("-DMAIN")))
1389 << "Included from main file, but own command is used";
1390 EXPECT_THAT(GetFlags(NotIncluded), Not(Contains("-DMAIN")))
1391 << "Not included from main file";
1393 // Open another file - it won't overwrite the associations with Main.
1394 std::string SomeIncludes = R"cpp(
1395 #include "no_cmd.h"
1396 #include "not_included.h"
1397 )cpp";
1398 S.update(Main2, getInputs(Main2, SomeIncludes), WantDiagnostics::Yes);
1399 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1400 EXPECT_THAT(GetFlags(NoCmd),
1401 AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2"))))
1402 << "mainfile association is stable";
1403 EXPECT_THAT(GetFlags(NotIncluded),
1404 AllOf(Contains("-DMAIN2"), Not(Contains("-DMAIN"))))
1405 << "new headers are associated with new mainfile";
1407 // Remove includes from main - this marks the associations as invalid but
1408 // doesn't actually remove them until another preamble claims them.
1409 S.update(Main, getInputs(Main, ""), WantDiagnostics::Yes);
1410 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1411 EXPECT_THAT(GetFlags(NoCmd),
1412 AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2"))))
1413 << "mainfile association not updated yet!";
1415 // Open yet another file - this time it claims the associations.
1416 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1417 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1418 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN3"))
1419 << "association invalidated and then claimed by main3";
1420 EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN"))
1421 << "association invalidated but not reclaimed";
1422 EXPECT_THAT(GetFlags(NotIncluded), Contains("-DMAIN2"))
1423 << "association still valid";
1425 // Delete the file from CDB, it should invalidate the associations.
1426 CDB.FailAll = true;
1427 EXPECT_THAT(GetFlags(NoCmd), Not(Contains("-DMAIN3")))
1428 << "association should've been invalidated.";
1429 // Also run update for Main3 to invalidate the preeamble to make sure next
1430 // update populates include cache associations.
1431 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1432 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1433 // Re-add the file and make sure nothing crashes.
1434 CDB.FailAll = false;
1435 S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes);
1436 EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1437 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN3"))
1438 << "association invalidated and then claimed by main3";
1441 TEST_F(TUSchedulerTests, PreservesLastActiveFile) {
1442 for (bool Sync : {false, true}) {
1443 auto Opts = optsForTest();
1444 if (Sync)
1445 Opts.AsyncThreadsCount = 0;
1446 TUScheduler S(CDB, Opts);
1448 auto CheckNoFileActionsSeesLastActiveFile =
1449 [&](llvm::StringRef LastActiveFile) {
1450 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1451 std::atomic<int> Counter(0);
1452 // We only check for run and runQuick as runWithAST and
1453 // runWithPreamble is always bound to a file.
1454 S.run("run-UsesLastActiveFile", /*Path=*/"", [&] {
1455 ++Counter;
1456 EXPECT_EQ(LastActiveFile, boundPath());
1458 S.runQuick("runQuick-UsesLastActiveFile", /*Path=*/"", [&] {
1459 ++Counter;
1460 EXPECT_EQ(LastActiveFile, boundPath());
1462 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1463 EXPECT_EQ(2, Counter.load());
1466 // Check that we see no file initially
1467 CheckNoFileActionsSeesLastActiveFile("");
1469 // Now check that every action scheduled with a particular file changes the
1470 // LastActiveFile.
1471 auto Path = testPath("run.cc");
1472 S.run(Path, Path, [] {});
1473 CheckNoFileActionsSeesLastActiveFile(Path);
1475 Path = testPath("runQuick.cc");
1476 S.runQuick(Path, Path, [] {});
1477 CheckNoFileActionsSeesLastActiveFile(Path);
1479 Path = testPath("runWithAST.cc");
1480 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1481 S.runWithAST(Path, Path, [](llvm::Expected<InputsAndAST> Inp) {
1482 EXPECT_TRUE(bool(Inp));
1484 CheckNoFileActionsSeesLastActiveFile(Path);
1486 Path = testPath("runWithPreamble.cc");
1487 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1488 S.runWithPreamble(
1489 Path, Path, TUScheduler::Stale,
1490 [](llvm::Expected<InputsAndPreamble> Inp) { EXPECT_TRUE(bool(Inp)); });
1491 CheckNoFileActionsSeesLastActiveFile(Path);
1493 Path = testPath("update.cc");
1494 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1495 CheckNoFileActionsSeesLastActiveFile(Path);
1497 // An update with the same contents should not change LastActiveFile.
1498 auto LastActive = Path;
1499 Path = testPath("runWithAST.cc");
1500 S.update(Path, getInputs(Path, ""), WantDiagnostics::No);
1501 CheckNoFileActionsSeesLastActiveFile(LastActive);
1505 TEST_F(TUSchedulerTests, PreambleThrottle) {
1506 const int NumRequests = 4;
1507 // Silly throttler that waits for 4 requests, and services them in reverse.
1508 // Doesn't honor cancellation but records it.
1509 struct : public PreambleThrottler {
1510 std::mutex Mu;
1511 std::vector<std::string> Acquires;
1512 std::vector<RequestID> Releases;
1513 llvm::DenseMap<RequestID, Callback> Callbacks;
1514 // If set, the notification is signalled after acquiring the specified ID.
1515 std::optional<std::pair<RequestID, Notification *>> Notify;
1517 RequestID acquire(llvm::StringRef Filename, Callback CB) override {
1518 RequestID ID;
1519 Callback Invoke;
1521 std::lock_guard<std::mutex> Lock(Mu);
1522 ID = Acquires.size();
1523 Acquires.emplace_back(Filename);
1524 // If we're full, satisfy this request immediately.
1525 if (Acquires.size() == NumRequests) {
1526 Invoke = std::move(CB);
1527 } else {
1528 Callbacks.try_emplace(ID, std::move(CB));
1531 if (Invoke)
1532 Invoke();
1534 std::lock_guard<std::mutex> Lock(Mu);
1535 if (Notify && ID == Notify->first) {
1536 Notify->second->notify();
1537 Notify.reset();
1540 return ID;
1543 void release(RequestID ID) override {
1544 Callback SatisfyNext;
1546 std::lock_guard<std::mutex> Lock(Mu);
1547 Releases.push_back(ID);
1548 if (ID > 0 && Acquires.size() == NumRequests)
1549 SatisfyNext = std::move(Callbacks[ID - 1]);
1551 if (SatisfyNext)
1552 SatisfyNext();
1555 void reset() {
1556 Acquires.clear();
1557 Releases.clear();
1558 Callbacks.clear();
1560 } Throttler;
1562 struct CaptureBuiltFilenames : public ParsingCallbacks {
1563 std::vector<std::string> &Filenames;
1564 CaptureBuiltFilenames(std::vector<std::string> &Filenames)
1565 : Filenames(Filenames) {}
1566 void onPreambleAST(
1567 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1568 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) override {
1569 // Deliberately no synchronization.
1570 // The PreambleThrottler should serialize these calls, if not then tsan
1571 // will find a bug here.
1572 Filenames.emplace_back(Path);
1576 auto Opts = optsForTest();
1577 Opts.AsyncThreadsCount = 2 * NumRequests; // throttler is the bottleneck
1578 Opts.PreambleThrottler = &Throttler;
1580 std::vector<std::string> Filenames;
1583 std::vector<std::string> BuiltFilenames;
1584 TUScheduler S(CDB, Opts,
1585 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1586 for (unsigned I = 0; I < NumRequests; ++I) {
1587 auto Path = testPath(std::to_string(I) + ".cc");
1588 Filenames.push_back(Path);
1589 S.update(Path, getInputs(Path, ""), WantDiagnostics::Yes);
1591 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
1593 // The throttler saw all files, and we built them.
1594 EXPECT_THAT(Throttler.Acquires,
1595 testing::UnorderedElementsAreArray(Filenames));
1596 EXPECT_THAT(BuiltFilenames,
1597 testing::UnorderedElementsAreArray(Filenames));
1598 // We built the files in reverse order that the throttler saw them.
1599 EXPECT_THAT(BuiltFilenames,
1600 testing::ElementsAreArray(Throttler.Acquires.rbegin(),
1601 Throttler.Acquires.rend()));
1602 // Resources for each file were correctly released.
1603 EXPECT_THAT(Throttler.Releases, ElementsAre(3, 2, 1, 0));
1606 Throttler.reset();
1608 // This time, enqueue 2 files, then cancel one of them while still waiting.
1609 // Finally shut down the server. Observe that everything gets cleaned up.
1610 Notification AfterAcquire2;
1611 Notification AfterFinishA;
1612 Throttler.Notify = {1, &AfterAcquire2};
1613 std::vector<std::string> BuiltFilenames;
1614 auto A = testPath("a.cc");
1615 auto B = testPath("b.cc");
1616 Filenames = {A, B};
1618 TUScheduler S(CDB, Opts,
1619 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1620 updateWithCallback(S, A, getInputs(A, ""), WantDiagnostics::Yes,
1621 [&] { AfterFinishA.notify(); });
1622 S.update(B, getInputs(B, ""), WantDiagnostics::Yes);
1623 AfterAcquire2.wait();
1625 // The throttler saw all files, but we built none.
1626 EXPECT_THAT(Throttler.Acquires,
1627 testing::UnorderedElementsAreArray(Filenames));
1628 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1629 // We haven't released anything yet, we're still waiting.
1630 EXPECT_THAT(Throttler.Releases, testing::IsEmpty());
1632 // FIXME: This is flaky, because the request can be destroyed after shutdown
1633 // if it hasn't been dequeued yet (stop() resets NextRequest).
1634 #if 0
1635 // Now close file A, which will shut down its AST worker.
1636 S.remove(A);
1637 // Request is destroyed after the queue shutdown, so release() has happened.
1638 AfterFinishA.wait();
1639 // We still didn't build anything.
1640 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1641 // But we've cancelled the request to build A (not sure which its ID is).
1642 EXPECT_THAT(Throttler.Releases, ElementsAre(AnyOf(1, 0)));
1643 #endif
1645 // Now shut down the TU Scheduler.
1647 // The throttler saw all files, but we built none.
1648 EXPECT_THAT(Throttler.Acquires,
1649 testing::UnorderedElementsAreArray(Filenames));
1650 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1651 // We gave up waiting and everything got released (in some order).
1652 EXPECT_THAT(Throttler.Releases, UnorderedElementsAre(1, 0));
1655 } // namespace
1656 } // namespace clangd
1657 } // namespace clang