21 #include "clang/Config/config.h"
22 #include "clang/Sema/CodeCompleteConsumer.h"
23 #include "clang/Tooling/ArgumentsAdjusters.h"
24 #include "llvm/ADT/None.h"
25 #include "llvm/ADT/Optional.h"
26 #include "llvm/ADT/SmallVector.h"
27 #include "llvm/ADT/StringMap.h"
28 #include "llvm/Support/Errc.h"
29 #include "llvm/Support/Path.h"
30 #include "llvm/Support/Regex.h"
31 #include "gmock/gmock.h"
32 #include "gtest/gtest.h"
46 using ::testing::AllOf;
47 using ::testing::ElementsAre;
48 using ::testing::Field;
50 using ::testing::IsEmpty;
51 using ::testing::Pair;
52 using ::testing::SizeIs;
53 using ::testing::UnorderedElementsAre;
55 MATCHER_P2(DeclAt, File,
Range,
"") {
56 return arg.PreferredDeclaration ==
60 bool diagsContainErrors(
const std::vector<Diag> &Diagnostics) {
61 for (
auto D : Diagnostics) {
63 D.Severity == DiagnosticsEngine::Fatal)
69 class ErrorCheckingCallbacks :
public ClangdServer::Callbacks {
71 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
72 std::vector<Diag> Diagnostics)
override {
73 bool HadError = diagsContainErrors(Diagnostics);
74 std::lock_guard<std::mutex> Lock(Mutex);
75 HadErrorInLastDiags = HadError;
78 bool hadErrorInLastDiags() {
79 std::lock_guard<std::mutex> Lock(Mutex);
80 return HadErrorInLastDiags;
85 bool HadErrorInLastDiags =
false;
90 class MultipleErrorCheckingCallbacks :
public ClangdServer::Callbacks {
92 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
93 std::vector<Diag> Diagnostics)
override {
94 bool HadError = diagsContainErrors(Diagnostics);
96 std::lock_guard<std::mutex> Lock(Mutex);
97 LastDiagsHadError[
File] = HadError;
103 std::vector<std::pair<Path, bool>> filesWithDiags()
const {
104 std::vector<std::pair<Path, bool>> Result;
105 std::lock_guard<std::mutex> Lock(Mutex);
106 for (
const auto &It : LastDiagsHadError)
107 Result.emplace_back(std::string(It.first()), It.second);
112 std::lock_guard<std::mutex> Lock(Mutex);
113 LastDiagsHadError.clear();
117 mutable std::mutex Mutex;
118 llvm::StringMap<bool> LastDiagsHadError;
122 std::string replacePtrsInDump(std::string
const &Dump) {
123 llvm::Regex RE(
"0x[0-9a-fA-F]+");
124 llvm::SmallVector<llvm::StringRef, 1> Matches;
125 llvm::StringRef Pending = Dump;
128 while (RE.match(Pending, &Matches)) {
129 assert(Matches.size() == 1 &&
"Exactly one match expected");
130 auto MatchPos = Matches[0].data() - Pending.data();
132 Result += Pending.take_front(MatchPos);
133 Pending = Pending.drop_front(MatchPos + Matches[0].size());
140 std::string dumpASTWithoutMemoryLocs(ClangdServer &Server,
PathRef File) {
141 auto DumpWithMemLocs =
runDumpAST(Server, File);
142 return replacePtrsInDump(DumpWithMemLocs);
145 class ClangdVFSTest :
public ::testing::Test {
147 std::string parseSourceAndDumpAST(
148 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
149 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
150 bool ExpectErrors =
false) {
152 ErrorCheckingCallbacks DiagConsumer;
153 MockCompilationDatabase CDB;
155 for (
const auto &FileWithContents : ExtraFiles)
157 std::string(FileWithContents.second);
159 auto SourceFilename =
testPath(SourceFileRelPath);
160 Server.addDocument(SourceFilename, SourceContents);
161 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
162 EXPECT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
163 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
168 TEST_F(ClangdVFSTest, Parse) {
172 auto Empty = parseSourceAndDumpAST(
"foo.cpp",
"", {});
173 auto OneDecl = parseSourceAndDumpAST(
"foo.cpp",
"int a;", {});
174 auto SomeDecls = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;", {});
175 EXPECT_NE(
Empty, OneDecl);
176 EXPECT_NE(
Empty, SomeDecls);
177 EXPECT_NE(SomeDecls, OneDecl);
179 auto Empty2 = parseSourceAndDumpAST(
"foo.cpp",
"");
180 auto OneDecl2 = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
181 auto SomeDecls2 = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
182 EXPECT_EQ(
Empty, Empty2);
183 EXPECT_EQ(OneDecl, OneDecl2);
184 EXPECT_EQ(SomeDecls, SomeDecls2);
187 TEST_F(ClangdVFSTest, ParseWithHeader) {
188 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {},
190 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {{
"foo.h",
""}},
193 const auto SourceContents = R
"cpp(
197 parseSourceAndDumpAST("foo.cpp", SourceContents, {{
"foo.h",
""}},
199 parseSourceAndDumpAST(
"foo.cpp", SourceContents, {{
"foo.h",
"int a;"}},
203 TEST_F(ClangdVFSTest, Reparse) {
205 ErrorCheckingCallbacks DiagConsumer;
206 MockCompilationDatabase CDB;
209 const auto SourceContents = R
"cpp(
217 FS.
Files[FooCpp] = SourceContents;
219 Server.addDocument(FooCpp, SourceContents);
220 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
221 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
222 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
224 Server.addDocument(FooCpp,
"");
225 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
226 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
227 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
229 Server.addDocument(FooCpp, SourceContents);
230 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
231 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
232 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
234 EXPECT_EQ(DumpParse1, DumpParse2);
235 EXPECT_NE(DumpParse1, DumpParseEmpty);
238 TEST_F(ClangdVFSTest, ReparseOnHeaderChange) {
240 ErrorCheckingCallbacks DiagConsumer;
241 MockCompilationDatabase CDB;
244 const auto SourceContents = R
"cpp(
253 FS.
Files[FooCpp] = SourceContents;
255 Server.addDocument(FooCpp, SourceContents);
256 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
257 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
258 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
261 Server.addDocument(FooCpp, SourceContents);
262 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
263 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
264 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
267 Server.addDocument(FooCpp, SourceContents);
268 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
269 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
270 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
272 EXPECT_EQ(DumpParse1, DumpParse2);
273 EXPECT_NE(DumpParse1, DumpParseDifferent);
276 TEST_F(ClangdVFSTest, PropagatesContexts) {
277 static Key<int> Secret;
278 struct ContextReadingFS :
public ThreadsafeFS {
282 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
287 struct Callbacks :
public ClangdServer::Callbacks {
288 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
289 std::vector<Diag> Diagnostics)
override {
294 MockCompilationDatabase CDB;
299 WithContextValue Entrypoint(Secret, 42);
300 Server.addDocument(
testPath(
"foo.cpp"),
"void main(){}");
302 ASSERT_TRUE(Server.blockUntilIdleForTest());
303 EXPECT_EQ(
FS.Got, 42);
304 EXPECT_EQ(Callbacks.Got, 42);
307 TEST(ClangdServerTest, RespectsConfig) {
309 Annotations Example(R
"cpp(
318 class ConfigProvider :
public config::Provider {
319 std::vector<config::CompiledFragment>
320 getFragments(
const config::Params &,
323 F.If.PathMatch.emplace_back(
".*foo.cc");
324 F.CompileFlags.Add.emplace_back(
"-DFOO=1");
325 return {std::move(F).compile(DC)};
330 Opts.ConfigProvider = &CfgProvider;
331 OverlayCDB CDB(
nullptr, {},
334 ClangdServer Server(CDB,
FS, Opts);
336 Server.addDocument(
testPath(
"foo.cc"), Example.code());
338 ASSERT_TRUE(
bool(Result)) << Result.takeError();
339 ASSERT_THAT(*Result, SizeIs(1));
340 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
342 Server.addDocument(
testPath(
"bar.cc"), Example.code());
344 ASSERT_TRUE(
bool(Result)) << Result.takeError();
345 ASSERT_THAT(*Result, SizeIs(1));
346 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
349 TEST_F(ClangdVFSTest, PropagatesVersion) {
350 MockCompilationDatabase CDB;
352 struct Callbacks :
public ClangdServer::Callbacks {
353 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
354 std::vector<Diag> Diagnostics)
override {
357 std::string Got =
"";
363 EXPECT_EQ(Callbacks.Got,
"42");
368 TEST_F(ClangdVFSTest, SearchLibDir) {
371 ErrorCheckingCallbacks DiagConsumer;
372 MockCompilationDatabase CDB;
373 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
374 {
"-xc++",
"-target",
"x86_64-linux-unknown",
375 "-m64",
"--gcc-toolchain=/randomusr",
376 "-stdlib=libstdc++"});
380 SmallString<8> Version(
"4.9.3");
383 SmallString<64> LibDir(
"/randomusr/lib/gcc/x86_64-linux-gnu");
384 llvm::sys::path::append(LibDir, Version);
388 SmallString<64> DummyLibFile;
389 llvm::sys::path::append(DummyLibFile, LibDir,
"64",
"crtbegin.o");
392 SmallString<64> IncludeDir(
"/randomusr/include/c++");
393 llvm::sys::path::append(IncludeDir, Version);
395 SmallString<64> StringPath;
396 llvm::sys::path::append(StringPath, IncludeDir,
"string");
397 FS.
Files[StringPath] =
"class mock_string {};";
400 const auto SourceContents = R
"cpp(
404 FS.Files[FooCpp] = SourceContents;
407 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
409 const auto SourceContentsWithError = R
"cpp(
414 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
416 #endif // LLVM_ON_UNIX
418 TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
420 ErrorCheckingCallbacks DiagConsumer;
421 MockCompilationDatabase CDB;
425 const auto SourceContents1 = R
"cpp(
429 const auto SourceContents2 = R
"cpp(
437 CDB.ExtraClangFlags = {
"-xc"};
439 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
441 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
444 CDB.ExtraClangFlags = {
"-xc++"};
446 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
449 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
451 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
454 TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) {
456 ErrorCheckingCallbacks DiagConsumer;
457 MockCompilationDatabase CDB;
461 const auto SourceContents = R
"cpp(
466 int main() { return 0; }
471 CDB.ExtraClangFlags = {
"-DWITH_ERROR"};
473 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
476 CDB.ExtraClangFlags = {};
478 ASSERT_TRUE(Server.blockUntilIdleForTest());
479 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
482 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
486 TEST_F(ClangdVFSTest, ReparseOpenedFiles) {
487 Annotations FooSource(R
"cpp(
489 static void $one[[bob]]() {}
491 static void $two[[bob]]() {}
494 int main () { bo^b (); return 0; }
497 Annotations BarSource(R"cpp(
503 Annotations BazSource(R"cpp(
508 MockCompilationDatabase CDB;
509 MultipleErrorCheckingCallbacks DiagConsumer;
520 CDB.ExtraClangFlags = {
"-DMACRO=1"};
521 Server.addDocument(FooCpp, FooSource.code());
522 Server.addDocument(BarCpp, BarSource.code());
523 Server.addDocument(BazCpp, BazSource.code());
524 ASSERT_TRUE(Server.blockUntilIdleForTest());
526 EXPECT_THAT(DiagConsumer.filesWithDiags(),
527 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
true),
528 Pair(BazCpp,
false)));
531 EXPECT_TRUE(
bool(Locations));
532 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"one"))));
535 CDB.ExtraClangFlags.clear();
536 DiagConsumer.clear();
537 Server.removeDocument(BazCpp);
538 Server.addDocument(FooCpp, FooSource.code());
539 Server.addDocument(BarCpp, BarSource.code());
540 ASSERT_TRUE(Server.blockUntilIdleForTest());
542 EXPECT_THAT(DiagConsumer.filesWithDiags(),
543 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
false)));
546 EXPECT_TRUE(
bool(Locations));
547 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"two"))));
550 MATCHER_P4(Stats,
Name, UsesMemory, PreambleBuilds, ASTBuilds,
"") {
551 return arg.first() ==
Name && (arg.second.UsedBytes != 0) == UsesMemory &&
552 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
553 std::tie(PreambleBuilds, ASTBuilds);
556 TEST_F(ClangdVFSTest, FileStats) {
558 ErrorCheckingCallbacks DiagConsumer;
559 MockCompilationDatabase CDB;
563 const auto SourceContents = R
"cpp(
573 EXPECT_THAT(Server.fileStats(), IsEmpty());
575 Server.addDocument(FooCpp, SourceContents);
576 Server.addDocument(BarCpp, SourceContents);
577 ASSERT_TRUE(Server.blockUntilIdleForTest());
579 EXPECT_THAT(Server.fileStats(),
580 UnorderedElementsAre(Stats(FooCpp,
true, 1, 1),
581 Stats(BarCpp,
true, 1, 1)));
583 Server.removeDocument(FooCpp);
584 ASSERT_TRUE(Server.blockUntilIdleForTest());
585 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp,
true, 1, 1)));
587 Server.removeDocument(BarCpp);
588 ASSERT_TRUE(Server.blockUntilIdleForTest());
589 EXPECT_THAT(Server.fileStats(), IsEmpty());
592 TEST_F(ClangdVFSTest, InvalidCompileCommand) {
594 ErrorCheckingCallbacks DiagConsumer;
595 MockCompilationDatabase CDB;
603 CDB.ExtraClangFlags.push_back(FooCpp);
608 EXPECT_EQ(
runDumpAST(Server, FooCpp),
"<no-ast>");
612 clangd::RenameOptions()));
616 clangd::CodeCompleteOptions()))
622 class ClangdThreadingTest :
public ClangdVFSTest {};
624 TEST_F(ClangdThreadingTest, StressTest) {
626 static const unsigned FilesCount = 5;
627 const unsigned RequestsCount = 500;
631 const unsigned BlockingRequestInterval = 40;
633 const auto SourceContentsWithoutErrors = R
"cpp(
640 const auto SourceContentsWithErrors = R
"cpp(
650 unsigned MaxLineForFileRequests = 7;
651 unsigned MaxColumnForFileRequests = 10;
655 for (
unsigned I = 0; I < FilesCount; ++I) {
656 std::string
Name = std::string(
"Foo") + std::to_string(I) +
".cpp";
662 unsigned HitsWithoutErrors = 0;
663 unsigned HitsWithErrors = 0;
664 bool HadErrorsInLastDiags =
false;
667 class TestDiagConsumer :
public ClangdServer::Callbacks {
669 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
671 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
672 std::vector<Diag> Diagnostics)
override {
673 StringRef FileIndexStr = llvm::sys::path::stem(
File);
674 ASSERT_TRUE(FileIndexStr.consume_front(
"Foo"));
676 unsigned long FileIndex = std::stoul(FileIndexStr.str());
678 bool HadError = diagsContainErrors(Diagnostics);
680 std::lock_guard<std::mutex> Lock(Mutex);
682 Stats[FileIndex].HitsWithErrors++;
684 Stats[FileIndex].HitsWithoutErrors++;
685 Stats[FileIndex].HadErrorsInLastDiags = HadError;
688 std::vector<FileStat> takeFileStats() {
689 std::lock_guard<std::mutex> Lock(Mutex);
690 return std::move(Stats);
695 std::vector<FileStat> Stats;
698 struct RequestStats {
699 unsigned RequestsWithoutErrors = 0;
700 unsigned RequestsWithErrors = 0;
701 bool LastContentsHadErrors =
false;
702 bool FileIsRemoved =
true;
705 std::vector<RequestStats> ReqStats;
706 ReqStats.reserve(FilesCount);
707 for (
unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
708 ReqStats.emplace_back();
710 TestDiagConsumer DiagConsumer;
712 MockCompilationDatabase CDB;
716 std::random_device RandGen;
718 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
721 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
723 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
724 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
727 auto UpdateStatsOnAddDocument = [&](
unsigned FileIndex,
bool HadErrors) {
728 auto &Stats = ReqStats[FileIndex];
731 ++Stats.RequestsWithErrors;
733 ++Stats.RequestsWithoutErrors;
734 Stats.LastContentsHadErrors = HadErrors;
735 Stats.FileIsRemoved =
false;
738 auto UpdateStatsOnRemoveDocument = [&](
unsigned FileIndex) {
739 auto &Stats = ReqStats[FileIndex];
741 Stats.FileIsRemoved =
true;
744 auto AddDocument = [&](
unsigned FileIndex,
bool SkipCache) {
745 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
747 ShouldHaveErrors ? SourceContentsWithErrors
748 : SourceContentsWithoutErrors);
749 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
753 auto AddDocumentRequest = [&]() {
754 unsigned FileIndex = FileIndexDist(RandGen);
755 AddDocument(FileIndex,
false);
758 auto ForceReparseRequest = [&]() {
759 unsigned FileIndex = FileIndexDist(RandGen);
760 AddDocument(FileIndex,
true);
763 auto RemoveDocumentRequest = [&]() {
764 unsigned FileIndex = FileIndexDist(RandGen);
766 if (ReqStats[FileIndex].FileIsRemoved)
767 AddDocument(FileIndex,
false);
769 Server.removeDocument(
FilePaths[FileIndex]);
770 UpdateStatsOnRemoveDocument(FileIndex);
773 auto CodeCompletionRequest = [&]() {
774 unsigned FileIndex = FileIndexDist(RandGen);
776 if (ReqStats[FileIndex].FileIsRemoved)
777 AddDocument(FileIndex,
false);
789 clangd::CodeCompleteOptions()));
792 auto LocateSymbolRequest = [&]() {
793 unsigned FileIndex = FileIndexDist(RandGen);
795 if (ReqStats[FileIndex].FileIsRemoved)
796 AddDocument(FileIndex,
false);
805 std::vector<std::function<void()>> AsyncRequests = {
806 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
807 std::vector<std::function<void()>> BlockingRequests = {
808 CodeCompletionRequest, LocateSymbolRequest};
811 std::uniform_int_distribution<int> AsyncRequestIndexDist(
812 0, AsyncRequests.size() - 1);
813 std::uniform_int_distribution<int> BlockingRequestIndexDist(
814 0, BlockingRequests.size() - 1);
815 for (
unsigned I = 1; I <= RequestsCount; ++I) {
816 if (I % BlockingRequestInterval != 0) {
818 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
819 AsyncRequests[RequestIndex]();
822 auto RequestIndex = BlockingRequestIndexDist(RandGen);
823 BlockingRequests[RequestIndex]();
826 ASSERT_TRUE(Server.blockUntilIdleForTest());
830 std::vector<FileStat> Stats = DiagConsumer.takeFileStats();
831 for (
unsigned I = 0; I < FilesCount; ++I) {
832 if (!ReqStats[I].FileIsRemoved) {
833 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
834 ReqStats[I].LastContentsHadErrors);
837 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
838 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
842 TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
843 class NoConcurrentAccessDiagConsumer :
public ClangdServer::Callbacks {
845 std::atomic<int> Count = {0};
847 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
848 : StartSecondReparse(std::move(StartSecondReparse)) {}
850 void onDiagnosticsReady(
PathRef, llvm::StringRef,
851 std::vector<Diag>)
override {
853 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
854 ASSERT_TRUE(Lock.owns_lock())
855 <<
"Detected concurrent onDiagnosticsReady calls for the same file.";
860 FirstRequest =
false;
861 StartSecondReparse.set_value();
863 std::this_thread::sleep_for(std::chrono::milliseconds(50));
869 bool FirstRequest =
true;
870 std::promise<void> StartSecondReparse;
873 const auto SourceContentsWithoutErrors = R
"cpp(
880 const auto SourceContentsWithErrors = R
"cpp(
891 std::promise<void> StartSecondPromise;
892 std::future<void> StartSecond = StartSecondPromise.get_future();
894 NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise));
895 MockCompilationDatabase CDB;
897 Server.addDocument(FooCpp, SourceContentsWithErrors);
899 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
900 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
901 ASSERT_EQ(DiagConsumer.Count, 2);
904 TEST_F(ClangdVFSTest, FormatCode) {
906 ErrorCheckingCallbacks DiagConsumer;
907 MockCompilationDatabase CDB;
911 std::string
Code = R
"cpp(
927 EXPECT_TRUE(static_cast<bool>(Replaces));
928 auto Changed = tooling::applyAllReplacements(
Code, *Replaces);
929 EXPECT_TRUE(static_cast<bool>(Changed));
933 TEST_F(ClangdVFSTest, ChangedHeaderFromISystem) {
935 ErrorCheckingCallbacks DiagConsumer;
936 MockCompilationDatabase CDB;
939 auto SourcePath =
testPath(
"source/foo.cpp");
940 auto HeaderPath =
testPath(
"headers/foo.h");
941 FS.
Files[HeaderPath] =
"struct X { int bar; };";
942 Annotations
Code(R
"cpp(
948 CDB.ExtraClangFlags.push_back("-xc++");
949 CDB.ExtraClangFlags.push_back(
"-isystem" +
testPath(
"headers"));
953 clangd::CodeCompleteOptions()))
958 FS.
Files[HeaderPath] =
"struct X { int bar; int baz; };";
961 clangd::CodeCompleteOptions()))
972 TEST(ClangdTests, PreambleVFSStatCache) {
973 class StatRecordingFS :
public ThreadsafeFS {
974 llvm::StringMap<unsigned> &CountStats;
978 llvm::StringMap<std::string> Files;
980 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
981 : CountStats(CountStats) {}
984 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
985 class StatRecordingVFS :
public llvm::vfs::ProxyFileSystem {
987 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem>
FS,
988 llvm::StringMap<unsigned> &CountStats)
989 : ProxyFileSystem(std::move(
FS)), CountStats(CountStats) {}
991 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
992 openFileForRead(
const Twine &
Path)
override {
993 ++CountStats[llvm::sys::path::filename(
Path.str())];
994 return ProxyFileSystem::openFileForRead(
Path);
996 llvm::ErrorOr<llvm::vfs::Status> status(
const Twine &
Path)
override {
997 ++CountStats[llvm::sys::path::filename(
Path.str())];
998 return ProxyFileSystem::status(
Path);
1002 llvm::StringMap<unsigned> &CountStats;
1005 return IntrusiveRefCntPtr<StatRecordingVFS>(
1006 new StatRecordingVFS(
buildTestFS(Files), CountStats));
1010 llvm::StringMap<unsigned> CountStats;
1011 StatRecordingFS
FS(CountStats);
1012 ErrorCheckingCallbacks DiagConsumer;
1013 MockCompilationDatabase CDB;
1016 auto SourcePath =
testPath(
"foo.cpp");
1017 auto HeaderPath =
testPath(
"foo.h");
1018 FS.
Files[HeaderPath] =
"struct TestSym {};";
1019 Annotations
Code(R
"cpp(
1028 unsigned Before = CountStats[
"foo.h"];
1029 EXPECT_GT(Before, 0u);
1031 clangd::CodeCompleteOptions()))
1033 EXPECT_EQ(CountStats[
"foo.h"], Before);
1034 EXPECT_THAT(Completions,
1039 TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) {
1041 ErrorCheckingCallbacks DiagConsumer;
1042 MockCompilationDatabase CDB;
1046 Annotations
Code(R
"cpp(
1047 namespace ns { int xyz; }
1054 auto Opts = clangd::CodeCompleteOptions();
1058 CDB.ExtraClangFlags = {
"yolo.cc"};
1059 Server.addDocument(FooCpp,
Code.code());
1060 ASSERT_TRUE(Server.blockUntilIdleForTest());
1062 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1064 EXPECT_THAT(Res.Completions,
1069 CDB.ExtraClangFlags = {
"-std=c++11"};
1070 Server.addDocument(FooCpp,
Code.code());
1071 ASSERT_TRUE(Server.blockUntilIdleForTest());
1085 TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) {
1087 ErrorCheckingCallbacks DiagConsumer;
1089 class DelayedCompilationDatabase :
public GlobalCompilationDatabase {
1091 DelayedCompilationDatabase(Notification &CanReturnCommand)
1092 : CanReturnCommand(CanReturnCommand) {}
1094 llvm::Optional<tooling::CompileCommand>
1098 CanReturnCommand.wait();
1100 std::vector<std::string>
CommandLine = {
"clangd",
"-ffreestanding",
1102 return {tooling::CompileCommand(llvm::sys::path::parent_path(
File),
1106 std::vector<std::string> ExtraClangFlags;
1109 Notification &CanReturnCommand;
1112 Notification CanReturnCommand;
1113 DelayedCompilationDatabase CDB(CanReturnCommand);
1117 Annotations
Code(R
"cpp(
1118 namespace ns { int xyz; }
1124 Server.addDocument(FooCpp, Code.code());
1128 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1129 auto Opts = clangd::CodeCompleteOptions();
1133 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1135 CanReturnCommand.notify();
1136 ASSERT_TRUE(Server.blockUntilIdleForTest());
1138 clangd::CodeCompleteOptions()))
1146 #if !defined(__has_feature) || !__has_feature(address_sanitizer)
1147 TEST_F(ClangdVFSTest, TestStackOverflow) {
1149 ErrorCheckingCallbacks DiagConsumer;
1150 MockCompilationDatabase CDB;
1153 const char *SourceContents = R
"cpp(
1154 constexpr int foo() { return foo(); }
1155 static_assert(foo());
1159 FS.
Files[FooCpp] = SourceContents;
1161 Server.addDocument(FooCpp, SourceContents);
1162 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
1165 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());