19 #include "clang/Config/config.h" 20 #include "clang/Sema/CodeCompleteConsumer.h" 21 #include "llvm/ADT/SmallVector.h" 22 #include "llvm/ADT/StringMap.h" 23 #include "llvm/Support/Errc.h" 24 #include "llvm/Support/Path.h" 25 #include "llvm/Support/Regex.h" 26 #include "gmock/gmock.h" 27 #include "gtest/gtest.h" 41 using ::testing::ElementsAre;
42 using ::testing::Field;
44 using ::testing::IsEmpty;
45 using ::testing::Pair;
46 using ::testing::UnorderedElementsAre;
48 MATCHER_P2(DeclAt, File,
Range,
"") {
49 return arg.PreferredDeclaration ==
53 bool diagsContainErrors(
const std::vector<Diag> &Diagnostics) {
54 for (
auto D : Diagnostics) {
56 D.Severity == DiagnosticsEngine::Fatal)
62 class ErrorCheckingDiagConsumer :
public DiagnosticsConsumer {
65 std::vector<Diag> Diagnostics)
override {
66 bool HadError = diagsContainErrors(Diagnostics);
67 std::lock_guard<std::mutex> Lock(Mutex);
68 HadErrorInLastDiags = HadError;
71 bool hadErrorInLastDiags() {
72 std::lock_guard<std::mutex> Lock(Mutex);
73 return HadErrorInLastDiags;
78 bool HadErrorInLastDiags =
false;
83 class MultipleErrorCheckingDiagConsumer :
public DiagnosticsConsumer {
86 std::vector<Diag> Diagnostics)
override {
87 bool HadError = diagsContainErrors(Diagnostics);
89 std::lock_guard<std::mutex> Lock(Mutex);
90 LastDiagsHadError[
File] = HadError;
96 std::vector<std::pair<Path, bool>> filesWithDiags()
const {
97 std::vector<std::pair<Path, bool>>
Result;
98 std::lock_guard<std::mutex> Lock(Mutex);
99 for (
const auto &It : LastDiagsHadError)
100 Result.emplace_back(It.first(), It.second);
105 std::lock_guard<std::mutex> Lock(Mutex);
106 LastDiagsHadError.clear();
110 mutable std::mutex Mutex;
111 llvm::StringMap<bool> LastDiagsHadError;
115 std::string replacePtrsInDump(std::string
const &Dump) {
116 llvm::Regex RE(
"0x[0-9a-fA-F]+");
117 llvm::SmallVector<llvm::StringRef, 1> Matches;
118 llvm::StringRef Pending = Dump;
121 while (RE.match(Pending, &Matches)) {
122 assert(Matches.size() == 1 &&
"Exactly one match expected");
123 auto MatchPos = Matches[0].data() - Pending.data();
125 Result += Pending.take_front(MatchPos);
126 Pending = Pending.drop_front(MatchPos + Matches[0].size());
134 auto DumpWithMemLocs =
runDumpAST(Server, File);
135 return replacePtrsInDump(DumpWithMemLocs);
138 class ClangdVFSTest :
public ::testing::Test {
140 std::string parseSourceAndDumpAST(
141 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
142 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
143 bool ExpectErrors =
false) {
146 MockCompilationDatabase CDB;
148 for (
const auto &FileWithContents : ExtraFiles)
149 FS.Files[
testPath(FileWithContents.first)] = FileWithContents.second;
151 auto SourceFilename =
testPath(SourceFileRelPath);
152 Server.addDocument(SourceFilename, SourceContents);
153 auto Result = dumpASTWithoutMemoryLocs(
Server, SourceFilename);
154 EXPECT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
155 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
160 TEST_F(ClangdVFSTest, Parse) {
164 auto Empty = parseSourceAndDumpAST(
"foo.cpp",
"", {});
165 auto OneDecl = parseSourceAndDumpAST(
"foo.cpp",
"int a;", {});
166 auto SomeDecls = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;", {});
167 EXPECT_NE(
Empty, OneDecl);
168 EXPECT_NE(
Empty, SomeDecls);
169 EXPECT_NE(SomeDecls, OneDecl);
171 auto Empty2 = parseSourceAndDumpAST(
"foo.cpp",
"");
172 auto OneDecl2 = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
173 auto SomeDecls2 = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
174 EXPECT_EQ(
Empty, Empty2);
175 EXPECT_EQ(OneDecl, OneDecl2);
176 EXPECT_EQ(SomeDecls, SomeDecls2);
179 TEST_F(ClangdVFSTest, ParseWithHeader) {
180 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {},
182 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {{
"foo.h",
""}},
185 const auto SourceContents = R
"cpp( 189 parseSourceAndDumpAST("foo.cpp", SourceContents, {{
"foo.h",
""}},
191 parseSourceAndDumpAST(
"foo.cpp", SourceContents, {{
"foo.h",
"int a;"}},
195 TEST_F(ClangdVFSTest, Reparse) {
198 MockCompilationDatabase CDB;
201 const auto SourceContents = R
"cpp( 208 FS.Files[
testPath(
"foo.h")] =
"int a;";
209 FS.Files[FooCpp] = SourceContents;
211 Server.addDocument(FooCpp, SourceContents);
212 auto DumpParse1 = dumpASTWithoutMemoryLocs(
Server, FooCpp);
213 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
214 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
216 Server.addDocument(FooCpp,
"");
217 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(
Server, FooCpp);
218 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
219 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
221 Server.addDocument(FooCpp, SourceContents);
222 auto DumpParse2 = dumpASTWithoutMemoryLocs(
Server, FooCpp);
223 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
224 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
226 EXPECT_EQ(DumpParse1, DumpParse2);
227 EXPECT_NE(DumpParse1, DumpParseEmpty);
230 TEST_F(ClangdVFSTest, ReparseOnHeaderChange) {
233 MockCompilationDatabase CDB;
236 const auto SourceContents = R
"cpp( 244 FS.Files[FooH] =
"int a;";
245 FS.Files[FooCpp] = SourceContents;
247 Server.addDocument(FooCpp, SourceContents);
248 auto DumpParse1 = dumpASTWithoutMemoryLocs(
Server, FooCpp);
249 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
250 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
253 Server.addDocument(FooCpp, SourceContents);
254 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(
Server, FooCpp);
255 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
256 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
258 FS.Files[FooH] =
"int a;";
259 Server.addDocument(FooCpp, SourceContents);
260 auto DumpParse2 = dumpASTWithoutMemoryLocs(
Server, FooCpp);
261 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
262 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
264 EXPECT_EQ(DumpParse1, DumpParse2);
265 EXPECT_NE(DumpParse1, DumpParseDifferent);
268 TEST_F(ClangdVFSTest, PropagatesContexts) {
269 static Key<int> Secret;
270 struct FSProvider :
public FileSystemProvider {
271 IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem()
const override {
277 struct DiagConsumer :
public DiagnosticsConsumer {
279 std::vector<Diag> Diagnostics)
override {
284 MockCompilationDatabase CDB;
289 WithContextValue Entrypoint(Secret, 42);
292 ASSERT_TRUE(
Server.blockUntilIdleForTest());
293 EXPECT_EQ(FS.Got, 42);
294 EXPECT_EQ(DiagConsumer.Got, 42);
299 TEST_F(ClangdVFSTest, SearchLibDir) {
303 MockCompilationDatabase CDB;
304 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
305 {
"-xc++",
"-target",
"x86_64-linux-unknown",
306 "-m64",
"--gcc-toolchain=/randomusr",
307 "-stdlib=libstdc++"});
311 SmallString<8> Version(
"4.9.3");
314 SmallString<64> LibDir(
"/randomusr/lib/gcc/x86_64-linux-gnu");
315 llvm::sys::path::append(LibDir, Version);
319 SmallString<64> DummyLibFile;
320 llvm::sys::path::append(DummyLibFile, LibDir,
"64",
"crtbegin.o");
321 FS.Files[DummyLibFile] =
"";
323 SmallString<64> IncludeDir(
"/randomusr/include/c++");
324 llvm::sys::path::append(IncludeDir, Version);
326 SmallString<64> StringPath;
327 llvm::sys::path::append(StringPath, IncludeDir,
"string");
328 FS.Files[StringPath] =
"class mock_string {};";
331 const auto SourceContents = R
"cpp( 335 FS.Files[FooCpp] = SourceContents; 338 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); 340 const auto SourceContentsWithError = R
"cpp( 345 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); 347 #endif // LLVM_ON_UNIX 349 TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
352 MockCompilationDatabase CDB;
356 const auto SourceContents1 = R
"cpp( 360 const auto SourceContents2 = R
"cpp( 365 FS.Files[FooCpp] = "";
368 CDB.ExtraClangFlags = {
"-xc"};
370 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
372 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
375 CDB.ExtraClangFlags = {
"-xc++"};
377 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
380 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
382 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
385 TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) {
388 MockCompilationDatabase CDB;
392 const auto SourceContents = R
"cpp( 397 int main() { return 0; } 399 FS.Files[FooCpp] = "";
402 CDB.ExtraClangFlags = {
"-DWITH_ERROR"};
404 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
407 CDB.ExtraClangFlags = {};
409 ASSERT_TRUE(
Server.blockUntilIdleForTest());
410 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
413 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
417 TEST_F(ClangdVFSTest, ReparseOpenedFiles) {
418 Annotations FooSource(R
"cpp( 420 static void $one[[bob]]() {} 422 static void $two[[bob]]() {} 425 int main () { bo^b (); return 0; } 428 Annotations BarSource(R"cpp( 434 Annotations BazSource(R"cpp( 439 MockCompilationDatabase CDB; 447 FS.Files[FooCpp] =
"";
448 FS.Files[BarCpp] =
"";
449 FS.Files[BazCpp] =
"";
451 CDB.ExtraClangFlags = {
"-DMACRO=1"};
452 Server.addDocument(FooCpp, FooSource.code());
453 Server.addDocument(BarCpp, BarSource.code());
454 Server.addDocument(BazCpp, BazSource.code());
455 ASSERT_TRUE(
Server.blockUntilIdleForTest());
457 EXPECT_THAT(DiagConsumer.filesWithDiags(),
458 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
true),
459 Pair(BazCpp,
false)));
462 EXPECT_TRUE(
bool(Locations));
463 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"one"))));
466 CDB.ExtraClangFlags.clear();
467 DiagConsumer.clear();
468 Server.removeDocument(BazCpp);
471 ASSERT_TRUE(
Server.blockUntilIdleForTest());
473 EXPECT_THAT(DiagConsumer.filesWithDiags(),
474 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
false)));
477 EXPECT_TRUE(
bool(Locations));
478 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"two"))));
481 TEST_F(ClangdVFSTest, MemoryUsage) {
484 MockCompilationDatabase CDB;
488 const auto SourceContents = R
"cpp( 495 FS.Files[FooCpp] =
"";
496 FS.Files[BarCpp] =
"";
498 EXPECT_THAT(
Server.getUsedBytesPerFile(), IsEmpty());
500 Server.addDocument(FooCpp, SourceContents);
501 Server.addDocument(BarCpp, SourceContents);
502 ASSERT_TRUE(
Server.blockUntilIdleForTest());
504 EXPECT_THAT(
Server.getUsedBytesPerFile(),
505 UnorderedElementsAre(Pair(FooCpp, Gt(0u)), Pair(BarCpp, Gt(0u))));
507 Server.removeDocument(FooCpp);
508 ASSERT_TRUE(
Server.blockUntilIdleForTest());
509 EXPECT_THAT(
Server.getUsedBytesPerFile(), ElementsAre(Pair(BarCpp, Gt(0u))));
511 Server.removeDocument(BarCpp);
512 ASSERT_TRUE(
Server.blockUntilIdleForTest());
513 EXPECT_THAT(
Server.getUsedBytesPerFile(), IsEmpty());
516 TEST_F(ClangdVFSTest, InvalidCompileCommand) {
519 MockCompilationDatabase CDB;
527 CDB.ExtraClangFlags.push_back(FooCpp);
538 clangd::CodeCompleteOptions()))
543 ASSERT_TRUE(
bool(SigHelp)) <<
"signatureHelp returned an error";
544 EXPECT_THAT(SigHelp->signatures, IsEmpty());
547 class ClangdThreadingTest :
public ClangdVFSTest {};
549 TEST_F(ClangdThreadingTest, StressTest) {
551 static const unsigned FilesCount = 5;
552 const unsigned RequestsCount = 500;
556 const unsigned BlockingRequestInterval = 40;
558 const auto SourceContentsWithoutErrors = R
"cpp( 565 const auto SourceContentsWithErrors = R
"cpp( 575 unsigned MaxLineForFileRequests = 7;
576 unsigned MaxColumnForFileRequests = 10;
580 for (
unsigned I = 0; I < FilesCount; ++I) {
581 std::string
Name = std::string(
"Foo") + std::to_string(I) +
".cpp";
583 FilePaths.push_back(
testPath(Name));
587 unsigned HitsWithoutErrors = 0;
588 unsigned HitsWithErrors = 0;
589 bool HadErrorsInLastDiags =
false;
592 class TestDiagConsumer :
public DiagnosticsConsumer {
594 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
597 std::vector<Diag> Diagnostics)
override {
598 StringRef FileIndexStr = llvm::sys::path::stem(File);
599 ASSERT_TRUE(FileIndexStr.consume_front(
"Foo"));
601 unsigned long FileIndex = std::stoul(FileIndexStr.str());
603 bool HadError = diagsContainErrors(Diagnostics);
605 std::lock_guard<std::mutex> Lock(Mutex);
607 Stats[FileIndex].HitsWithErrors++;
609 Stats[FileIndex].HitsWithoutErrors++;
610 Stats[FileIndex].HadErrorsInLastDiags = HadError;
613 std::vector<FileStat> takeFileStats() {
614 std::lock_guard<std::mutex> Lock(Mutex);
615 return std::move(Stats);
620 std::vector<FileStat> Stats;
623 struct RequestStats {
624 unsigned RequestsWithoutErrors = 0;
625 unsigned RequestsWithErrors = 0;
626 bool LastContentsHadErrors =
false;
627 bool FileIsRemoved =
true;
630 std::vector<RequestStats> ReqStats;
631 ReqStats.reserve(FilesCount);
632 for (
unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
633 ReqStats.emplace_back();
637 MockCompilationDatabase CDB;
641 std::random_device RandGen;
643 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
646 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
648 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
649 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
652 auto UpdateStatsOnAddDocument = [&](
unsigned FileIndex,
bool HadErrors) {
653 auto &Stats = ReqStats[FileIndex];
656 ++Stats.RequestsWithErrors;
658 ++Stats.RequestsWithoutErrors;
659 Stats.LastContentsHadErrors = HadErrors;
660 Stats.FileIsRemoved =
false;
663 auto UpdateStatsOnRemoveDocument = [&](
unsigned FileIndex) {
664 auto &Stats = ReqStats[FileIndex];
666 Stats.FileIsRemoved =
true;
669 auto AddDocument = [&](
unsigned FileIndex,
bool SkipCache) {
670 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
671 Server.addDocument(FilePaths[FileIndex],
672 ShouldHaveErrors ? SourceContentsWithErrors
673 : SourceContentsWithoutErrors,
675 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
679 auto AddDocumentRequest = [&]() {
680 unsigned FileIndex = FileIndexDist(RandGen);
681 AddDocument(FileIndex,
false);
684 auto ForceReparseRequest = [&]() {
685 unsigned FileIndex = FileIndexDist(RandGen);
686 AddDocument(FileIndex,
true);
689 auto RemoveDocumentRequest = [&]() {
690 unsigned FileIndex = FileIndexDist(RandGen);
692 if (ReqStats[FileIndex].FileIsRemoved)
693 AddDocument(FileIndex,
false);
695 Server.removeDocument(FilePaths[FileIndex]);
696 UpdateStatsOnRemoveDocument(FileIndex);
699 auto CodeCompletionRequest = [&]() {
700 unsigned FileIndex = FileIndexDist(RandGen);
702 if (ReqStats[FileIndex].FileIsRemoved)
703 AddDocument(FileIndex,
false);
706 Pos.line = LineDist(RandGen);
707 Pos.character = ColumnDist(RandGen);
715 clangd::CodeCompleteOptions()));
718 auto LocateSymbolRequest = [&]() {
719 unsigned FileIndex = FileIndexDist(RandGen);
721 if (ReqStats[FileIndex].FileIsRemoved)
722 AddDocument(FileIndex,
false);
725 Pos.line = LineDist(RandGen);
726 Pos.character = ColumnDist(RandGen);
731 std::vector<std::function<void()>> AsyncRequests = {
732 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
733 std::vector<std::function<void()>> BlockingRequests = {
734 CodeCompletionRequest, LocateSymbolRequest};
737 std::uniform_int_distribution<int> AsyncRequestIndexDist(
738 0, AsyncRequests.size() - 1);
739 std::uniform_int_distribution<int> BlockingRequestIndexDist(
740 0, BlockingRequests.size() - 1);
741 for (
unsigned I = 1; I <= RequestsCount; ++I) {
742 if (I % BlockingRequestInterval != 0) {
744 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
745 AsyncRequests[RequestIndex]();
748 auto RequestIndex = BlockingRequestIndexDist(RandGen);
749 BlockingRequests[RequestIndex]();
752 ASSERT_TRUE(
Server.blockUntilIdleForTest());
756 std::vector<FileStat> Stats = DiagConsumer.takeFileStats();
757 for (
unsigned I = 0; I < FilesCount; ++I) {
758 if (!ReqStats[I].FileIsRemoved) {
759 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
760 ReqStats[I].LastContentsHadErrors);
763 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
764 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
768 TEST_F(ClangdVFSTest, CheckSourceHeaderSwitch) {
771 MockCompilationDatabase CDB;
774 auto SourceContents = R
"cpp( 781 auto Invalid =
testPath(
"main.cpp");
783 FS.Files[FooCpp] = SourceContents;
784 FS.Files[FooH] =
"int a;";
785 FS.Files[Invalid] =
"int main() { \n return 0; \n }";
787 Optional<Path> PathResult =
Server.switchSourceHeader(FooCpp);
788 EXPECT_TRUE(PathResult.hasValue());
789 ASSERT_EQ(PathResult.getValue(), FooH);
791 PathResult =
Server.switchSourceHeader(FooH);
792 EXPECT_TRUE(PathResult.hasValue());
793 ASSERT_EQ(PathResult.getValue(), FooCpp);
795 SourceContents = R
"c( 805 FS.Files[FooC] = SourceContents;
806 FS.Files[FooHH] =
"int a;";
808 PathResult =
Server.switchSourceHeader(FooC);
809 EXPECT_TRUE(PathResult.hasValue());
810 ASSERT_EQ(PathResult.getValue(), FooHH);
815 FS.Files[Foo2C] = SourceContents;
816 FS.Files[Foo2HH] =
"int a;";
818 PathResult =
Server.switchSourceHeader(Foo2C);
819 EXPECT_TRUE(PathResult.hasValue());
820 ASSERT_EQ(PathResult.getValue(), Foo2HH);
824 auto Foo3HXX =
testPath(
"foo3.hxx");
826 SourceContents = R
"c( 831 FS.Files[Foo3C] = SourceContents; 832 FS.Files[Foo3HXX] = "int a;";
834 PathResult =
Server.switchSourceHeader(Foo3C);
835 EXPECT_TRUE(PathResult.hasValue());
836 ASSERT_EQ(PathResult.getValue(), Foo3HXX);
840 PathResult =
Server.switchSourceHeader(Invalid);
841 EXPECT_FALSE(PathResult.hasValue());
844 TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
845 class NoConcurrentAccessDiagConsumer :
public DiagnosticsConsumer {
847 std::atomic<int> Count = {0};
849 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
850 : StartSecondReparse(std::move(StartSecondReparse)) {}
852 void onDiagnosticsReady(
PathRef, std::vector<Diag>)
override {
854 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
855 ASSERT_TRUE(Lock.owns_lock())
856 <<
"Detected concurrent onDiagnosticsReady calls for the same file.";
861 FirstRequest =
false;
862 StartSecondReparse.set_value();
864 std::this_thread::sleep_for(std::chrono::milliseconds(50));
870 bool FirstRequest =
true;
871 std::promise<void> StartSecondReparse;
874 const auto SourceContentsWithoutErrors = R
"cpp( 881 const auto SourceContentsWithErrors = R
"cpp( 890 FS.Files[FooCpp] =
"";
892 std::promise<void> StartSecondPromise;
893 std::future<void> StartSecond = StartSecondPromise.get_future();
895 NoConcurrentAccessDiagConsumer
DiagConsumer(std::move(StartSecondPromise));
896 MockCompilationDatabase CDB;
898 Server.addDocument(FooCpp, SourceContentsWithErrors);
900 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
901 ASSERT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
902 ASSERT_EQ(DiagConsumer.Count, 2);
905 TEST_F(ClangdVFSTest, FormatCode) {
908 MockCompilationDatabase CDB;
912 std::string Code = R
"cpp( 924 FS.Files[Path] = Code; 927 auto Replaces =
Server.formatFile(Code,
Path);
928 EXPECT_TRUE(static_cast<bool>(Replaces));
929 auto Changed = tooling::applyAllReplacements(Code, *Replaces);
930 EXPECT_TRUE(static_cast<bool>(
Changed));
934 TEST_F(ClangdVFSTest, ChangedHeaderFromISystem) {
937 MockCompilationDatabase CDB;
940 auto SourcePath =
testPath(
"source/foo.cpp");
941 auto HeaderPath =
testPath(
"headers/foo.h");
942 FS.Files[HeaderPath] =
"struct X { int bar; };";
943 Annotations Code(R
"cpp( 949 CDB.ExtraClangFlags.push_back("-xc++");
950 CDB.ExtraClangFlags.push_back(
"-isystem" +
testPath(
"headers"));
954 clangd::CodeCompleteOptions()))
959 FS.Files[HeaderPath] =
"struct X { int bar; int baz; };";
962 clangd::CodeCompleteOptions()))
973 TEST(ClangdTests, PreambleVFSStatCache) {
974 class ListenStatsFSProvider :
public FileSystemProvider {
976 ListenStatsFSProvider(llvm::StringMap<unsigned> &CountStats)
977 : CountStats(CountStats) {}
979 IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem()
const override {
980 class ListenStatVFS :
public llvm::vfs::ProxyFileSystem {
982 ListenStatVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
983 llvm::StringMap<unsigned> &CountStats)
984 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
986 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
987 openFileForRead(
const Twine &
Path)
override {
988 ++CountStats[llvm::sys::path::filename(Path.str())];
989 return ProxyFileSystem::openFileForRead(Path);
991 llvm::ErrorOr<llvm::vfs::Status> status(
const Twine &Path)
override {
992 ++CountStats[llvm::sys::path::filename(Path.str())];
993 return ProxyFileSystem::status(Path);
997 llvm::StringMap<unsigned> &CountStats;
1000 return IntrusiveRefCntPtr<ListenStatVFS>(
1005 llvm::StringMap<std::string>
Files;
1006 llvm::StringMap<unsigned> &CountStats;
1009 llvm::StringMap<unsigned> CountStats;
1010 ListenStatsFSProvider
FS(CountStats);
1012 MockCompilationDatabase CDB;
1015 auto SourcePath =
testPath(
"foo.cpp");
1016 auto HeaderPath =
testPath(
"foo.h");
1017 FS.Files[HeaderPath] =
"struct TestSym {};";
1018 Annotations Code(R
"cpp( 1027 unsigned Before = CountStats[
"foo.h"];
1028 EXPECT_GT(Before, 0u);
1030 clangd::CodeCompleteOptions()))
1032 EXPECT_EQ(CountStats[
"foo.h"], Before);
1033 EXPECT_THAT(Completions,
1038 TEST_F(ClangdVFSTest, FlagsWithPlugins) {
1041 MockCompilationDatabase CDB;
1042 CDB.ExtraClangFlags = {
1048 OverlayCDB OCDB(&CDB);
1052 const auto SourceContents =
"int main() { return 0; }";
1053 FS.Files[FooCpp] = FooCpp;
1054 Server.addDocument(FooCpp, SourceContents);
1055 auto Result = dumpASTWithoutMemoryLocs(
Server, FooCpp);
1056 EXPECT_TRUE(
Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
1057 EXPECT_NE(Result,
"<no-ast>");
1060 TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) {
1063 MockCompilationDatabase CDB;
1067 Annotations Code(R
"cpp( 1068 namespace ns { int xyz; } 1073 FS.Files[FooCpp] = FooCpp; 1075 auto Opts = clangd::CodeCompleteOptions();
1079 CDB.ExtraClangFlags = {
"yolo.cc"};
1080 Server.addDocument(FooCpp, Code.code());
1081 ASSERT_TRUE(
Server.blockUntilIdleForTest());
1083 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1085 EXPECT_THAT(Res.Completions,
1090 CDB.ExtraClangFlags = {
"-std=c++11"};
1091 Server.addDocument(FooCpp, Code.code());
1092 ASSERT_TRUE(
Server.blockUntilIdleForTest());
1106 TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) {
1110 class DelayedCompilationDatabase :
public GlobalCompilationDatabase {
1112 DelayedCompilationDatabase(Notification &CanReturnCommand)
1113 : CanReturnCommand(CanReturnCommand) {}
1115 llvm::Optional<tooling::CompileCommand>
1116 getCompileCommand(
PathRef File)
const override {
1119 CanReturnCommand.wait();
1120 auto FileName = llvm::sys::path::filename(File);
1121 std::vector<std::string>
CommandLine = {
"clangd",
"-ffreestanding", File};
1122 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
1123 FileName, std::move(CommandLine),
"")};
1126 std::vector<std::string> ExtraClangFlags;
1129 Notification &CanReturnCommand;
1132 Notification CanReturnCommand;
1133 DelayedCompilationDatabase CDB(CanReturnCommand);
1137 Annotations Code(R
"cpp( 1138 namespace ns { int xyz; } 1143 FS.Files[FooCpp] = FooCpp; 1144 Server.addDocument(FooCpp, Code.code()); 1148 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1149 auto Opts = clangd::CodeCompleteOptions();
1153 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1155 CanReturnCommand.notify();
1156 ASSERT_TRUE(
Server.blockUntilIdleForTest());
1158 clangd::CodeCompleteOptions()))
Always use text-based completion.
llvm::Expected< CodeCompleteResult > runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, clangd::CodeCompleteOptions Opts)
std::string runDumpAST(ClangdServer &Server, PathRef File)
llvm::Expected< SignatureHelp > runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos)
llvm::Expected< std::vector< LocatedSymbol > > runLocateSymbolAt(ClangdServer &Server, PathRef File, Position Pos)
#define EXPECT_ERROR(expectedValue)
Run the parser if inputs (preamble) are ready.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
static Options optsForTest()
std::vector< llvm::StringRef > CommandLine
MockFSProvider FSProvider
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > buildTestFS(llvm::StringMap< std::string > const &Files, llvm::StringMap< time_t > const &Timestamps)
TEST(BackgroundQueueTest, Priority)
std::string testPath(PathRef File)
std::string Path
A typedef to represent a file path.
static const Context & current()
Returns the context for the current thread, creating it if needed.
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
static constexpr llvm::StringLiteral Name
std::vector< std::string > FilePaths
const Type & getExisting(const Key< Type > &Key) const
A helper to get a reference to a Key that must exist in the map.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::Expected< std::vector< DocumentHighlight > > runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos)
CharSourceRange Range
SourceRange for the file name.
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
std::vector< const char * > Expected
IgnoreDiagnostics DiagConsumer
llvm::Expected< std::vector< TextEdit > > runRename(ClangdServer &Server, PathRef File, Position Pos, llvm::StringRef NewName)
Diagnostics must not be generated for this snapshot.
llvm::StringMap< std::string > Files
void runAddDocument(ClangdServer &Server, PathRef File, llvm::StringRef Contents, WantDiagnostics WantDiags)