21#include "clang-include-cleaner/Record.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"
38#include <condition_variable>
52using ::testing::AllOf;
53using ::testing::AnyOf;
54using ::testing::Contains;
56using ::testing::ElementsAre;
58using ::testing::Field;
59using ::testing::IsEmpty;
62using ::testing::Pointee;
63using ::testing::SizeIs;
64using ::testing::UnorderedElementsAre;
66MATCHER_P2(TUState, PreambleActivity, ASTActivity,
"") {
67 if (arg.PreambleActivity != PreambleActivity) {
68 *result_listener <<
"preamblestate is "
69 <<
static_cast<uint8_t
>(arg.PreambleActivity);
72 if (arg.ASTActivity.K != ASTActivity) {
73 *result_listener <<
"aststate is " << arg.ASTActivity.K;
80static Key<std::string> BoundPath;
84llvm::StringRef boundPath() {
86 return V ? *V : llvm::StringRef(
"");
89TUScheduler::Options optsForTest() {
91 Opts.ContextProvider = bindPath;
95class TUSchedulerTests :
public ::testing::Test {
97 ParseInputs getInputs(
PathRef File, std::string Contents) {
99 Inputs.CompileCommand = *CDB.getCompileCommand(
File);
101 Inputs.Contents = std::move(Contents);
102 Inputs.Opts = ParseOptions();
106 void updateWithCallback(TUScheduler &S,
PathRef File,
108 llvm::unique_function<
void()> CB) {
109 updateWithCallback(S,
File, getInputs(
File, std::string(Contents)), WD,
113 void updateWithCallback(TUScheduler &S,
PathRef File, ParseInputs Inputs,
115 llvm::unique_function<
void()> CB) {
116 WithContextValue Ctx(llvm::make_scope_exit(std::move(CB)));
117 S.update(
File, Inputs, WD);
120 static Key<llvm::unique_function<void(
PathRef File, std::vector<Diag>)>>
125 static std::unique_ptr<ParsingCallbacks> captureDiags() {
126 class CaptureDiags :
public ParsingCallbacks {
128 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish)
override {
129 reportDiagnostics(File, AST.getDiagnostics(), Publish);
132 void onFailedAST(PathRef File, llvm::StringRef Version,
133 std::vector<Diag> Diags, PublishFn Publish)
override {
134 reportDiagnostics(File, Diags, Publish);
138 void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
144 const_cast<llvm::unique_function<
void(PathRef, std::vector<Diag>)
> &>(
149 return std::make_unique<CaptureDiags>();
155 void updateWithDiags(TUScheduler &S,
PathRef File, ParseInputs Inputs,
157 llvm::unique_function<
void(std::vector<Diag>)> CB) {
159 WithContextValue Ctx(DiagsCallbackKey,
160 [OrigFile, CB = std::move(CB)](
162 assert(
File == OrigFile);
163 CB(std::move(Diags));
165 S.update(
File, std::move(Inputs), WD);
168 void updateWithDiags(TUScheduler &S,
PathRef File, llvm::StringRef Contents,
170 llvm::unique_function<
void(std::vector<Diag>)> CB) {
171 return updateWithDiags(S,
File, getInputs(
File, std::string(Contents)), WD,
176 MockCompilationDatabase CDB;
180 TUSchedulerTests::DiagsCallbackKey;
182TEST_F(TUSchedulerTests, MissingFiles) {
183 TUScheduler S(CDB, optsForTest());
186 FS.Files[Added] =
"x";
204 S.runWithAST(
"", Added,
205 [&](Expected<InputsAndAST>
AST) { EXPECT_TRUE(
bool(
AST)); });
207 [&](Expected<InputsAndPreamble>
Preamble) {
213 S.runWithAST(
"", Added,
216 [&](Expected<InputsAndPreamble>
Preamble) {
218 llvm::consumeError(
Preamble.takeError());
225 std::atomic<int> CallbackCount(0);
230 TUScheduler S(CDB, optsForTest(), captureDiags());
237 [&](std::vector<Diag>) { Ready.wait(); });
239 [&](std::vector<Diag>) { ++CallbackCount; });
241 [&](std::vector<Diag>) {
243 <<
"auto should have been cancelled by auto";
246 [&](std::vector<Diag>) {
247 ADD_FAILURE() <<
"no diags should not be called back";
250 [&](std::vector<Diag>) { ++CallbackCount; });
255 EXPECT_EQ(2, CallbackCount);
258TEST_F(TUSchedulerTests, Debounce) {
259 auto Opts = optsForTest();
261 TUScheduler S(CDB, Opts, captureDiags());
265 [&](std::vector<Diag>) {
267 <<
"auto should have been debounced and canceled";
270 std::this_thread::sleep_for(std::chrono::milliseconds(50));
275 [&](std::vector<Diag>) { N.notify(); });
280 [&](std::vector<Diag>) {
282 <<
"auto should have been discarded (dead write)";
286TEST_F(TUSchedulerTests, Cancellation) {
296 std::vector<StringRef> DiagsSeen, ReadsSeen, ReadsCanceled;
298 Notification Proceed;
299 TUScheduler S(CDB, optsForTest(), captureDiags());
304 WithContext
C(std::move(
T.first));
307 [&,
ID](std::vector<Diag> Diags) { DiagsSeen.push_back(
ID); });
308 return std::move(
T.second);
313 WithContext
C(std::move(
T.first));
314 S.runWithAST(
ID,
Path, [&,
ID](llvm::Expected<InputsAndAST>
E) {
315 if (
auto Err =
E.takeError()) {
316 if (Err.isA<CancelledError>()) {
317 ReadsCanceled.push_back(ID);
318 consumeError(std::move(Err));
320 ADD_FAILURE() <<
"Non-cancelled error for " << ID <<
": "
321 << llvm::toString(std::move(Err));
324 ReadsSeen.push_back(
ID);
327 return std::move(
T.second);
331 [&]() { Proceed.wait(); });
344 EXPECT_THAT(DiagsSeen, ElementsAre(
"U2",
"U3"))
345 <<
"U1 and all dependent reads were cancelled. "
346 "U2 has a dependent read R2A. "
347 "U3 was not cancelled.";
348 EXPECT_THAT(ReadsSeen, ElementsAre(
"R2B"))
349 <<
"All reads other than R2B were cancelled";
350 EXPECT_THAT(ReadsCanceled, ElementsAre(
"R1",
"R2A",
"R3"))
351 <<
"All reads other than R2B were cancelled";
354TEST_F(TUSchedulerTests, InvalidationNoCrash) {
356 TUScheduler S(CDB, optsForTest(), captureDiags());
358 Notification StartedRunning;
359 Notification ScheduledChange;
365 "invalidatable-but-running",
Path,
366 [&](llvm::Expected<InputsAndAST>
AST) {
367 StartedRunning.notify();
368 ScheduledChange.wait();
369 ASSERT_TRUE(
bool(
AST));
372 StartedRunning.wait();
374 ScheduledChange.notify();
378TEST_F(TUSchedulerTests, Invalidation) {
380 TUScheduler S(CDB, optsForTest(), captureDiags());
381 std::atomic<int> Builds(0), Actions(0);
389 "invalidatable",
Path,
390 [&](llvm::Expected<InputsAndAST>
AST) {
392 EXPECT_FALSE(
bool(
AST));
393 llvm::Error
E =
AST.takeError();
394 EXPECT_TRUE(
E.isA<CancelledError>());
395 handleAllErrors(std::move(
E), [&](
const CancelledError &
E) {
401 "not-invalidatable",
Path,
402 [&](llvm::Expected<InputsAndAST>
AST) {
404 EXPECT_TRUE(
bool(
AST));
409 ADD_FAILURE() <<
"Shouldn't build, all dependents invalidated";
412 "invalidatable",
Path,
413 [&](llvm::Expected<InputsAndAST>
AST) {
415 EXPECT_FALSE(
bool(
AST));
416 llvm::Error
E =
AST.takeError();
417 EXPECT_TRUE(
E.isA<CancelledError>());
418 consumeError(std::move(
E));
422 [&](std::vector<Diag>) { ++Builds; });
424 "invalidatable",
Path,
425 [&](llvm::Expected<InputsAndAST>
AST) {
427 EXPECT_TRUE(
bool(
AST)) <<
"Shouldn't be invalidated, no update follows";
433 EXPECT_EQ(2, Builds.load()) <<
"Middle build should be skipped";
434 EXPECT_EQ(4, Actions.load()) <<
"All actions should run (some with error)";
442TEST_F(TUSchedulerTests, InvalidationUnchanged) {
444 TUScheduler S(CDB, optsForTest(), captureDiags());
445 std::atomic<int> Actions(0);
452 "invalidatable",
Path,
453 [&](llvm::Expected<InputsAndAST>
AST) {
455 EXPECT_TRUE(
bool(
AST))
456 <<
"Should not invalidate based on an update with same content: "
457 << llvm::toString(
AST.takeError());
461 ADD_FAILURE() <<
"Shouldn't build, identical to previous";
466 EXPECT_EQ(1, Actions.load()) <<
"All actions should run";
469TEST_F(TUSchedulerTests, ManyUpdates) {
470 const int FilesCount = 3;
471 const int UpdatesPerFile = 10;
474 int TotalASTReads = 0;
475 int TotalPreambleReads = 0;
476 int TotalUpdates = 0;
477 llvm::StringMap<int> LatestDiagVersion;
481 auto Opts = optsForTest();
483 TUScheduler S(CDB, Opts, captureDiags());
485 std::vector<std::string> Files;
486 for (
int I = 0; I < FilesCount; ++I) {
487 std::string
Name =
"foo" + std::to_string(I) +
".cpp";
489 this->FS.Files[Files.back()] =
"";
492 StringRef Contents1 = R
"cpp(int a;)cpp";
493 StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
494 StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
496 StringRef AllContents[] = {Contents1, Contents2, Contents3};
497 const int AllContentsSize = 3;
501 static Key<int> NonceKey;
504 for (
int FileI = 0; FileI < FilesCount; ++FileI) {
505 for (
int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
506 auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
508 auto File = Files[FileI];
509 auto Inputs = getInputs(
File, Contents.str());
511 WithContextValue WithNonce(NonceKey, ++Nonce);
512 Inputs.
Version = std::to_string(UpdateI);
515 [
File, Nonce, Version(Inputs.
Version), &Mut, &TotalUpdates,
516 &LatestDiagVersion](std::vector<Diag>) {
517 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
518 EXPECT_EQ(File, boundPath());
520 std::lock_guard<std::mutex> Lock(Mut);
522 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
524 auto It = LatestDiagVersion.try_emplace(File, -1);
525 const int PrevVersion = It.first->second;
527 ASSERT_TRUE(llvm::to_integer(Version, CurVersion, 10));
528 EXPECT_LT(PrevVersion, CurVersion);
529 It.first->getValue() = CurVersion;
533 WithContextValue WithNonce(NonceKey, ++Nonce);
536 [
File, Inputs, Nonce, &Mut,
537 &TotalASTReads](Expected<InputsAndAST>
AST) {
539 EXPECT_EQ(
File, boundPath());
541 ASSERT_TRUE((
bool)
AST);
543 EXPECT_EQ(
AST->Inputs.Version, Inputs.
Version);
544 EXPECT_EQ(
AST->AST.version(), Inputs.
Version);
546 std::lock_guard<std::mutex> Lock(Mut);
553 WithContextValue WithNonce(NonceKey, ++Nonce);
556 [
File, Inputs, Nonce, &Mut,
557 &TotalPreambleReads](Expected<InputsAndPreamble>
Preamble) {
559 EXPECT_EQ(
File, boundPath());
564 std::lock_guard<std::mutex> Lock(Mut);
565 ++TotalPreambleReads;
574 std::lock_guard<std::mutex> Lock(Mut);
577 EXPECT_GE(TotalUpdates, FilesCount);
578 EXPECT_LE(TotalUpdates, FilesCount * UpdatesPerFile);
580 for (
const auto &
Entry : LatestDiagVersion)
581 EXPECT_EQ(
Entry.second, UpdatesPerFile - 1);
582 EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
583 EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
586TEST_F(TUSchedulerTests, EvictedAST) {
587 std::atomic<int> BuiltASTCounter(0);
588 auto Opts = optsForTest();
589 Opts.AsyncThreadsCount = 1;
590 Opts.RetentionPolicy.MaxRetainedASTs = 2;
591 trace::TestTracer Tracer;
592 TUScheduler S(CDB, Opts);
594 llvm::StringLiteral SourceContents = R
"cpp(
598 llvm::StringLiteral OtherSourceContents = R"cpp(
607 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
608 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
612 [&BuiltASTCounter]() { ++BuiltASTCounter; });
614 ASSERT_EQ(BuiltASTCounter.load(), 1);
615 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
616 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(1));
621 [&BuiltASTCounter]() { ++BuiltASTCounter; });
623 [&BuiltASTCounter]() { ++BuiltASTCounter; });
625 ASSERT_EQ(BuiltASTCounter.load(), 3);
626 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
627 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(2));
630 ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(
Bar, Baz));
634 [&BuiltASTCounter]() { ++BuiltASTCounter; });
636 ASSERT_EQ(BuiltASTCounter.load(), 4);
637 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
638 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(1));
642 EXPECT_THAT(S.getFilesWithCachedAST(),
643 UnorderedElementsAre(
Foo, AnyOf(
Bar, Baz)));
649TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
650 auto Opts = optsForTest();
651 Opts.RetentionPolicy.MaxRetainedASTs = 1;
652 TUScheduler S(CDB, Opts);
655 auto FooInputs = getInputs(
Foo,
"int x=1;");
657 auto BarInputs = getInputs(
Bar,
"int x=2;");
664 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(
Bar));
671 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(
Bar));
673 ASSERT_EQ(S.fileStats().lookup(
Foo).ASTBuilds, 1u);
674 ASSERT_EQ(S.fileStats().lookup(
Bar).ASTBuilds, 1u);
677TEST_F(TUSchedulerTests, EmptyPreamble) {
678 TUScheduler S(CDB, optsForTest());
683 FS.Files[Header] =
"void foo()";
684 FS.Timestamps[Header] = time_t(0);
685 auto *WithPreamble = R
"cpp(
689 auto *WithEmptyPreamble = R
"cpp(int main() {})cpp";
693 [&](Expected<InputsAndPreamble>
Preamble) {
708 [&](Expected<InputsAndPreamble>
Preamble) {
716TEST_F(TUSchedulerTests, ASTSignalsSmokeTests) {
717 TUScheduler S(CDB, optsForTest());
721 FS.Files[Header] =
"namespace tar { int foo(); }";
722 const char *Contents = R
"cpp(
734 Notification TaskRun;
737 [&](Expected<InputsAndPreamble> IP) {
739 std::vector<std::pair<StringRef, int>> NS;
740 for (
const auto &P : IP->Signals->RelatedNamespaces)
741 NS.emplace_back(P.getKey(), P.getValue());
743 UnorderedElementsAre(Pair(
"ns::", 1), Pair(
"tar::", 1)));
745 std::vector<std::pair<SymbolID, int>> Sym;
746 for (
const auto &P : IP->Signals->ReferencedSymbols)
747 Sym.emplace_back(P.getFirst(), P.getSecond());
748 EXPECT_THAT(Sym, UnorderedElementsAre(Pair(
ns(
"tar").
ID, 1),
749 Pair(
ns(
"ns").
ID, 1),
750 Pair(
func(
"tar::foo").
ID, 1),
751 Pair(
func(
"ns::func").
ID, 1)));
757TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
760 TUScheduler S(CDB, optsForTest());
762 auto *NonEmptyPreamble = R
"cpp(
768 constexpr int ReadsToSchedule = 10;
769 std::mutex PreamblesMut;
770 std::vector<const void *> Preambles(ReadsToSchedule,
nullptr);
772 for (
int I = 0; I < ReadsToSchedule; ++I) {
775 [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
776 std::lock_guard<std::mutex> Lock(PreamblesMut);
777 Preambles[I] = cantFail(std::move(IP)).Preamble;
782 std::lock_guard<std::mutex> Lock(PreamblesMut);
783 ASSERT_NE(Preambles[0],
nullptr);
784 ASSERT_THAT(Preambles, Each(Preambles[0]));
787TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
788 TUScheduler S(CDB, optsForTest(), captureDiags());
793 FS.Files[Header] =
"int a;";
794 FS.Timestamps[Header] = time_t(0);
796 std::string SourceContents = R
"cpp(
802 auto DoUpdate = [&](std::string Contents) ->
bool {
803 std::atomic<bool> Updated(
false);
806 [&Updated](std::vector<Diag>) { Updated =
true; });
809 ADD_FAILURE() <<
"Updated has not finished in one second. Threading bug?";
814 ASSERT_TRUE(DoUpdate(SourceContents));
815 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
816 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
817 ASSERT_FALSE(DoUpdate(SourceContents));
818 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
819 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
822 FS.Timestamps[Header] = time_t(1);
823 ASSERT_TRUE(DoUpdate(SourceContents));
824 ASSERT_FALSE(DoUpdate(SourceContents));
825 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
826 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
829 SourceContents +=
"\nint c = b;";
830 ASSERT_TRUE(DoUpdate(SourceContents));
831 ASSERT_FALSE(DoUpdate(SourceContents));
832 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
833 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
836 CDB.ExtraClangFlags.push_back(
"-DSOMETHING");
837 ASSERT_TRUE(DoUpdate(SourceContents));
838 ASSERT_FALSE(DoUpdate(SourceContents));
841 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 5u);
842 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
848TEST_F(TUSchedulerTests, MissingHeader) {
849 CDB.ExtraClangFlags.push_back(
"-I" +
testPath(
"a"));
850 CDB.ExtraClangFlags.push_back(
"-I" +
testPath(
"b"));
852 FS.Files.try_emplace(
"a/__unused__");
853 FS.Files.try_emplace(
"b/__unused__");
854 TUScheduler S(CDB, optsForTest(), captureDiags());
860 auto *SourceContents = R
"cpp(
865 ParseInputs Inputs = getInputs(Source, SourceContents);
866 std::atomic<size_t> DiagCount(0);
872 [&DiagCount](std::vector<Diag> Diags) {
877 "use of undeclared identifier 'b'")));
881 FS.Files[HeaderB] =
"int b;";
882 FS.Timestamps[HeaderB] = time_t(1);
886 [&DiagCount](std::vector<Diag> Diags) {
888 EXPECT_THAT(Diags, IsEmpty());
894 FS.Files[HeaderA] =
"int a;";
895 FS.Timestamps[HeaderA] = time_t(1);
899 [&DiagCount](std::vector<Diag> Diags) {
902 <<
"Didn't expect new diagnostics when adding a/foo.h";
909 [&DiagCount](std::vector<Diag> Diags) {
915 EXPECT_EQ(DiagCount, 3U);
918TEST_F(TUSchedulerTests, NoChangeDiags) {
919 trace::TestTracer Tracer;
920 TUScheduler S(CDB, optsForTest(), captureDiags());
923 const auto *Contents =
"int a; int b;";
925 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"hit"), SizeIs(0));
926 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"miss"), SizeIs(0));
927 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
928 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
931 [](std::vector<Diag>) { ADD_FAILURE() <<
"Should not be called."; });
932 S.runWithAST(
"touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
934 cantFail(std::move(IA));
937 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"hit"), SizeIs(0));
938 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"miss"), SizeIs(1));
942 std::atomic<bool> SeenDiags(
false);
944 [&](std::vector<Diag>) { SeenDiags =
true; });
946 ASSERT_TRUE(SeenDiags);
947 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(1));
948 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
954 [&](std::vector<Diag>) { ADD_FAILURE() <<
"Should not be called."; });
958TEST_F(TUSchedulerTests, Run) {
959 for (
bool Sync : {
false,
true}) {
960 auto Opts = optsForTest();
962 Opts.AsyncThreadsCount = 0;
963 TUScheduler S(CDB, Opts);
965 S.run(
"add 1",
"", [&] { ++
Counter; });
966 S.run(
"add 2",
"", [&] {
Counter += 2; });
970 Notification TaskRun;
972 WithContextValue CtxWithKey(TestKey, 10);
973 const char *
Path =
"somepath";
974 S.run(
"props context",
Path, [&] {
976 EXPECT_EQ(
Path, boundPath());
983TEST_F(TUSchedulerTests, TUStatus) {
984 class CaptureTUStatus :
public ClangdServer::Callbacks {
986 void onFileUpdated(PathRef File,
const TUStatus &Status)
override {
987 auto ASTAction = Status.ASTActivity.K;
989 std::lock_guard<std::mutex> Lock(Mutex);
996 if (ASTActions.empty() || ASTActions.back() != ASTAction)
997 ASTActions.push_back(ASTAction);
998 if (PreambleActions.empty() || PreambleActions.back() != PreambleAction)
999 PreambleActions.push_back(PreambleAction);
1002 std::vector<PreambleAction> preambleStatuses() {
1003 std::lock_guard<std::mutex> Lock(Mutex);
1004 return PreambleActions;
1007 std::vector<ASTAction::Kind> astStatuses() {
1008 std::lock_guard<std::mutex> Lock(Mutex);
1014 std::vector<ASTAction::Kind> ASTActions;
1015 std::vector<PreambleAction> PreambleActions;
1018 MockCompilationDatabase CDB;
1020 Annotations
Code(
"int m^ain () {}");
1024 Server.addDocument(
testPath(
"foo.cpp"),
Code.code(),
"1",
1026 ASSERT_TRUE(Server.blockUntilIdleForTest());
1027 Server.locateSymbolAt(
testPath(
"foo.cpp"),
Code.point(),
1028 [](
Expected<std::vector<LocatedSymbol>> Result) {
1029 ASSERT_TRUE((bool)Result);
1031 ASSERT_TRUE(Server.blockUntilIdleForTest());
1033 EXPECT_THAT(CaptureTUStatus.preambleStatuses(),
1044 EXPECT_THAT(CaptureTUStatus.astStatuses(),
1060TEST_F(TUSchedulerTests, CommandLineErrors) {
1062 CDB.ExtraClangFlags = {
"-fsome-unknown-flag"};
1067 TUScheduler S(CDB, optsForTest(), captureDiags());
1069 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1084TEST_F(TUSchedulerTests, CommandLineWarnings) {
1086 CDB.ExtraClangFlags = {
"-Wsome-unknown-warning"};
1091 TUScheduler S(CDB, optsForTest(), captureDiags());
1093 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1103TEST(DebouncePolicy, Compute) {
1104 namespace c = std::chrono;
1105 DebouncePolicy::clock::duration History[] = {
1111 DebouncePolicy Policy;
1112 Policy.Min = c::seconds(3);
1113 Policy.Max = c::seconds(25);
1115 auto Compute = [&](llvm::ArrayRef<DebouncePolicy::clock::duration> History) {
1116 return c::duration_cast<c::duration<float, c::seconds::period>>(
1117 Policy.compute(History))
1120 EXPECT_NEAR(10, Compute(History), 0.01) <<
"(upper) median = 10";
1121 Policy.RebuildRatio = 1.5;
1122 EXPECT_NEAR(15, Compute(History), 0.01) <<
"median = 10, ratio = 1.5";
1123 Policy.RebuildRatio = 3;
1124 EXPECT_NEAR(25, Compute(History), 0.01) <<
"constrained by max";
1125 Policy.RebuildRatio = 0;
1126 EXPECT_NEAR(3, Compute(History), 0.01) <<
"constrained by min";
1127 EXPECT_NEAR(25, Compute({}), 0.01) <<
"no history -> max";
1130TEST_F(TUSchedulerTests, AsyncPreambleThread) {
1133 class BlockPreambleThread :
public ParsingCallbacks {
1135 BlockPreambleThread(llvm::StringRef BlockVersion, Notification &N)
1136 : BlockVersion(BlockVersion), N(N) {}
1138 PathRef
Path, llvm::StringRef Version, CapturedASTCtx,
1139 std::shared_ptr<const include_cleaner::PragmaIncludes>)
override {
1140 if (Version == BlockVersion)
1145 llvm::StringRef BlockVersion;
1149 static constexpr llvm::StringLiteral InputsV0 =
"v0";
1150 static constexpr llvm::StringLiteral InputsV1 =
"v1";
1152 TUScheduler S(CDB, optsForTest(),
1153 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1156 auto PI = getInputs(
File,
"");
1157 PI.Version = InputsV0.str();
1162 PI.Version = InputsV1.str();
1166 Notification RunASTAction;
1169 S.runWithAST(
"test",
File, [&](Expected<InputsAndAST>
AST) {
1170 ASSERT_TRUE(
bool(
AST));
1173 EXPECT_THAT(
AST->AST.preambleVersion(), InputsV0);
1174 EXPECT_THAT(
AST->Inputs.Version, InputsV1.str());
1175 RunASTAction.notify();
1177 RunASTAction.wait();
1181TEST_F(TUSchedulerTests, OnlyPublishWhenPreambleIsBuilt) {
1182 struct PreamblePublishCounter :
public ParsingCallbacks {
1183 PreamblePublishCounter(
int &PreamblePublishCount)
1184 : PreamblePublishCount(PreamblePublishCount) {}
1185 void onPreamblePublished(PathRef File)
override { ++PreamblePublishCount; }
1186 int &PreamblePublishCount;
1189 int PreamblePublishCount = 0;
1190 TUScheduler S(CDB, optsForTest(),
1191 std::make_unique<PreamblePublishCounter>(PreamblePublishCount));
1196 EXPECT_EQ(PreamblePublishCount, 1);
1200 EXPECT_EQ(PreamblePublishCount, 1);
1204 EXPECT_EQ(PreamblePublishCount, 2);
1207TEST_F(TUSchedulerTests, PublishWithStalePreamble) {
1210 class BlockPreambleThread :
public ParsingCallbacks {
1212 using DiagsCB = std::function<void(ParsedAST &)>;
1213 BlockPreambleThread(Notification &UnblockPreamble, DiagsCB CB)
1214 : UnblockPreamble(UnblockPreamble), CB(std::move(CB)) {}
1217 PathRef
Path, llvm::StringRef Version, CapturedASTCtx,
1218 std::shared_ptr<const include_cleaner::PragmaIncludes>)
override {
1221 <<
"Expected notification";
1225 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish)
override {
1229 void onFailedAST(PathRef File, llvm::StringRef Version,
1230 std::vector<Diag> Diags, PublishFn Publish)
override {
1231 ADD_FAILURE() <<
"Received failed ast for: " << File <<
" with version "
1236 bool BuildBefore =
false;
1237 Notification &UnblockPreamble;
1238 std::function<void(ParsedAST &)> CB;
1243 class DiagCollector {
1245 void onDiagnostics(ParsedAST &AST) {
1246 std::scoped_lock<std::mutex> Lock(DiagMu);
1247 DiagVersions.emplace_back(
1248 std::make_pair(AST.preambleVersion()->str(), AST.version().str()));
1249 DiagsReceived.notify_all();
1252 std::pair<std::string, std::string>
1253 waitForNewDiags(TUScheduler &S, PathRef File, ParseInputs PI) {
1254 std::unique_lock<std::mutex> Lock(DiagMu);
1257 S.update(File, std::move(PI), WantDiagnostics::Auto);
1258 size_t OldSize = DiagVersions.size();
1259 bool ReceivedDiags = DiagsReceived.wait_for(
1260 Lock, std::chrono::seconds(5),
1261 [
this, OldSize] {
return OldSize + 1 == DiagVersions.size(); });
1262 if (!ReceivedDiags) {
1263 ADD_FAILURE() <<
"Timed out waiting for diags";
1264 return {
"invalid",
"version"};
1266 return DiagVersions.back();
1269 std::vector<std::pair<std::string, std::string>> diagVersions() {
1270 std::scoped_lock<std::mutex> Lock(DiagMu);
1271 return DiagVersions;
1275 std::condition_variable DiagsReceived;
1277 std::vector<std::pair< std::string,
1282 DiagCollector Collector;
1283 Notification UnblockPreamble;
1284 auto DiagCallbacks = std::make_unique<BlockPreambleThread>(
1286 [&Collector](ParsedAST &
AST) { Collector.onDiagnostics(
AST); });
1287 TUScheduler S(CDB, optsForTest(), std::move(DiagCallbacks));
1289 auto BlockForDiags = [&](ParseInputs PI) {
1290 return Collector.waitForNewDiags(S,
File, std::move(PI));
1294 auto PI = getInputs(
File,
"");
1295 PI.Version = PI.Contents =
"1";
1296 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"1"));
1301 PI.Contents =
"#define BAR\n" + PI.Version;
1302 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"2"));
1305 PI.Contents =
"#define FOO\n" + PI.Version;
1306 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"3"));
1308 UnblockPreamble.notify();
1312 EXPECT_THAT(Collector.diagVersions().back(), Pair(PI.Version, PI.Version));
1316 PI.Contents =
"#define FOO\n" + PI.Version;
1319 EXPECT_THAT(Collector.diagVersions().back(), Pair(
"3",
"3"));
1324TEST_F(TUSchedulerTests, IncluderCache) {
1325 static std::string Main =
testPath(
"main.cpp"), Main2 =
testPath(
"main2.cpp"),
1328 Unreliable =
testPath(
"unreliable.h"),
1330 NotIncluded =
testPath(
"not_included.h");
1331 struct NoHeadersCDB :
public GlobalCompilationDatabase {
1332 std::optional<tooling::CompileCommand>
1333 getCompileCommand(PathRef File)
const override {
1334 if (File == NoCmd || File == NotIncluded || FailAll)
1335 return std::nullopt;
1336 auto Basic = getFallbackCommand(File);
1337 Basic.Heuristic.clear();
1338 if (File == Unreliable) {
1339 Basic.Heuristic =
"not reliable";
1340 }
else if (File == Main) {
1341 Basic.CommandLine.push_back(
"-DMAIN");
1342 }
else if (File == Main2) {
1343 Basic.CommandLine.push_back(
"-DMAIN2");
1344 }
else if (File == Main3) {
1345 Basic.CommandLine.push_back(
"-DMAIN3");
1350 std::atomic<bool> FailAll{
false};
1352 TUScheduler S(CDB, optsForTest());
1353 auto GetFlags = [&](
PathRef Header) {
1356 Notification CmdDone;
1357 tooling::CompileCommand Cmd;
1359 [&](llvm::Expected<InputsAndPreamble> Inputs) {
1360 ASSERT_FALSE(!Inputs) << Inputs.takeError();
1361 Cmd = std::move(Inputs->Command);
1366 return Cmd.CommandLine;
1369 for (
const auto &
Path : {NoCmd, Unreliable, OK, NotIncluded})
1370 FS.Files[
Path] =
";";
1373 EXPECT_THAT(GetFlags(Main), Contains(
"-DMAIN")) <<
"sanity check";
1374 EXPECT_THAT(GetFlags(NoCmd), Not(Contains(
"-DMAIN"))) <<
"no includes yet";
1377 const char *AllIncludes = R
"cpp(
1380 #include "unreliable.h"
1384 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN"))
1385 <<
"Included from main file, has no own command";
1386 EXPECT_THAT(GetFlags(Unreliable), Contains(
"-DMAIN"))
1387 <<
"Included from main file, own command is heuristic";
1388 EXPECT_THAT(GetFlags(OK), Not(Contains(
"-DMAIN")))
1389 <<
"Included from main file, but own command is used";
1390 EXPECT_THAT(GetFlags(NotIncluded), Not(Contains(
"-DMAIN")))
1391 <<
"Not included from main file";
1394 std::string SomeIncludes = R
"cpp(
1396 #include "not_included.h"
1400 EXPECT_THAT(GetFlags(NoCmd),
1401 AllOf(Contains("-DMAIN"), Not(Contains(
"-DMAIN2"))))
1402 <<
"mainfile association is stable";
1403 EXPECT_THAT(GetFlags(NotIncluded),
1404 AllOf(Contains(
"-DMAIN2"), Not(Contains(
"-DMAIN"))))
1405 <<
"new headers are associated with new mainfile";
1411 EXPECT_THAT(GetFlags(NoCmd),
1412 AllOf(Contains(
"-DMAIN"), Not(Contains(
"-DMAIN2"))))
1413 <<
"mainfile association not updated yet!";
1418 EXPECT_THAT(GetFlags(NoCmd), Contains(
"-DMAIN3"))
1419 <<
"association invalidated and then claimed by main3";
1420 EXPECT_THAT(GetFlags(Unreliable), Contains(
"-DMAIN"))
1421 <<
"association invalidated but not reclaimed";
1422 EXPECT_THAT(GetFlags(NotIncluded), Contains(
"-DMAIN2"))
1423 <<
"association still valid";
1427 EXPECT_THAT(GetFlags(NoCmd), Not(Contains(
"-DMAIN3")))
1428 <<
"association should've been invalidated.";
1434 CDB.FailAll =
false;
1437 EXPECT_THAT(GetFlags(NoCmd), Contains(
"-DMAIN3"))
1438 <<
"association invalidated and then claimed by main3";
1441TEST_F(TUSchedulerTests, PreservesLastActiveFile) {
1442 for (
bool Sync : {
false,
true}) {
1443 auto Opts = optsForTest();
1445 Opts.AsyncThreadsCount = 0;
1446 TUScheduler S(CDB, Opts);
1448 auto CheckNoFileActionsSeesLastActiveFile =
1449 [&](llvm::StringRef LastActiveFile) {
1454 S.run(
"run-UsesLastActiveFile",
"", [&] {
1456 EXPECT_EQ(LastActiveFile, boundPath());
1458 S.runQuick(
"runQuick-UsesLastActiveFile",
"", [&] {
1460 EXPECT_EQ(LastActiveFile, boundPath());
1467 CheckNoFileActionsSeesLastActiveFile(
"");
1473 CheckNoFileActionsSeesLastActiveFile(
Path);
1477 CheckNoFileActionsSeesLastActiveFile(
Path);
1481 S.runWithAST(
Path,
Path, [](llvm::Expected<InputsAndAST> Inp) {
1482 EXPECT_TRUE(
bool(Inp));
1484 CheckNoFileActionsSeesLastActiveFile(
Path);
1490 [](llvm::Expected<InputsAndPreamble> Inp) { EXPECT_TRUE(
bool(Inp)); });
1491 CheckNoFileActionsSeesLastActiveFile(
Path);
1495 CheckNoFileActionsSeesLastActiveFile(
Path);
1498 auto LastActive =
Path;
1501 CheckNoFileActionsSeesLastActiveFile(LastActive);
1505TEST_F(TUSchedulerTests, PreambleThrottle) {
1506 const int NumRequests = 4;
1509 struct :
public PreambleThrottler {
1511 std::vector<std::string> Acquires;
1512 std::vector<RequestID> Releases;
1513 llvm::DenseMap<RequestID, Callback> Callbacks;
1515 std::optional<std::pair<RequestID, Notification *>> Notify;
1521 std::lock_guard<std::mutex> Lock(Mu);
1522 ID = Acquires.size();
1525 if (Acquires.size() == NumRequests) {
1526 Invoke = std::move(CB);
1528 Callbacks.try_emplace(
ID, std::move(CB));
1534 std::lock_guard<std::mutex> Lock(Mu);
1535 if (Notify &&
ID == Notify->first) {
1536 Notify->second->notify();
1543 void release(RequestID
ID)
override {
1546 std::lock_guard<std::mutex> Lock(Mu);
1547 Releases.push_back(
ID);
1548 if (
ID > 0 && Acquires.size() == NumRequests)
1549 SatisfyNext = std::move(Callbacks[
ID - 1]);
1562 struct CaptureBuiltFilenames :
public ParsingCallbacks {
1563 std::vector<std::string> &Filenames;
1564 CaptureBuiltFilenames(std::vector<std::string> &Filenames)
1565 : Filenames(Filenames) {}
1567 PathRef
Path, llvm::StringRef Version, CapturedASTCtx,
1568 std::shared_ptr<const include_cleaner::PragmaIncludes> PI)
override {
1572 Filenames.emplace_back(
Path);
1576 auto Opts = optsForTest();
1577 Opts.AsyncThreadsCount = 2 * NumRequests;
1578 Opts.PreambleThrottler = &Throttler;
1580 std::vector<std::string> Filenames;
1583 std::vector<std::string> BuiltFilenames;
1584 TUScheduler S(CDB, Opts,
1585 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1586 for (
unsigned I = 0; I < NumRequests; ++I) {
1588 Filenames.push_back(
Path);
1594 EXPECT_THAT(Throttler.Acquires,
1595 testing::UnorderedElementsAreArray(Filenames));
1596 EXPECT_THAT(BuiltFilenames,
1597 testing::UnorderedElementsAreArray(Filenames));
1599 EXPECT_THAT(BuiltFilenames,
1600 testing::ElementsAreArray(Throttler.Acquires.rbegin(),
1601 Throttler.Acquires.rend()));
1603 EXPECT_THAT(Throttler.Releases, ElementsAre(3, 2, 1, 0));
1610 Notification AfterAcquire2;
1611 Notification AfterFinishA;
1612 Throttler.Notify = {1, &AfterAcquire2};
1613 std::vector<std::string> BuiltFilenames;
1618 TUScheduler S(CDB, Opts,
1619 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1621 [&] { AfterFinishA.notify(); });
1623 AfterAcquire2.wait();
1626 EXPECT_THAT(Throttler.Acquires,
1627 testing::UnorderedElementsAreArray(Filenames));
1628 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1630 EXPECT_THAT(Throttler.Releases, testing::IsEmpty());
1638 AfterFinishA.wait();
1640 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1642 EXPECT_THAT(Throttler.Releases, ElementsAre(AnyOf(1, 0)));
1648 EXPECT_THAT(Throttler.Acquires,
1649 testing::UnorderedElementsAreArray(Filenames));
1650 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1652 EXPECT_THAT(Throttler.Releases, UnorderedElementsAre(1, 0));
llvm::SmallString< 256U > Name
std::string Filename
Filename as a string.
std::vector< HeaderHandle > Path
std::vector< const char * > Expected
static Key< llvm::unique_function< void(PathRef File, std::vector< Diag >)> > DiagsCallbackKey
WantDiagnostics Diagnostics
std::optional< UpdateType > Update
#define EXPECT_ERROR(expectedValue)
static Options optsForTest()
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().
static const Context & current()
Returns the context for the current thread, creating it if needed.
const Type * get(const Key< Type > &Key) const
Get data stored for a typed Key.
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().
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
Symbol func(llvm::StringRef Name)
std::function< void()> Canceler
A canceller requests cancellation of a task, when called.
std::string Path
A typedef to represent a file path.
Symbol ns(llvm::StringRef Name)
MATCHER_P2(hasFlag, Flag, Path, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
TEST(BackgroundQueueTest, Priority)
std::pair< Context, Canceler > cancelableTask(int Reason)
Defines a new task whose cancellation may be requested.
WantDiagnostics
Determines whether diagnostics should be generated for a file snapshot.
@ Auto
Diagnostics must not be generated for this snapshot.
@ No
Diagnostics must be generated for this snapshot.
Deadline timeoutSeconds(std::optional< double > Seconds)
Makes a deadline from a timeout in seconds. std::nullopt means wait forever.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static DebouncePolicy fixed(clock::duration)
A policy that always returns the same duration, useful for tests.