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