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