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;
71llvm::StringRef boundPath() {
73 return V ? *V : llvm::StringRef(
"");
78 Opts.ContextProvider = bindPath;
82class TUSchedulerTests :
public ::testing::Test {
84 ParseInputs getInputs(
PathRef File, std::string Contents) {
86 Inputs.CompileCommand = *CDB.getCompileCommand(
File);
88 Inputs.Contents = std::move(Contents);
89 Inputs.Opts = ParseOptions();
93 void updateWithCallback(TUScheduler &S,
PathRef File,
95 llvm::unique_function<
void()> CB) {
96 updateWithCallback(S,
File, getInputs(
File, std::string(Contents)), WD,
100 void updateWithCallback(TUScheduler &S,
PathRef File, ParseInputs Inputs,
102 llvm::unique_function<
void()> CB) {
103 WithContextValue Ctx(llvm::scope_exit(std::move(CB)));
104 S.update(
File, Inputs, WD);
107 static Key<llvm::unique_function<void(
PathRef File, std::vector<Diag>)>>
112 static std::unique_ptr<ParsingCallbacks> captureDiags() {
113 class CaptureDiags :
public ParsingCallbacks {
115 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish)
override {
116 reportDiagnostics(File,
AST.getDiagnostics(), Publish);
119 void onFailedAST(PathRef File, llvm::StringRef Version,
120 std::vector<Diag> Diags, PublishFn Publish)
override {
121 reportDiagnostics(File, Diags, Publish);
125 void reportDiagnostics(PathRef File, llvm::ArrayRef<Diag> Diags,
127 auto *
D = Context::current().get(DiagsCallbackKey);
131 const_cast<llvm::unique_function<
void(
PathRef, std::vector<Diag>)
> &>(
136 return std::make_unique<CaptureDiags>();
142 void updateWithDiags(TUScheduler &S,
PathRef File, ParseInputs Inputs,
144 llvm::unique_function<
void(std::vector<Diag>)> CB) {
146 WithContextValue Ctx(DiagsCallbackKey,
147 [OrigFile, CB = std::move(CB)](
149 assert(
File == OrigFile);
150 CB(std::move(Diags));
152 S.update(
File, std::move(Inputs), WD);
155 void updateWithDiags(TUScheduler &S,
PathRef File, llvm::StringRef Contents,
157 llvm::unique_function<
void(std::vector<Diag>)> CB) {
158 return updateWithDiags(S,
File, getInputs(
File, std::string(Contents)), WD,
163 MockCompilationDatabase CDB;
167 TUSchedulerTests::DiagsCallbackKey;
169TEST_F(TUSchedulerTests, MissingFiles) {
173 FS.Files[Added] =
"x";
191 S.runWithAST(
"", Added,
192 [&](Expected<InputsAndAST>
AST) { EXPECT_TRUE(
bool(
AST)); });
194 [&](Expected<InputsAndPreamble>
Preamble) {
200 S.runWithAST(
"", Added,
203 [&](Expected<InputsAndPreamble>
Preamble) {
205 llvm::consumeError(
Preamble.takeError());
212 std::atomic<int> CallbackCount(0);
224 [&](std::vector<Diag>) { Ready.wait(); });
226 [&](std::vector<Diag>) { ++CallbackCount; });
228 [&](std::vector<Diag>) {
230 <<
"auto should have been cancelled by auto";
233 [&](std::vector<Diag>) {
234 ADD_FAILURE() <<
"no diags should not be called back";
237 [&](std::vector<Diag>) { ++CallbackCount; });
242 EXPECT_EQ(2, CallbackCount);
245TEST_F(TUSchedulerTests, Debounce) {
246 auto Opts = optsForTest();
252 [&](std::vector<Diag>) {
254 <<
"auto should have been debounced and canceled";
257 std::this_thread::sleep_for(std::chrono::milliseconds(50));
262 [&](std::vector<Diag>) { N.notify(); });
267 [&](std::vector<Diag>) {
269 <<
"auto should have been discarded (dead write)";
273TEST_F(TUSchedulerTests, Cancellation) {
283 std::vector<StringRef> DiagsSeen, ReadsSeen, ReadsCanceled;
289 auto Update = [&](StringRef ID) ->
Canceler {
294 [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); });
295 return std::move(
T.second);
301 S.runWithAST(ID,
Path, [&, ID](llvm::Expected<InputsAndAST> E) {
302 if (
auto Err = E.takeError()) {
304 ReadsCanceled.push_back(ID);
305 consumeError(std::move(Err));
307 ADD_FAILURE() <<
"Non-cancelled error for " << ID <<
": "
308 << llvm::toString(std::move(Err));
311 ReadsSeen.push_back(ID);
314 return std::move(
T.second);
318 [&]() { Proceed.wait(); });
331 EXPECT_THAT(DiagsSeen, ElementsAre(
"U2",
"U3"))
332 <<
"U1 and all dependent reads were cancelled. "
333 "U2 has a dependent read R2A. "
334 "U3 was not cancelled.";
335 EXPECT_THAT(ReadsSeen, ElementsAre(
"R2B"))
336 <<
"All reads other than R2B were cancelled";
337 EXPECT_THAT(ReadsCanceled, ElementsAre(
"R1",
"R2A",
"R3"))
338 <<
"All reads other than R2B were cancelled";
341TEST_F(TUSchedulerTests, InvalidationNoCrash) {
352 "invalidatable-but-running",
Path,
353 [&](llvm::Expected<InputsAndAST>
AST) {
354 StartedRunning.notify();
355 ScheduledChange.wait();
356 ASSERT_TRUE(
bool(
AST));
359 StartedRunning.wait();
361 ScheduledChange.notify();
365TEST_F(TUSchedulerTests, Invalidation) {
368 std::atomic<int> Builds(0), Actions(0);
376 "invalidatable",
Path,
377 [&](llvm::Expected<InputsAndAST>
AST) {
379 EXPECT_FALSE(
bool(
AST));
380 llvm::Error E =
AST.takeError();
388 "not-invalidatable",
Path,
389 [&](llvm::Expected<InputsAndAST>
AST) {
391 EXPECT_TRUE(
bool(
AST));
396 ADD_FAILURE() <<
"Shouldn't build, all dependents invalidated";
399 "invalidatable",
Path,
400 [&](llvm::Expected<InputsAndAST>
AST) {
402 EXPECT_FALSE(
bool(
AST));
403 llvm::Error E =
AST.takeError();
405 consumeError(std::move(E));
409 [&](std::vector<Diag>) { ++Builds; });
411 "invalidatable",
Path,
412 [&](llvm::Expected<InputsAndAST>
AST) {
414 EXPECT_TRUE(
bool(
AST)) <<
"Shouldn't be invalidated, no update follows";
420 EXPECT_EQ(2, Builds.load()) <<
"Middle build should be skipped";
421 EXPECT_EQ(4, Actions.load()) <<
"All actions should run (some with error)";
429TEST_F(TUSchedulerTests, InvalidationUnchanged) {
432 std::atomic<int> Actions(0);
439 "invalidatable",
Path,
440 [&](llvm::Expected<InputsAndAST>
AST) {
442 EXPECT_TRUE(
bool(
AST))
443 <<
"Should not invalidate based on an update with same content: "
444 << llvm::toString(
AST.takeError());
448 ADD_FAILURE() <<
"Shouldn't build, identical to previous";
453 EXPECT_EQ(1, Actions.load()) <<
"All actions should run";
456TEST_F(TUSchedulerTests, ManyUpdates) {
457 const int FilesCount = 3;
458 const int UpdatesPerFile = 10;
461 int TotalASTReads = 0;
462 int TotalPreambleReads = 0;
463 int TotalUpdates = 0;
464 llvm::StringMap<int> LatestDiagVersion;
468 auto Opts = optsForTest();
472 std::vector<std::string> Files;
473 for (
int I = 0; I < FilesCount; ++I) {
474 std::string Name =
"foo" + std::to_string(I) +
".cpp";
476 this->FS.Files[Files.back()] =
"";
479 StringRef Contents1 = R
"cpp(int a;)cpp";
480 StringRef Contents2 = R"cpp(int main() { return 1; })cpp";
481 StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp";
483 StringRef AllContents[] = {Contents1, Contents2, Contents3};
484 const int AllContentsSize = 3;
491 for (
int FileI = 0; FileI < FilesCount; ++FileI) {
492 for (
int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) {
493 auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize];
495 auto File = Files[FileI];
496 auto Inputs = getInputs(
File, Contents.str());
499 Inputs.Version = std::to_string(UpdateI);
502 [
File, Nonce, Version(Inputs.Version), &Mut, &TotalUpdates,
503 &LatestDiagVersion](std::vector<Diag>) {
504 EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
505 EXPECT_EQ(File, boundPath());
507 std::lock_guard<std::mutex> Lock(Mut);
509 EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
511 auto It = LatestDiagVersion.try_emplace(File, -1);
512 const int PrevVersion = It.first->second;
514 ASSERT_TRUE(llvm::to_integer(Version, CurVersion, 10));
515 EXPECT_LT(PrevVersion, CurVersion);
516 It.first->getValue() = CurVersion;
523 [
File, Inputs, Nonce, &Mut,
524 &TotalASTReads](Expected<InputsAndAST>
AST) {
526 EXPECT_EQ(
File, boundPath());
528 ASSERT_TRUE((
bool)
AST);
529 EXPECT_EQ(
AST->Inputs.Contents, Inputs.Contents);
530 EXPECT_EQ(
AST->Inputs.Version, Inputs.Version);
531 EXPECT_EQ(
AST->AST.version(), Inputs.Version);
533 std::lock_guard<std::mutex> Lock(Mut);
543 [
File, Inputs, Nonce, &Mut,
544 &TotalPreambleReads](Expected<InputsAndPreamble>
Preamble) {
546 EXPECT_EQ(
File, boundPath());
549 EXPECT_EQ(
Preamble->Contents, Inputs.Contents);
551 std::lock_guard<std::mutex> Lock(Mut);
552 ++TotalPreambleReads;
561 std::lock_guard<std::mutex> Lock(Mut);
564 EXPECT_GE(TotalUpdates, FilesCount);
565 EXPECT_LE(TotalUpdates, FilesCount * UpdatesPerFile);
567 for (
const auto &Entry : LatestDiagVersion)
568 EXPECT_EQ(Entry.second, UpdatesPerFile - 1);
569 EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile);
570 EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile);
573TEST_F(TUSchedulerTests, EvictedAST) {
574 std::atomic<int> BuiltASTCounter(0);
575 auto Opts = optsForTest();
576 Opts.AsyncThreadsCount = 1;
577 Opts.RetentionPolicy.MaxRetainedASTs = 2;
581 llvm::StringLiteral SourceContents = R
"cpp(
585 llvm::StringLiteral OtherSourceContents = R"cpp(
594 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
595 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
599 [&BuiltASTCounter]() { ++BuiltASTCounter; });
601 ASSERT_EQ(BuiltASTCounter.load(), 1);
602 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
603 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(1));
608 [&BuiltASTCounter]() { ++BuiltASTCounter; });
610 [&BuiltASTCounter]() { ++BuiltASTCounter; });
612 ASSERT_EQ(BuiltASTCounter.load(), 3);
613 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
614 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(2));
617 ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
621 [&BuiltASTCounter]() { ++BuiltASTCounter; });
623 ASSERT_EQ(BuiltASTCounter.load(), 4);
624 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
625 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(1));
629 EXPECT_THAT(S.getFilesWithCachedAST(),
630 UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
636TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
637 auto Opts = optsForTest();
638 Opts.RetentionPolicy.MaxRetainedASTs = 1;
642 auto FooInputs = getInputs(Foo,
"int x=1;");
644 auto BarInputs = getInputs(Bar,
"int x=2;");
651 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
658 ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
660 ASSERT_EQ(S.fileStats().lookup(Foo).ASTBuilds, 1u);
661 ASSERT_EQ(S.fileStats().lookup(Bar).ASTBuilds, 1u);
664TEST_F(TUSchedulerTests, EmptyPreamble) {
670 FS.Files[Header] =
"void foo()";
671 FS.Timestamps[Header] = time_t(0);
672 auto *WithPreamble = R
"cpp(
676 auto *WithEmptyPreamble = R
"cpp(int main() {})cpp";
680 [&](Expected<InputsAndPreamble>
Preamble) {
695 [&](Expected<InputsAndPreamble>
Preamble) {
703TEST_F(TUSchedulerTests, ASTSignalsSmokeTests) {
708 FS.Files[Header] =
"namespace tar { int foo(); }";
709 const char *Contents = R
"cpp(
724 [&](Expected<InputsAndPreamble> IP) {
726 std::vector<std::pair<StringRef, int>> NS;
727 for (
const auto &P : IP->Signals->RelatedNamespaces)
728 NS.emplace_back(
P.getKey(),
P.getValue());
730 UnorderedElementsAre(Pair(
"ns::", 1), Pair(
"tar::", 1)));
732 std::vector<std::pair<SymbolID, int>> Sym;
733 for (
const auto &P : IP->Signals->ReferencedSymbols)
734 Sym.emplace_back(
P.getFirst(),
P.getSecond());
735 EXPECT_THAT(Sym, UnorderedElementsAre(Pair(
ns(
"tar").ID, 1),
736 Pair(
ns(
"ns").ID, 1),
737 Pair(
func(
"tar::foo").ID, 1),
738 Pair(
func(
"ns::func").ID, 1)));
744TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
749 auto *NonEmptyPreamble = R
"cpp(
755 constexpr int ReadsToSchedule = 10;
756 std::mutex PreamblesMut;
757 std::vector<const void *> Preambles(ReadsToSchedule,
nullptr);
759 for (
int I = 0; I < ReadsToSchedule; ++I) {
762 [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) {
763 std::lock_guard<std::mutex> Lock(PreamblesMut);
764 Preambles[I] = cantFail(std::move(IP)).Preamble;
769 std::lock_guard<std::mutex> Lock(PreamblesMut);
770 ASSERT_NE(Preambles[0],
nullptr);
771 ASSERT_THAT(Preambles, Each(Preambles[0]));
774TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
780 FS.Files[Header] =
"int a;";
781 FS.Timestamps[Header] = time_t(0);
783 std::string SourceContents = R
"cpp(
789 auto DoUpdate = [&](std::string Contents) ->
bool {
790 std::atomic<bool> Updated(
false);
793 [&Updated](std::vector<Diag>) { Updated =
true; });
796 ADD_FAILURE() <<
"Updated has not finished in one second. Threading bug?";
801 ASSERT_TRUE(DoUpdate(SourceContents));
802 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
803 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
804 ASSERT_FALSE(DoUpdate(SourceContents));
805 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
806 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
809 FS.Timestamps[Header] = time_t(1);
810 ASSERT_TRUE(DoUpdate(SourceContents));
811 ASSERT_FALSE(DoUpdate(SourceContents));
812 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
813 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
816 SourceContents +=
"\nint c = b;";
817 ASSERT_TRUE(DoUpdate(SourceContents));
818 ASSERT_FALSE(DoUpdate(SourceContents));
819 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
820 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
823 CDB.ExtraClangFlags.push_back(
"-DSOMETHING");
824 ASSERT_TRUE(DoUpdate(SourceContents));
825 ASSERT_FALSE(DoUpdate(SourceContents));
828 ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 5u);
829 ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
835TEST_F(TUSchedulerTests, MissingHeader) {
836 CDB.ExtraClangFlags.push_back(
"-I" +
testPath(
"a"));
837 CDB.ExtraClangFlags.push_back(
"-I" +
testPath(
"b"));
839 FS.Files.try_emplace(
"a/__unused__");
840 FS.Files.try_emplace(
"b/__unused__");
847 auto *SourceContents = R
"cpp(
852 ParseInputs Inputs = getInputs(Source, SourceContents);
853 std::atomic<size_t> DiagCount(0);
859 [&DiagCount](std::vector<Diag> Diags) {
864 "use of undeclared identifier 'b'")));
868 FS.Files[HeaderB] =
"int b;";
869 FS.Timestamps[HeaderB] = time_t(1);
873 [&DiagCount](std::vector<Diag> Diags) {
875 EXPECT_THAT(Diags, IsEmpty());
881 FS.Files[HeaderA] =
"int a;";
882 FS.Timestamps[HeaderA] = time_t(1);
886 [&DiagCount](std::vector<Diag> Diags) {
889 <<
"Didn't expect new diagnostics when adding a/foo.h";
893 Inputs.ForceRebuild =
true;
896 [&DiagCount](std::vector<Diag> Diags) {
902 EXPECT_EQ(DiagCount, 3U);
905TEST_F(TUSchedulerTests, NoChangeDiags) {
910 const auto *Contents =
"int a; int b;";
912 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"hit"), SizeIs(0));
913 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"miss"), SizeIs(0));
914 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(0));
915 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
918 [](std::vector<Diag>) { ADD_FAILURE() <<
"Should not be called."; });
919 S.runWithAST(
"touchAST", FooCpp, [](Expected<InputsAndAST> IA) {
921 cantFail(std::move(IA));
924 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"hit"), SizeIs(0));
925 EXPECT_THAT(Tracer.takeMetric(
"ast_access_read",
"miss"), SizeIs(1));
929 std::atomic<bool> SeenDiags(
false);
931 [&](std::vector<Diag>) { SeenDiags =
true; });
933 ASSERT_TRUE(SeenDiags);
934 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"hit"), SizeIs(1));
935 EXPECT_THAT(Tracer.takeMetric(
"ast_access_diag",
"miss"), SizeIs(0));
941 [&](std::vector<Diag>) { ADD_FAILURE() <<
"Should not be called."; });
945TEST_F(TUSchedulerTests, Run) {
946 for (
bool Sync : {
false,
true}) {
947 auto Opts = optsForTest();
949 Opts.AsyncThreadsCount = 0;
951 std::atomic<int> Counter(0);
952 S.run(
"add 1",
"", [&] { ++Counter; });
953 S.run(
"add 2",
"", [&] { Counter += 2; });
955 EXPECT_EQ(Counter.load(), 3);
960 const char *
Path =
"somepath";
961 S.run(
"props context",
Path, [&] {
963 EXPECT_EQ(
Path, boundPath());
973 void onFileUpdated(PathRef File,
const TUStatus &Status)
override {
974 auto ASTAction = Status.ASTActivity.K;
976 std::lock_guard<std::mutex> Lock(Mutex);
983 if (ASTActions.empty() || ASTActions.back() != ASTAction)
984 ASTActions.push_back(ASTAction);
985 if (PreambleActions.empty() || PreambleActions.back() != PreambleAction)
986 PreambleActions.push_back(PreambleAction);
989 std::vector<PreambleAction> preambleStatuses() {
990 std::lock_guard<std::mutex> Lock(Mutex);
991 return PreambleActions;
994 std::vector<ASTAction::Kind> astStatuses() {
995 std::lock_guard<std::mutex> Lock(Mutex);
1001 std::vector<ASTAction::Kind> ASTActions;
1002 std::vector<PreambleAction> PreambleActions;
1011 Server.addDocument(
testPath(
"foo.cpp"), Code.code(),
"1",
1013 ASSERT_TRUE(Server.blockUntilIdleForTest());
1014 Server.locateSymbolAt(
testPath(
"foo.cpp"), Code.point(),
1015 [](Expected<std::vector<LocatedSymbol>> Result) {
1016 ASSERT_TRUE((bool)Result);
1018 ASSERT_TRUE(Server.blockUntilIdleForTest());
1020 EXPECT_THAT(CaptureTUStatus.preambleStatuses(),
1031 EXPECT_THAT(CaptureTUStatus.astStatuses(),
1047TEST_F(TUSchedulerTests, CommandLineErrors) {
1049 CDB.ExtraClangFlags = {
"-fsome-unknown-flag"};
1054 TUScheduler S(CDB, optsForTest(), captureDiags());
1055 std::vector<Diag> Diagnostics;
1056 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1058 Diagnostics = std::move(D);
1071TEST_F(TUSchedulerTests, CommandLineWarnings) {
1073 CDB.ExtraClangFlags = {
"-Wsome-unknown-warning"};
1078 TUScheduler S(CDB, optsForTest(), captureDiags());
1079 std::vector<Diag> Diagnostics;
1080 updateWithDiags(S,
testPath(
"foo.cpp"),
"void test() {}",
1082 Diagnostics = std::move(D);
1087 EXPECT_THAT(Diagnostics, IsEmpty());
1091 namespace c = std::chrono;
1092 DebouncePolicy::clock::duration History[] = {
1099 Policy.
Min = c::seconds(3);
1100 Policy.Max = c::seconds(25);
1102 auto Compute = [&](llvm::ArrayRef<DebouncePolicy::clock::duration> History) {
1103 return c::duration_cast<c::duration<float, c::seconds::period>>(
1104 Policy.compute(History))
1107 EXPECT_NEAR(10, Compute(History), 0.01) <<
"(upper) median = 10";
1108 Policy.RebuildRatio = 1.5;
1109 EXPECT_NEAR(15, Compute(History), 0.01) <<
"median = 10, ratio = 1.5";
1110 Policy.RebuildRatio = 3;
1111 EXPECT_NEAR(25, Compute(History), 0.01) <<
"constrained by max";
1112 Policy.RebuildRatio = 0;
1113 EXPECT_NEAR(3, Compute(History), 0.01) <<
"constrained by min";
1114 EXPECT_NEAR(25, Compute({}), 0.01) <<
"no history -> max";
1117TEST_F(TUSchedulerTests, AsyncPreambleThread) {
1122 BlockPreambleThread(llvm::StringRef BlockVersion, Notification &N)
1123 : BlockVersion(BlockVersion), N(N) {}
1125 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1126 std::shared_ptr<const include_cleaner::PragmaIncludes>)
override {
1127 if (Version == BlockVersion)
1132 llvm::StringRef BlockVersion;
1136 static constexpr llvm::StringLiteral InputsV0 =
"v0";
1137 static constexpr llvm::StringLiteral InputsV1 =
"v1";
1140 std::make_unique<BlockPreambleThread>(InputsV1, Ready));
1143 auto PI = getInputs(
File,
"");
1144 PI.Version = InputsV0.str();
1149 PI.Version = InputsV1.str();
1156 S.runWithAST(
"test",
File, [&](Expected<InputsAndAST>
AST) {
1157 ASSERT_TRUE(
bool(
AST));
1160 EXPECT_THAT(
AST->AST.preambleVersion(), InputsV0);
1161 EXPECT_THAT(
AST->Inputs.Version, InputsV1.str());
1162 RunASTAction.notify();
1164 RunASTAction.
wait();
1168TEST_F(TUSchedulerTests, OnlyPublishWhenPreambleIsBuilt) {
1170 PreamblePublishCounter(
int &PreamblePublishCount)
1171 : PreamblePublishCount(PreamblePublishCount) {}
1172 void onPreamblePublished(PathRef File)
override { ++PreamblePublishCount; }
1173 int &PreamblePublishCount;
1176 int PreamblePublishCount = 0;
1178 std::make_unique<PreamblePublishCounter>(PreamblePublishCount));
1183 EXPECT_EQ(PreamblePublishCount, 1);
1187 EXPECT_EQ(PreamblePublishCount, 1);
1191 EXPECT_EQ(PreamblePublishCount, 2);
1194TEST_F(TUSchedulerTests, PublishWithStalePreamble) {
1199 using DiagsCB = std::function<void(ParsedAST &)>;
1200 BlockPreambleThread(Notification &UnblockPreamble, DiagsCB CB)
1201 : UnblockPreamble(UnblockPreamble), CB(std::move(CB)) {}
1204 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1205 std::shared_ptr<const include_cleaner::PragmaIncludes>)
override {
1208 <<
"Expected notification";
1212 void onMainAST(PathRef File, ParsedAST &AST, PublishFn Publish)
override {
1216 void onFailedAST(PathRef File, llvm::StringRef Version,
1217 std::vector<Diag> Diags, PublishFn Publish)
override {
1218 ADD_FAILURE() <<
"Received failed ast for: " <<
File <<
" with version "
1223 bool BuildBefore =
false;
1224 Notification &UnblockPreamble;
1225 std::function<void(ParsedAST &)> CB;
1230 class DiagCollector {
1232 void onDiagnostics(ParsedAST &AST) {
1233 std::scoped_lock<std::mutex> Lock(DiagMu);
1234 DiagVersions.emplace_back(
1235 std::make_pair(
AST.preambleVersion()->str(),
AST.version().str()));
1236 DiagsReceived.notify_all();
1239 std::pair<std::string, std::string>
1240 waitForNewDiags(TUScheduler &S, PathRef File, ParseInputs PI) {
1241 std::unique_lock<std::mutex> Lock(DiagMu);
1244 S.update(File, std::move(PI), WantDiagnostics::Auto);
1245 size_t OldSize = DiagVersions.size();
1246 bool ReceivedDiags = DiagsReceived.wait_for(
1247 Lock, std::chrono::seconds(5),
1248 [
this, OldSize] {
return OldSize + 1 == DiagVersions.size(); });
1249 if (!ReceivedDiags) {
1250 ADD_FAILURE() <<
"Timed out waiting for diags";
1251 return {
"invalid",
"version"};
1253 return DiagVersions.back();
1256 std::vector<std::pair<std::string, std::string>> diagVersions() {
1257 std::scoped_lock<std::mutex> Lock(DiagMu);
1258 return DiagVersions;
1262 std::condition_variable DiagsReceived;
1264 std::vector<std::pair< std::string,
1269 DiagCollector Collector;
1271 auto DiagCallbacks = std::make_unique<BlockPreambleThread>(
1274 TUScheduler S(CDB, optsForTest(), std::move(DiagCallbacks));
1277 return Collector.waitForNewDiags(S,
File, std::move(PI));
1281 auto PI = getInputs(
File,
"");
1282 PI.Version = PI.Contents =
"1";
1283 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"1"));
1288 PI.Contents =
"#define BAR\n" + PI.Version;
1289 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"2"));
1292 PI.Contents =
"#define FOO\n" + PI.Version;
1293 ASSERT_THAT(BlockForDiags(PI), testing::Pair(
"1",
"3"));
1295 UnblockPreamble.notify();
1299 EXPECT_THAT(Collector.diagVersions().back(), Pair(PI.Version, PI.Version));
1303 PI.Contents =
"#define FOO\n" + PI.Version;
1306 EXPECT_THAT(Collector.diagVersions().back(), Pair(
"3",
"3"));
1311TEST_F(TUSchedulerTests, IncluderCache) {
1312 static std::string Main =
testPath(
"main.cpp"), Main2 =
testPath(
"main2.cpp"),
1315 Unreliable =
testPath(
"unreliable.h"),
1317 NotIncluded =
testPath(
"not_included.h");
1319 std::optional<tooling::CompileCommand>
1320 getCompileCommand(PathRef File)
const override {
1321 if (File == NoCmd || File == NotIncluded || FailAll)
1322 return std::nullopt;
1323 auto Basic = getFallbackCommand(File);
1324 Basic.Heuristic.clear();
1325 if (File == Unreliable) {
1326 Basic.Heuristic =
"not reliable";
1327 }
else if (File == Main) {
1328 Basic.CommandLine.push_back(
"-DMAIN");
1329 }
else if (File == Main2) {
1330 Basic.CommandLine.push_back(
"-DMAIN2");
1331 }
else if (File == Main3) {
1332 Basic.CommandLine.push_back(
"-DMAIN3");
1337 std::atomic<bool> FailAll{
false};
1340 auto GetFlags = [&](
PathRef Header) {
1344 tooling::CompileCommand Cmd;
1346 [&](llvm::Expected<InputsAndPreamble> Inputs) {
1347 ASSERT_FALSE(!Inputs) << Inputs.takeError();
1348 Cmd = std::move(Inputs->Command);
1353 return Cmd.CommandLine;
1356 for (
const auto &
Path : {NoCmd, Unreliable, OK, NotIncluded})
1357 FS.Files[
Path] =
";";
1360 EXPECT_THAT(GetFlags(Main), Contains(
"-DMAIN")) <<
"sanity check";
1361 EXPECT_THAT(GetFlags(NoCmd), Not(Contains(
"-DMAIN"))) <<
"no includes yet";
1364 const char *AllIncludes = R
"cpp(
1367 #include "unreliable.h"
1371 EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN"))
1372 <<
"Included from main file, has no own command";
1373 EXPECT_THAT(GetFlags(Unreliable), Contains(
"-DMAIN"))
1374 <<
"Included from main file, own command is heuristic";
1375 EXPECT_THAT(GetFlags(OK), Not(Contains(
"-DMAIN")))
1376 <<
"Included from main file, but own command is used";
1377 EXPECT_THAT(GetFlags(NotIncluded), Not(Contains(
"-DMAIN")))
1378 <<
"Not included from main file";
1381 std::string SomeIncludes = R
"cpp(
1383 #include "not_included.h"
1387 EXPECT_THAT(GetFlags(NoCmd),
1388 AllOf(Contains("-DMAIN"), Not(Contains(
"-DMAIN2"))))
1389 <<
"mainfile association is stable";
1390 EXPECT_THAT(GetFlags(NotIncluded),
1391 AllOf(Contains(
"-DMAIN2"), Not(Contains(
"-DMAIN"))))
1392 <<
"new headers are associated with new mainfile";
1398 EXPECT_THAT(GetFlags(NoCmd),
1399 AllOf(Contains(
"-DMAIN"), Not(Contains(
"-DMAIN2"))))
1400 <<
"mainfile association not updated yet!";
1405 EXPECT_THAT(GetFlags(NoCmd), Contains(
"-DMAIN3"))
1406 <<
"association invalidated and then claimed by main3";
1407 EXPECT_THAT(GetFlags(Unreliable), Contains(
"-DMAIN"))
1408 <<
"association invalidated but not reclaimed";
1409 EXPECT_THAT(GetFlags(NotIncluded), Contains(
"-DMAIN2"))
1410 <<
"association still valid";
1414 EXPECT_THAT(GetFlags(NoCmd), Not(Contains(
"-DMAIN3")))
1415 <<
"association should've been invalidated.";
1421 CDB.FailAll =
false;
1424 EXPECT_THAT(GetFlags(NoCmd), Contains(
"-DMAIN3"))
1425 <<
"association invalidated and then claimed by main3";
1428TEST_F(TUSchedulerTests, PreservesLastActiveFile) {
1429 for (
bool Sync : {
false,
true}) {
1430 auto Opts = optsForTest();
1432 Opts.AsyncThreadsCount = 0;
1435 auto CheckNoFileActionsSeesLastActiveFile =
1436 [&](llvm::StringRef LastActiveFile) {
1438 std::atomic<int> Counter(0);
1441 S.run(
"run-UsesLastActiveFile",
"", [&] {
1443 EXPECT_EQ(LastActiveFile, boundPath());
1445 S.runQuick(
"runQuick-UsesLastActiveFile",
"", [&] {
1447 EXPECT_EQ(LastActiveFile, boundPath());
1450 EXPECT_EQ(2, Counter.load());
1454 CheckNoFileActionsSeesLastActiveFile(
"");
1460 CheckNoFileActionsSeesLastActiveFile(
Path);
1464 CheckNoFileActionsSeesLastActiveFile(
Path);
1468 S.runWithAST(
Path,
Path, [](llvm::Expected<InputsAndAST> Inp) {
1469 EXPECT_TRUE(
bool(Inp));
1471 CheckNoFileActionsSeesLastActiveFile(
Path);
1477 [](llvm::Expected<InputsAndPreamble> Inp) { EXPECT_TRUE(
bool(Inp)); });
1478 CheckNoFileActionsSeesLastActiveFile(
Path);
1482 CheckNoFileActionsSeesLastActiveFile(
Path);
1485 auto LastActive =
Path;
1488 CheckNoFileActionsSeesLastActiveFile(LastActive);
1492TEST_F(TUSchedulerTests, PreambleThrottle) {
1493 const int NumRequests = 4;
1498 std::vector<std::string> Acquires;
1499 std::vector<RequestID> Releases;
1500 llvm::DenseMap<RequestID, Callback> Callbacks;
1502 std::optional<std::pair<RequestID, Notification *>> Notify;
1504 RequestID acquire(llvm::StringRef Filename,
Callback CB)
override {
1508 std::lock_guard<std::mutex> Lock(Mu);
1509 ID = Acquires.size();
1510 Acquires.emplace_back(Filename);
1512 if (Acquires.size() == NumRequests) {
1513 Invoke = std::move(CB);
1515 Callbacks.try_emplace(ID, std::move(CB));
1521 std::lock_guard<std::mutex> Lock(Mu);
1522 if (Notify && ID == Notify->first) {
1523 Notify->second->notify();
1530 void release(RequestID ID)
override {
1533 std::lock_guard<std::mutex> Lock(Mu);
1534 Releases.push_back(ID);
1535 if (ID > 0 && Acquires.size() == NumRequests)
1536 SatisfyNext = std::move(Callbacks[ID - 1]);
1550 std::vector<std::string> &Filenames;
1551 CaptureBuiltFilenames(std::vector<std::string> &Filenames)
1552 : Filenames(Filenames) {}
1554 PathRef Path, llvm::StringRef Version, CapturedASTCtx,
1555 std::shared_ptr<const include_cleaner::PragmaIncludes> PI)
override {
1559 Filenames.emplace_back(Path);
1563 auto Opts = optsForTest();
1564 Opts.AsyncThreadsCount = 2 * NumRequests;
1565 Opts.PreambleThrottler = &Throttler;
1567 std::vector<std::string> Filenames;
1570 std::vector<std::string> BuiltFilenames;
1572 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1573 for (
unsigned I = 0; I < NumRequests; ++I) {
1575 Filenames.push_back(
Path);
1581 EXPECT_THAT(Throttler.Acquires,
1582 testing::UnorderedElementsAreArray(Filenames));
1583 EXPECT_THAT(BuiltFilenames,
1584 testing::UnorderedElementsAreArray(Filenames));
1586 EXPECT_THAT(BuiltFilenames,
1587 testing::ElementsAreArray(Throttler.Acquires.rbegin(),
1588 Throttler.Acquires.rend()));
1590 EXPECT_THAT(Throttler.Releases, ElementsAre(3, 2, 1, 0));
1599 Throttler.Notify = {1, &AfterAcquire2};
1600 std::vector<std::string> BuiltFilenames;
1606 std::make_unique<CaptureBuiltFilenames>(BuiltFilenames));
1608 [&] { AfterFinishA.notify(); });
1610 AfterAcquire2.wait();
1613 EXPECT_THAT(Throttler.Acquires,
1614 testing::UnorderedElementsAreArray(Filenames));
1615 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1617 EXPECT_THAT(Throttler.Releases, testing::IsEmpty());
1625 AfterFinishA.wait();
1627 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1629 EXPECT_THAT(Throttler.Releases, ElementsAre(AnyOf(1, 0)));
1635 EXPECT_THAT(Throttler.Acquires,
1636 testing::UnorderedElementsAreArray(Filenames));
1637 EXPECT_THAT(BuiltFilenames, testing::IsEmpty());
1639 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)
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::ProBoundsAvoidUncheckedContainerAccessCheck 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.