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 "Context.h"
11 #include "Diagnostics.h"
12 #include "Matchers.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Preamble.h"
16 #include "TUScheduler.h"
17 #include "TestFS.h"
18 #include "Threading.h"
19 #include "clang/Basic/DiagnosticDriver.h"
20 #include "llvm/ADT/ArrayRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 #include "llvm/ADT/ScopeExit.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include <algorithm>
26 #include <utility>
27
28 namespace clang {
29 namespace clangd {
30 namespace {
31
32 using ::testing::AnyOf;
33 using ::testing::Each;
34 using ::testing::ElementsAre;
35 using ::testing::Eq;
36 using ::testing::Field;
37 using ::testing::IsEmpty;
38 using ::testing::Pointee;
39 using ::testing::UnorderedElementsAre;
40
41 MATCHER_P2(TUState, State, ActionName, "") {
42 return arg.Action.S == State && arg.Action.Name == ActionName;
43 }
44
45 class TUSchedulerTests : public ::testing::Test {
46 protected:
getInputs(PathRef File,std::string Contents)47 ParseInputs getInputs(PathRef File, std::string Contents) {
48 ParseInputs Inputs;
49 Inputs.CompileCommand = *CDB.getCompileCommand(File);
50 Inputs.FS = buildTestFS(Files, Timestamps);
51 Inputs.Contents = std::move(Contents);
52 Inputs.Opts = ParseOptions();
53 return Inputs;
54 }
55
updateWithCallback(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void ()> CB)56 void updateWithCallback(TUScheduler &S, PathRef File,
57 llvm::StringRef Contents, WantDiagnostics WD,
58 llvm::unique_function<void()> CB) {
59 WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
60 S.update(File, getInputs(File, Contents), WD);
61 }
62
63 static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
64 DiagsCallbackKey;
65
66 /// A diagnostics callback that should be passed to TUScheduler when it's used
67 /// in updateWithDiags.
captureDiags()68 static std::unique_ptr<ParsingCallbacks> captureDiags() {
69 class CaptureDiags : public ParsingCallbacks {
70 public:
71 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish) override {
72 reportDiagnostics(File, AST.getDiagnostics(), Publish);
73 }
74
75 void onFailedAST(PathRef File, std::vector<Diag> Diags,
76 PublishFn Publish) override {
77 reportDiagnostics(File, Diags, Publish);
78 }
79
80 private:
81 void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
82 PublishFn Publish) {
83 auto D = Context::current().get(DiagsCallbackKey);
84 if (!D)
85 return;
86 Publish([&]() {
87 const_cast<
88 llvm::unique_function<void(PathRef, std::vector<Diag>)> &> (*D)(
89 File, std::move(Diags));
90 });
91 }
92 };
93 return std::make_unique<CaptureDiags>();
94 }
95
96 /// Schedule an update and call \p CB with the diagnostics it produces, if
97 /// any. The TUScheduler should be created with captureDiags as a
98 /// DiagsCallback for this to work.
updateWithDiags(TUScheduler & S,PathRef File,ParseInputs Inputs,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)99 void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
100 WantDiagnostics WD,
101 llvm::unique_function<void(std::vector<Diag>)> CB) {
102 Path OrigFile = File.str();
103 WithContextValue Ctx(DiagsCallbackKey,
104 [OrigFile, CB = std::move(CB)](
105 PathRef File, std::vector<Diag> Diags) mutable {
106 assert(File == OrigFile);
107 CB(std::move(Diags));
108 });
109 S.update(File, std::move(Inputs), WD);
110 }
111
updateWithDiags(TUScheduler & S,PathRef File,llvm::StringRef Contents,WantDiagnostics WD,llvm::unique_function<void (std::vector<Diag>)> CB)112 void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents,
113 WantDiagnostics WD,
114 llvm::unique_function<void(std::vector<Diag>)> CB) {
115 return updateWithDiags(S, File, getInputs(File, Contents), WD,
116 std::move(CB));
117 }
118
119 llvm::StringMap<std::string> Files;
120 llvm::StringMap<time_t> Timestamps;
121 MockCompilationDatabase CDB;
122 };
123
124 Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
125 TUSchedulerTests::DiagsCallbackKey;
126
TEST_F(TUSchedulerTests,MissingFiles)127 TEST_F(TUSchedulerTests, MissingFiles) {
128 TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
129 /*StorePreamblesInMemory=*/true, /*ASTCallbacks=*/nullptr,
130 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
131 ASTRetentionPolicy());
132
133 auto Added = testPath("added.cpp");
134 Files[Added] = "x";
135
136 auto Missing = testPath("missing.cpp");
137 Files[Missing] = "";
138
139 EXPECT_EQ(S.getContents(Added), "");
140 S.update(Added, getInputs(Added, "x"), WantDiagnostics::No);
141 EXPECT_EQ(S.getContents(Added), "x");
142
143 // Assert each operation for missing file is an error (even if it's
144 // available in VFS).
145 S.runWithAST("", Missing,
146 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
147 S.runWithPreamble(
148 "", Missing, TUScheduler::Stale,
149 [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); });
150 // remove() shouldn't crash on missing files.
151 S.remove(Missing);
152
153 // Assert there aren't any errors for added file.
154 S.runWithAST("", Added,
155 [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); });
156 S.runWithPreamble("", Added, TUScheduler::Stale,
157 [&](Expected<InputsAndPreamble> Preamble) {
158 EXPECT_TRUE(bool(Preamble));
159 });
160 EXPECT_EQ(S.getContents(Added), "x");
161 S.remove(Added);
162 EXPECT_EQ(S.getContents(Added), "");
163
164 // Assert that all operations fail after removing the file.
165 S.runWithAST("", Added,
166 [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); });
167 S.runWithPreamble("", Added, TUScheduler::Stale,
168 [&](Expected<InputsAndPreamble> Preamble) {
169 ASSERT_FALSE(bool(Preamble));
170 llvm::consumeError(Preamble.takeError());
171 });
172 // remove() shouldn't crash on missing files.
173 S.remove(Added);
174 }
175
TEST_F(TUSchedulerTests,WantDiagnostics)176 TEST_F(TUSchedulerTests, WantDiagnostics) {
177 std::atomic<int> CallbackCount(0);
178 {
179 // To avoid a racy test, don't allow tasks to actually run on the worker
180 // thread until we've scheduled them all.
181 Notification Ready;
182 TUScheduler S(
183 CDB, getDefaultAsyncThreadsCount(),
184 /*StorePreamblesInMemory=*/true, captureDiags(),
185 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
186 ASTRetentionPolicy());
187 auto Path = testPath("foo.cpp");
188 updateWithDiags(S, Path, "", WantDiagnostics::Yes,
189 [&](std::vector<Diag>) { Ready.wait(); });
190 updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes,
191 [&](std::vector<Diag>) { ++CallbackCount; });
192 updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto,
193 [&](std::vector<Diag>) {
194 ADD_FAILURE()
195 << "auto should have been cancelled by auto";
196 });
197 updateWithDiags(S, Path, "request no diags", WantDiagnostics::No,
198 [&](std::vector<Diag>) {
199 ADD_FAILURE() << "no diags should not be called back";
200 });
201 updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto,
202 [&](std::vector<Diag>) { ++CallbackCount; });
203 Ready.notify();
204
205 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
206 }
207 EXPECT_EQ(2, CallbackCount);
208 }
209
TEST_F(TUSchedulerTests,Debounce)210 TEST_F(TUSchedulerTests, Debounce) {
211 std::atomic<int> CallbackCount(0);
212 {
213 TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
214 /*StorePreamblesInMemory=*/true, captureDiags(),
215 /*UpdateDebounce=*/std::chrono::seconds(1),
216 ASTRetentionPolicy());
217 // FIXME: we could probably use timeouts lower than 1 second here.
218 auto Path = testPath("foo.cpp");
219 updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto,
220 [&](std::vector<Diag>) {
221 ADD_FAILURE()
222 << "auto should have been debounced and canceled";
223 });
224 std::this_thread::sleep_for(std::chrono::milliseconds(200));
225 updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto,
226 [&](std::vector<Diag>) { ++CallbackCount; });
227 std::this_thread::sleep_for(std::chrono::seconds(2));
228 updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto,
229 [&](std::vector<Diag>) { ++CallbackCount; });
230
231 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
232 }
233 EXPECT_EQ(2, CallbackCount);
234 }
235
includes(const PreambleData * Preamble)236 static std::vector<std::string> includes(const PreambleData *Preamble) {
237 std::vector<std::string> Result;
238 if (Preamble)
239 for (const auto &Inclusion : Preamble->Includes.MainFileIncludes)
240 Result.push_back(Inclusion.Written);
241 return Result;
242 }
243
TEST_F(TUSchedulerTests,PreambleConsistency)244 TEST_F(TUSchedulerTests, PreambleConsistency) {
245 std::atomic<int> CallbackCount(0);
246 {
247 Notification InconsistentReadDone; // Must live longest.
248 TUScheduler S(
249 CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
250 /*ASTCallbacks=*/nullptr,
251 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
252 ASTRetentionPolicy());
253 auto Path = testPath("foo.cpp");
254 // Schedule two updates (A, B) and two preamble reads (stale, consistent).
255 // The stale read should see A, and the consistent read should see B.
256 // (We recognize the preambles by their included files).
257 updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() {
258 // This callback runs in between the two preamble updates.
259
260 // This blocks update B, preventing it from winning the race
261 // against the stale read.
262 // If the first read was instead consistent, this would deadlock.
263 InconsistentReadDone.wait();
264 // This delays update B, preventing it from winning a race
265 // against the consistent read. The consistent read sees B
266 // only because it waits for it.
267 // If the second read was stale, it would usually see A.
268 std::this_thread::sleep_for(std::chrono::milliseconds(100));
269 });
270 S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes);
271
272 S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
273 [&](Expected<InputsAndPreamble> Pre) {
274 ASSERT_TRUE(bool(Pre));
275 assert(bool(Pre));
276 EXPECT_THAT(includes(Pre->Preamble),
277 ElementsAre("<A>"));
278 InconsistentReadDone.notify();
279 ++CallbackCount;
280 });
281 S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
282 [&](Expected<InputsAndPreamble> Pre) {
283 ASSERT_TRUE(bool(Pre));
284 EXPECT_THAT(includes(Pre->Preamble),
285 ElementsAre("<B>"));
286 ++CallbackCount;
287 });
288 }
289 EXPECT_EQ(2, CallbackCount);
290 }
291
TEST_F(TUSchedulerTests,Cancellation)292 TEST_F(TUSchedulerTests, Cancellation) {
293 // We have the following update/read sequence
294 // U0
295 // U1(WantDiags=Yes) <-- cancelled
296 // R1 <-- cancelled
297 // U2(WantDiags=Yes) <-- cancelled
298 // R2A <-- cancelled
299 // R2B
300 // U3(WantDiags=Yes)
301 // R3 <-- cancelled
302 std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled;
303 {
304 Notification Proceed; // Ensure we schedule everything.
305 TUScheduler S(
306 CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true,
307 /*ASTCallbacks=*/captureDiags(),
308 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
309 ASTRetentionPolicy());
310 auto Path = testPath("foo.cpp");
311 // Helper to schedule a named update and return a function to cancel it.
312 auto Update = [&](std::string ID) -> Canceler {
313 auto T = cancelableTask();
314 WithContext C(std::move(T.first));
315 updateWithDiags(
316 S, Path, "//" + ID, WantDiagnostics::Yes,
317 [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
318 return std::move(T.second);
319 };
320 // Helper to schedule a named read and return a function to cancel it.
321 auto Read = [&](std::string ID) -> Canceler {
322 auto T = cancelableTask();
323 WithContext C(std::move(T.first));
324 S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) {
325 if (auto Err = E.takeError()) {
326 if (Err.isA<CancelledError>()) {
327 ReadsCanceled.push_back(ID);
328 consumeError(std::move(Err));
329 } else {
330 ADD_FAILURE() << "Non-cancelled error for " << ID << ": "
331 << llvm::toString(std::move(Err));
332 }
333 } else {
334 ReadsSeen.push_back(ID);
335 }
336 });
337 return std::move(T.second);
338 };
339
340 updateWithCallback(S, Path, "", WantDiagnostics::Yes,
341 [&]() { Proceed.wait(); });
342 // The second parens indicate cancellation, where present.
343 Update("U1")();
344 Read("R1")();
345 Update("U2")();
346 Read("R2A")();
347 Read("R2B");
348 Update("U3");
349 Read("R3")();
350 Proceed.notify();
351
352 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
353 }
354 EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3"))
355 << "U1 and all dependent reads were cancelled. "
356 "U2 has a dependent read R2A. "
357 "U3 was not cancelled.";
358 EXPECT_THAT(ReadsSeen, ElementsAre("R2B"))
359 << "All reads other than R2B were cancelled";
360 EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3"))
361 << "All reads other than R2B were cancelled";
362 }
363
TEST_F(TUSchedulerTests,ManyUpdates)364 TEST_F(TUSchedulerTests, ManyUpdates) {
365 const int FilesCount = 3;
366 const int UpdatesPerFile = 10;
367
368 std::mutex Mut;
369 int TotalASTReads = 0;
370 int TotalPreambleReads = 0;
371 int TotalUpdates = 0;
372
373 // Run TUScheduler and collect some stats.
374 {
375 TUScheduler S(CDB, getDefaultAsyncThreadsCount(),
376 /*StorePreamblesInMemory=*/true, captureDiags(),
377 /*UpdateDebounce=*/std::chrono::milliseconds(50),
378 ASTRetentionPolicy());
379
380 std::vector<std::string> Files;
381 for (int I = 0; I < FilesCount; ++I) {
382 std::string Name = "foo" + std::to_string(I) + ".cpp";
383 Files.push_back(testPath(Name));
384 this->Files[Files.back()] = "";
385 }
386
387 StringRef Contents1 = R"cpp(int a;)cpp";
388 StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
389 StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
390
391 StringRef AllContents[] = {Contents1, Contents2, Contents3};
392 const int AllContentsSize = 3;
393
394 // Scheduler may run tasks asynchronously, but should propagate the
395 // context. We stash a nonce in the context, and verify it in the task.
396 static Key<int> NonceKey;
397 int Nonce = 0;
398
399 for (int FileI = 0; FileI < FilesCount; ++FileI) {
400 for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
401 auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
402
403 auto File = Files[FileI];
404 auto Inputs = getInputs(File, Contents.str());
405 {
406 WithContextValue WithNonce(NonceKey, ++Nonce);
407 updateWithDiags(
408 S, File, Inputs, WantDiagnostics::Auto,
409 [File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) {
410 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
411
412 std::lock_guard<std::mutex> Lock(Mut);
413 ++TotalUpdates;
414 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
415 });
416 }
417 {
418 WithContextValue WithNonce(NonceKey, ++Nonce);
419 S.runWithAST(
420 "CheckAST", File,
421 [File, Inputs, Nonce, &Mut,
422 &TotalASTReads](Expected<InputsAndAST> AST) {
423 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
424
425 ASSERT_TRUE((bool)AST);
426 EXPECT_EQ(AST->Inputs.FS, Inputs.FS);
427 EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents);
428
429 std::lock_guard<std::mutex> Lock(Mut);
430 ++TotalASTReads;
431 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
432 });
433 }
434
435 {
436 WithContextValue WithNonce(NonceKey, ++Nonce);
437 S.runWithPreamble(
438 "CheckPreamble", File, TUScheduler::Stale,
439 [File, Inputs, Nonce, &Mut,
440 &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) {
441 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
442
443 ASSERT_TRUE((bool)Preamble);
444 EXPECT_EQ(Preamble->Contents, Inputs.Contents);
445
446 std::lock_guard<std::mutex> Lock(Mut);
447 ++TotalPreambleReads;
448 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
449 });
450 }
451 }
452 }
453 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
454 } // TUScheduler destructor waits for all operations to finish.
455
456 std::lock_guard<std::mutex> Lock(Mut);
457 EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile);
458 EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
459 EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
460 }
461
TEST_F(TUSchedulerTests,EvictedAST)462 TEST_F(TUSchedulerTests, EvictedAST) {
463 std::atomic<int> BuiltASTCounter(0);
464 ASTRetentionPolicy Policy;
465 Policy.MaxRetainedASTs = 2;
466 TUScheduler S(CDB,
467 /*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true,
468 /*ASTCallbacks=*/nullptr,
469 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
470 Policy);
471
472 llvm::StringLiteral SourceContents = R"cpp(
473 int* a;
474 double* b = a;
475 )cpp";
476 llvm::StringLiteral OtherSourceContents = R"cpp(
477 int* a;
478 double* b = a + 0;
479 )cpp";
480
481 auto Foo = testPath("foo.cpp");
482 auto Bar = testPath("bar.cpp");
483 auto Baz = testPath("baz.cpp");
484
485 // Build one file in advance. We will not access it later, so it will be the
486 // one that the cache will evict.
487 updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes,
488 [&BuiltASTCounter]() { ++BuiltASTCounter; });
489 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
490 ASSERT_EQ(BuiltASTCounter.load(), 1);
491
492 // Build two more files. Since we can retain only 2 ASTs, these should be
493 // the ones we see in the cache later.
494 updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes,
495 [&BuiltASTCounter]() { ++BuiltASTCounter; });
496 updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes,
497 [&BuiltASTCounter]() { ++BuiltASTCounter; });
498 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
499 ASSERT_EQ(BuiltASTCounter.load(), 3);
500
501 // Check only the last two ASTs are retained.
502 ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
503
504 // Access the old file again.
505 updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes,
506 [&BuiltASTCounter]() { ++BuiltASTCounter; });
507 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
508 ASSERT_EQ(BuiltASTCounter.load(), 4);
509
510 // Check the AST for foo.cpp is retained now and one of the others got
511 // evicted.
512 EXPECT_THAT(S.getFilesWithCachedAST(),
513 UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
514 }
515
TEST_F(TUSchedulerTests,EmptyPreamble)516 TEST_F(TUSchedulerTests, EmptyPreamble) {
517 TUScheduler S(CDB,
518 /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
519 /*ASTCallbacks=*/nullptr,
520 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
521 ASTRetentionPolicy());
522
523 auto Foo = testPath("foo.cpp");
524 auto Header = testPath("foo.h");
525
526 Files[Header] = "void foo()";
527 Timestamps[Header] = time_t(0);
528 auto WithPreamble = R"cpp(
529 #include "foo.h"
530 int main() {}
531 )cpp";
532 auto WithEmptyPreamble = R"cpp(int main() {})cpp";
533 S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto);
534 S.runWithPreamble(
535 "getNonEmptyPreamble", Foo, TUScheduler::Stale,
536 [&](Expected<InputsAndPreamble> Preamble) {
537 // We expect to get a non-empty preamble.
538 EXPECT_GT(
539 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
540 0u);
541 });
542 // Wait for the preamble is being built.
543 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
544
545 // Update the file which results in an empty preamble.
546 S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto);
547 // Wait for the preamble is being built.
548 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
549 S.runWithPreamble(
550 "getEmptyPreamble", Foo, TUScheduler::Stale,
551 [&](Expected<InputsAndPreamble> Preamble) {
552 // We expect to get an empty preamble.
553 EXPECT_EQ(
554 cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size,
555 0u);
556 });
557 }
558
TEST_F(TUSchedulerTests,RunWaitsForPreamble)559 TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
560 // Testing strategy: we update the file and schedule a few preamble reads at
561 // the same time. All reads should get the same non-null preamble.
562 TUScheduler S(CDB,
563 /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
564 /*ASTCallbacks=*/nullptr,
565 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
566 ASTRetentionPolicy());
567 auto Foo = testPath("foo.cpp");
568 auto NonEmptyPreamble = R"cpp(
569 #define FOO 1
570 #define BAR 2
571
572 int main() {}
573 )cpp";
574 constexpr int ReadsToSchedule = 10;
575 std::mutex PreamblesMut;
576 std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
577 S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto);
578 for (int I = 0; I < ReadsToSchedule; ++I) {
579 S.runWithPreamble(
580 "test", Foo, TUScheduler::Stale,
581 [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
582 std::lock_guard<std::mutex> Lock(PreamblesMut);
583 Preambles[I] = cantFail(std::move(IP)).Preamble;
584 });
585 }
586 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
587 // Check all actions got the same non-null preamble.
588 std::lock_guard<std::mutex> Lock(PreamblesMut);
589 ASSERT_NE(Preambles[0], nullptr);
590 ASSERT_THAT(Preambles, Each(Preambles[0]));
591 }
592
TEST_F(TUSchedulerTests,NoopOnEmptyChanges)593 TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
594 TUScheduler S(CDB,
595 /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
596 /*StorePreambleInMemory=*/true, captureDiags(),
597 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
598 ASTRetentionPolicy());
599
600 auto Source = testPath("foo.cpp");
601 auto Header = testPath("foo.h");
602
603 Files[Header] = "int a;";
604 Timestamps[Header] = time_t(0);
605
606 auto SourceContents = R"cpp(
607 #include "foo.h"
608 int b = a;
609 )cpp";
610
611 // Return value indicates if the updated callback was received.
612 auto DoUpdate = [&](std::string Contents) -> bool {
613 std::atomic<bool> Updated(false);
614 Updated = false;
615 updateWithDiags(S, Source, Contents, WantDiagnostics::Yes,
616 [&Updated](std::vector<Diag>) { Updated = true; });
617 bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10));
618 if (!UpdateFinished)
619 ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
620 return Updated;
621 };
622
623 // Test that subsequent updates with the same inputs do not cause rebuilds.
624 ASSERT_TRUE(DoUpdate(SourceContents));
625 ASSERT_FALSE(DoUpdate(SourceContents));
626
627 // Update to a header should cause a rebuild, though.
628 Timestamps[Header] = time_t(1);
629 ASSERT_TRUE(DoUpdate(SourceContents));
630 ASSERT_FALSE(DoUpdate(SourceContents));
631
632 // Update to the contents should cause a rebuild.
633 auto OtherSourceContents = R"cpp(
634 #include "foo.h"
635 int c = d;
636 )cpp";
637 ASSERT_TRUE(DoUpdate(OtherSourceContents));
638 ASSERT_FALSE(DoUpdate(OtherSourceContents));
639
640 // Update to the compile commands should also cause a rebuild.
641 CDB.ExtraClangFlags.push_back("-DSOMETHING");
642 ASSERT_TRUE(DoUpdate(OtherSourceContents));
643 ASSERT_FALSE(DoUpdate(OtherSourceContents));
644 }
645
TEST_F(TUSchedulerTests,NoChangeDiags)646 TEST_F(TUSchedulerTests, NoChangeDiags) {
647 TUScheduler S(CDB,
648 /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
649 /*StorePreambleInMemory=*/true, captureDiags(),
650 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
651 ASTRetentionPolicy());
652
653 auto FooCpp = testPath("foo.cpp");
654 auto Contents = "int a; int b;";
655
656 updateWithDiags(
657 S, FooCpp, Contents, WantDiagnostics::No,
658 [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
659 S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
660 // Make sure the AST was actually built.
661 cantFail(std::move(IA));
662 });
663 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
664
665 // Even though the inputs didn't change and AST can be reused, we need to
666 // report the diagnostics, as they were not reported previously.
667 std::atomic<bool> SeenDiags(false);
668 updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto,
669 [&](std::vector<Diag>) { SeenDiags = true; });
670 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
671 ASSERT_TRUE(SeenDiags);
672
673 // Subsequent request does not get any diagnostics callback because the same
674 // diags have previously been reported and the inputs didn't change.
675 updateWithDiags(
676 S, FooCpp, Contents, WantDiagnostics::Auto,
677 [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
678 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
679 }
680
TEST_F(TUSchedulerTests,Run)681 TEST_F(TUSchedulerTests, Run) {
682 TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
683 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/nullptr,
684 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
685 ASTRetentionPolicy());
686 std::atomic<int> Counter(0);
687 S.run("add 1", [&] { ++Counter; });
688 S.run("add 2", [&] { Counter += 2; });
689 ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
690 EXPECT_EQ(Counter.load(), 3);
691
692 Notification TaskRun;
693 Key<int> TestKey;
694 WithContextValue CtxWithKey(TestKey, 10);
695 S.run("props context", [&] {
696 EXPECT_EQ(Context::current().getExisting(TestKey), 10);
697 TaskRun.notify();
698 });
699 TaskRun.wait();
700 }
701
TEST_F(TUSchedulerTests,TUStatus)702 TEST_F(TUSchedulerTests, TUStatus) {
703 class CaptureTUStatus : public DiagnosticsConsumer {
704 public:
705 void onDiagnosticsReady(PathRef File,
706 std::vector<Diag> Diagnostics) override {}
707
708 void onFileUpdated(PathRef File, const TUStatus &Status) override {
709 std::lock_guard<std::mutex> Lock(Mutex);
710 AllStatus.push_back(Status);
711 }
712
713 std::vector<TUStatus> allStatus() {
714 std::lock_guard<std::mutex> Lock(Mutex);
715 return AllStatus;
716 }
717
718 private:
719 std::mutex Mutex;
720 std::vector<TUStatus> AllStatus;
721 } CaptureTUStatus;
722 MockFSProvider FS;
723 MockCompilationDatabase CDB;
724 ClangdServer Server(CDB, FS, CaptureTUStatus, ClangdServer::optsForTest());
725 Annotations Code("int m^ain () {}");
726
727 // We schedule the following tasks in the queue:
728 // [Update] [GoToDefinition]
729 Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes);
730 Server.locateSymbolAt(testPath("foo.cpp"), Code.point(),
731 [](Expected<std::vector<LocatedSymbol>> Result) {
732 ASSERT_TRUE((bool)Result);
733 });
734
735 ASSERT_TRUE(Server.blockUntilIdleForTest());
736
737 EXPECT_THAT(CaptureTUStatus.allStatus(),
738 ElementsAre(
739 // Statuses of "Update" action.
740 TUState(TUAction::RunningAction, "Update"),
741 TUState(TUAction::BuildingPreamble, "Update"),
742 TUState(TUAction::BuildingFile, "Update"),
743
744 // Statuses of "Definitions" action
745 TUState(TUAction::RunningAction, "Definitions"),
746 TUState(TUAction::Idle, /*No action*/ "")));
747 }
748
TEST_F(TUSchedulerTests,CommandLineErrors)749 TEST_F(TUSchedulerTests, CommandLineErrors) {
750 // We should see errors from command-line parsing inside the main file.
751 CDB.ExtraClangFlags = {"-fsome-unknown-flag"};
752
753 // (!) 'Ready' must live longer than TUScheduler.
754 Notification Ready;
755
756 TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
757 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
758 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
759 ASTRetentionPolicy());
760
761 std::vector<Diag> Diagnostics;
762 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
763 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
764 Diagnostics = std::move(D);
765 Ready.notify();
766 });
767 Ready.wait();
768
769 EXPECT_THAT(
770 Diagnostics,
771 ElementsAre(AllOf(
772 Field(&Diag::ID, Eq(diag::err_drv_unknown_argument)),
773 Field(&Diag::Name, Eq("drv_unknown_argument")),
774 Field(&Diag::Message, "unknown argument: '-fsome-unknown-flag'"))));
775 }
776
TEST_F(TUSchedulerTests,CommandLineWarnings)777 TEST_F(TUSchedulerTests, CommandLineWarnings) {
778 // We should not see warnings from command-line parsing.
779 CDB.ExtraClangFlags = {"-Wsome-unknown-warning"};
780
781 // (!) 'Ready' must live longer than TUScheduler.
782 Notification Ready;
783
784 TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
785 /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/captureDiags(),
786 /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
787 ASTRetentionPolicy());
788
789 std::vector<Diag> Diagnostics;
790 updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
791 WantDiagnostics::Yes, [&](std::vector<Diag> D) {
792 Diagnostics = std::move(D);
793 Ready.notify();
794 });
795 Ready.wait();
796
797 EXPECT_THAT(Diagnostics, IsEmpty());
798 }
799
800 } // namespace
801 } // namespace clangd
802 } // namespace clang
803