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;
84llvm::StringRef boundPath() {
86 return V ? *V : llvm::StringRef(
"");
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,
140 auto *
D = Context::current().get(DiagsCallbackKey);
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) {
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);
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();
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;
302 auto Update = [&](StringRef ID) ->
Canceler {
307 [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
308 return std::move(
T.second);
314 S.runWithAST(ID,
Path, [&, ID](llvm::Expected<InputsAndAST> E) {
315 if (
auto Err = E.takeError()) {
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) {
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) {
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();
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();
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) {
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();
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;
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());
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;
536 [
File, Inputs, Nonce, &Mut,
537 &TotalASTReads](Expected<InputsAndAST>
AST) {
539 EXPECT_EQ(
File, boundPath());
541 ASSERT_TRUE((
bool)
AST);
542 EXPECT_EQ(
AST->Inputs.Contents, Inputs.Contents);
543 EXPECT_EQ(
AST->Inputs.Version, Inputs.Version);
544 EXPECT_EQ(
AST->AST.version(), Inputs.Version);
546 std::lock_guard<std::mutex> Lock(Mut);
556 [
File, Inputs, Nonce, &Mut,
557 &TotalPreambleReads](Expected<InputsAndPreamble>
Preamble) {
559 EXPECT_EQ(
File, boundPath());
562 EXPECT_EQ(
Preamble->Contents, Inputs.Contents);
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;
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;
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) {
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) {
721 FS.Files[Header] =
"namespace tar { int foo(); }";
722 const char *Contents = R
"cpp(
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) {
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) {
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__");
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";
906 Inputs.ForceRebuild =
true;
909 [&DiagCount](std::vector<Diag> Diags) {
915 EXPECT_EQ(DiagCount, 3U);
918TEST_F(TUSchedulerTests, NoChangeDiags) {
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;
964 std::atomic<int> Counter(0);
965 S.run(
"add 1",
"", [&] { ++Counter; });
966 S.run(
"add 2",
"", [&] { Counter += 2; });
968 EXPECT_EQ(Counter.load(), 3);
973 const char *
Path =
"somepath";
974 S.run(
"props context",
Path, [&] {
976 EXPECT_EQ(
Path, boundPath());
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;
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());
1068 std::vector<Diag> Diagnostics;
1069 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1071 Diagnostics = std::move(D);
1084TEST_F(TUSchedulerTests, CommandLineWarnings) {
1086 CDB.ExtraClangFlags = {
"-Wsome-unknown-warning"};
1091 TUScheduler S(CDB, optsForTest(), captureDiags());
1092 std::vector<Diag> Diagnostics;
1093 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1095 Diagnostics = std::move(D);
1100 EXPECT_THAT(Diagnostics, IsEmpty());
1104 namespace c = std::chrono;
1105 DebouncePolicy::clock::duration History[] = {
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) {
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";
1153 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1156 auto PI = getInputs(
File,
"");
1157 PI.Version = InputsV0.str();
1162 PI.Version = InputsV1.str();
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) {
1183 PreamblePublishCounter(
int &PreamblePublishCount)
1184 : PreamblePublishCount(PreamblePublishCount) {}
1185 void onPreamblePublished(PathRef File)
override { ++PreamblePublishCount; }
1186 int &PreamblePublishCount;
1189 int PreamblePublishCount = 0;
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) {
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;
1284 auto DiagCallbacks = std::make_unique<BlockPreambleThread>(
1287 TUScheduler S(CDB, optsForTest(), std::move(DiagCallbacks));
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");
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};
1353 auto GetFlags = [&](
PathRef Header) {
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;
1448 auto CheckNoFileActionsSeesLastActiveFile =
1449 [&](llvm::StringRef LastActiveFile) {
1451 std::atomic<int> Counter(0);
1454 S.run(
"run-UsesLastActiveFile",
"", [&] {
1456 EXPECT_EQ(LastActiveFile, boundPath());
1458 S.runQuick(
"runQuick-UsesLastActiveFile",
"", [&] {
1460 EXPECT_EQ(LastActiveFile, boundPath());
1463 EXPECT_EQ(2, Counter.load());
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;
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;
1517 RequestID acquire(llvm::StringRef Filename,
Callback CB)
override {
1521 std::lock_guard<std::mutex> Lock(Mu);
1522 ID = Acquires.size();
1523 Acquires.emplace_back(Filename);
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]);
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;
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));
1612 Throttler.Notify = {1, &AfterAcquire2};
1613 std::vector<std::string> BuiltFilenames;
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));
#define EXPECT_ERROR(expectedValue)
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Conventional error when no result is returned due to cancellation.
Interface with hooks for users of ClangdServer to be notified of events.
Manages a collection of source files and derived data (ASTs, indexes), and provides language-aware fe...
static Options optsForTest()
A context is an immutable container for per-request data that must be propagated through layers that ...
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.
Provides compilation arguments used for parsing C and C++ files.
Values in a Context are indexed by typed keys.
A threadsafe flag that is initially clear.
Stores and provides access to parsed AST.
PreambleThrottler controls which preambles can build at any given time.
Handles running tasks for ClangdServer and managing the resources (e.g., preambles and ASTs) for open...
static std::optional< llvm::StringRef > getFileBeingProcessedInContext()
@ StaleOrAbsent
Besides accepting stale preamble, this also allow preamble to be absent (not ready or failed to build...
@ Stale
The preamble may be generated from an older version of the file.
@ NoInvalidation
The request will run unless explicitly cancelled.
@ InvalidateOnUpdate
The request will be implicitly cancelled by a subsequent update().
WithContextValue extends Context::current() with a single value.
WithContext replaces Context::current() with a provided scope.
A RAII Tracer that can be used by tests.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
Symbol func(llvm::StringRef Name)
Symbol ns(llvm::StringRef Name)
MATCHER_P2(hasFlag, Flag, Path, "")
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
std::string testPath(PathRef File, llvm::sys::path::Style Style)
TEST(BackgroundQueueTest, Priority)
std::pair< Context, Canceler > cancelableTask(int Reason)
Defines a new task whose cancellation may be requested.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
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.
std::string Path
A typedef to represent a file path.
std::function< void()> Canceler
A canceller requests cancellation of a task, when called.
Deadline timeoutSeconds(std::optional< double > Seconds)
Makes a deadline from a timeout in seconds. std::nullopt means wait forever.
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess P
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Clangd may wait after an update to see if another one comes along.
clock::duration Min
The minimum time that we always debounce for.
static DebouncePolicy fixed(clock::duration)
A policy that always returns the same duration, useful for tests.