24#include "clang/Config/config.h"
25#include "clang/Sema/CodeCompleteConsumer.h"
26#include "clang/Tooling/ArgumentsAdjusters.h"
27#include "clang/Tooling/Core/Replacement.h"
28#include "llvm/ADT/ArrayRef.h"
29#include "llvm/ADT/SmallVector.h"
30#include "llvm/ADT/StringMap.h"
31#include "llvm/ADT/StringRef.h"
32#include "llvm/Config/llvm-config.h"
33#include "llvm/Support/Allocator.h"
34#include "llvm/Support/Error.h"
35#include "llvm/Support/Path.h"
36#include "llvm/Support/Regex.h"
37#include "llvm/Support/VirtualFileSystem.h"
38#include "llvm/Testing/Support/Error.h"
39#include "gmock/gmock.h"
40#include "gtest/gtest.h"
55using ::testing::AllOf;
56using ::testing::ElementsAre;
57using ::testing::Field;
58using ::testing::IsEmpty;
60using ::testing::SizeIs;
61using ::testing::UnorderedElementsAre;
64 return arg.PreferredDeclaration ==
68bool diagsContainErrors(
const std::vector<Diag> &
Diagnostics) {
70 if (D.Severity == DiagnosticsEngine::Error ||
71 D.Severity == DiagnosticsEngine::Fatal)
77class ErrorCheckingCallbacks :
public ClangdServer::Callbacks {
79 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
82 std::lock_guard<std::mutex> Lock(Mutex);
83 HadErrorInLastDiags = HadError;
86 bool hadErrorInLastDiags() {
87 std::lock_guard<std::mutex> Lock(Mutex);
88 return HadErrorInLastDiags;
93 bool HadErrorInLastDiags =
false;
98class MultipleErrorCheckingCallbacks :
public ClangdServer::Callbacks {
100 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
104 std::lock_guard<std::mutex> Lock(Mutex);
105 LastDiagsHadError[
File] = HadError;
111 std::vector<std::pair<Path, bool>> filesWithDiags()
const {
112 std::vector<std::pair<Path, bool>> Result;
113 std::lock_guard<std::mutex> Lock(Mutex);
114 for (
const auto &It : LastDiagsHadError)
115 Result.emplace_back(std::string(It.first()), It.second);
120 std::lock_guard<std::mutex> Lock(Mutex);
121 LastDiagsHadError.clear();
125 mutable std::mutex Mutex;
126 llvm::StringMap<bool> LastDiagsHadError;
130std::string replacePtrsInDump(std::string
const &Dump) {
131 llvm::Regex RE(
"0x[0-9a-fA-F]+");
132 llvm::SmallVector<llvm::StringRef, 1> Matches;
133 llvm::StringRef Pending = Dump;
136 while (RE.match(Pending, &Matches)) {
137 assert(Matches.size() == 1 &&
"Exactly one match expected");
138 auto MatchPos = Matches[0].data() - Pending.data();
140 Result += Pending.take_front(MatchPos);
141 Pending = Pending.drop_front(MatchPos + Matches[0].size());
151 Server.customAction(
File,
"DumpAST", [&](llvm::Expected<InputsAndAST>
AST) {
153 llvm::raw_string_ostream ResultOS(Result);
154 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS,
true);
156 llvm::consumeError(
AST.takeError());
165std::string dumpASTWithoutMemoryLocs(ClangdServer &Server,
PathRef File) {
169std::string parseSourceAndDumpAST(
170 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
171 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
172 bool ExpectErrors =
false) {
175 MockCompilationDatabase CDB;
177 for (
const auto &FileWithContents : ExtraFiles)
178 FS.Files[
testPath(FileWithContents.first)] =
179 std::string(FileWithContents.second);
181 auto SourceFilename =
testPath(SourceFileRelPath);
182 Server.addDocument(SourceFilename, SourceContents);
183 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
184 EXPECT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
185 EXPECT_EQ(ExpectErrors,
DiagConsumer.hadErrorInLastDiags());
189TEST(ClangdServerTest, Parse) {
193 auto Empty = parseSourceAndDumpAST(
"foo.cpp",
"");
194 auto OneDecl = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
195 auto SomeDecls = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
196 EXPECT_NE(
Empty, OneDecl);
197 EXPECT_NE(
Empty, SomeDecls);
198 EXPECT_NE(SomeDecls, OneDecl);
200 auto Empty2 = parseSourceAndDumpAST(
"foo.cpp",
"");
201 auto OneDecl2 = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
202 auto SomeDecls2 = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
203 EXPECT_EQ(
Empty, Empty2);
204 EXPECT_EQ(OneDecl, OneDecl2);
205 EXPECT_EQ(SomeDecls, SomeDecls2);
208TEST(ClangdServerTest, ParseWithHeader) {
209 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {},
211 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {{
"foo.h",
""}},
214 const auto *SourceContents = R
"cpp(
218 parseSourceAndDumpAST("foo.cpp", SourceContents, {{
"foo.h",
""}},
220 parseSourceAndDumpAST(
"foo.cpp", SourceContents, {{
"foo.h",
"int a;"}},
224TEST(ClangdServerTest, Reparse) {
227 MockCompilationDatabase CDB;
230 const auto *SourceContents = R
"cpp(
237 FS.Files[
testPath(
"foo.h")] =
"int a;";
238 FS.Files[FooCpp] = SourceContents;
240 Server.addDocument(FooCpp, SourceContents);
241 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
242 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
245 Server.addDocument(FooCpp,
"");
246 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
247 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
250 Server.addDocument(FooCpp, SourceContents);
251 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
252 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
255 EXPECT_EQ(DumpParse1, DumpParse2);
256 EXPECT_NE(DumpParse1, DumpParseEmpty);
259TEST(ClangdServerTest, ReparseOnHeaderChange) {
262 MockCompilationDatabase CDB;
265 const auto *SourceContents = R
"cpp(
273 FS.Files[FooH] =
"int a;";
274 FS.Files[FooCpp] = SourceContents;
276 Server.addDocument(FooCpp, SourceContents);
277 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
278 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
282 Server.addDocument(FooCpp, SourceContents);
283 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
284 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
287 FS.Files[FooH] =
"int a;";
288 Server.addDocument(FooCpp, SourceContents);
289 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
290 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
293 EXPECT_EQ(DumpParse1, DumpParse2);
294 EXPECT_NE(DumpParse1, DumpParseDifferent);
297TEST(ClangdServerTest, PropagatesContexts) {
298 static Key<int> Secret;
299 struct ContextReadingFS :
public ThreadsafeFS {
303 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
304 Got = Context::current().getExisting(Secret);
308 struct Callbacks :
public ClangdServer::Callbacks {
309 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
311 Got = Context::current().getExisting(Secret);
315 MockCompilationDatabase CDB;
320 WithContextValue Entrypoint(Secret, 42);
321 Server.addDocument(
testPath(
"foo.cpp"),
"void main(){}");
323 ASSERT_TRUE(Server.blockUntilIdleForTest());
324 EXPECT_EQ(FS.Got, 42);
325 EXPECT_EQ(Callbacks.Got, 42);
328TEST(ClangdServerTest, RespectsConfig) {
330 Annotations Example(R
"cpp(
339 class ConfigProvider :
public config::Provider {
340 std::vector<config::CompiledFragment>
341 getFragments(
const config::Params &,
342 config::DiagnosticCallback DC)
const override {
344 F.If.PathMatch.emplace_back(
".*foo.cc");
345 F.CompileFlags.Add.emplace_back(
"-DFOO=1");
346 return {std::move(F).compile(DC)};
351 Opts.ContextProvider =
353 OverlayCDB CDB(
nullptr, {},
356 ClangdServer Server(CDB, FS, Opts);
358 Server.addDocument(
testPath(
"foo.cc"), Example.code());
360 ASSERT_TRUE(
bool(Result)) << Result.takeError();
361 ASSERT_THAT(*Result, SizeIs(1));
362 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
364 Server.addDocument(
testPath(
"bar.cc"), Example.code());
366 ASSERT_TRUE(
bool(Result)) << Result.takeError();
367 ASSERT_THAT(*Result, SizeIs(1));
368 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
371TEST(ClangdServerTest, PropagatesVersion) {
372 MockCompilationDatabase CDB;
374 struct Callbacks :
public ClangdServer::Callbacks {
375 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
379 std::string Got =
"";
385 EXPECT_EQ(Callbacks.Got,
"42");
390TEST(ClangdServerTest, SearchLibDir) {
394 MockCompilationDatabase CDB;
395 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
396 {
"-xc++",
"--target=x86_64-unknown-linux-gnu",
397 "-m64",
"--gcc-toolchain=/randomusr",
398 "-stdlib=libstdc++"});
402 SmallString<8> Version(
"4.9.3");
405 SmallString<64> LibDir(
"/randomusr/lib/gcc/x86_64-linux-gnu");
406 llvm::sys::path::append(LibDir, Version);
410 SmallString<64> MockLibFile;
411 llvm::sys::path::append(MockLibFile, LibDir,
"64",
"crtbegin.o");
412 FS.Files[MockLibFile] =
"";
414 SmallString<64> IncludeDir(
"/randomusr/include/c++");
415 llvm::sys::path::append(IncludeDir, Version);
417 SmallString<64> StringPath;
418 llvm::sys::path::append(StringPath, IncludeDir,
"string");
419 FS.Files[StringPath] =
"class mock_string {};";
422 const auto *SourceContents = R
"cpp(
426 FS.Files[FooCpp] = SourceContents;
431 const auto *SourceContentsWithError = R
"cpp(
440TEST(ClangdServerTest, ForceReparseCompileCommand) {
443 MockCompilationDatabase CDB;
447 const auto *SourceContents1 = R
"cpp(
451 const auto *SourceContents2 = R
"cpp(
456 FS.Files[FooCpp] = "";
459 CDB.ExtraClangFlags = {
"-xc"};
466 CDB.ExtraClangFlags = {
"-xc++"};
476TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
479 MockCompilationDatabase CDB;
483 const auto *SourceContents = R
"cpp(
488int main() { return 0; }
490 FS.Files[FooCpp] = "";
493 CDB.ExtraClangFlags = {
"-DWITH_ERROR"};
498 CDB.ExtraClangFlags = {};
500 ASSERT_TRUE(Server.blockUntilIdleForTest());
508TEST(ClangdServerTest, ReparseOpenedFiles) {
509 Annotations FooSource(R
"cpp(
511static void $one[[bob]]() {}
513static void $two[[bob]]() {}
516int main () { bo^b (); return 0; }
519 Annotations BarSource(R"cpp(
525 Annotations BazSource(R"cpp(
530 MockCompilationDatabase CDB;
538 FS.Files[FooCpp] =
"";
539 FS.Files[BarCpp] =
"";
540 FS.Files[BazCpp] =
"";
542 CDB.ExtraClangFlags = {
"-DMACRO=1"};
543 Server.addDocument(FooCpp, FooSource.code());
544 Server.addDocument(BarCpp, BarSource.code());
545 Server.addDocument(BazCpp, BazSource.code());
546 ASSERT_TRUE(Server.blockUntilIdleForTest());
549 UnorderedElementsAre(
Pair(FooCpp,
false),
Pair(BarCpp,
true),
550 Pair(BazCpp,
false)));
553 EXPECT_TRUE(
bool(Locations));
554 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"one"))));
557 CDB.ExtraClangFlags.clear();
559 Server.removeDocument(BazCpp);
560 Server.addDocument(FooCpp, FooSource.code());
561 Server.addDocument(BarCpp, BarSource.code());
562 ASSERT_TRUE(Server.blockUntilIdleForTest());
565 UnorderedElementsAre(
Pair(FooCpp,
false),
Pair(BarCpp,
false)));
568 EXPECT_TRUE(
bool(Locations));
569 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"two"))));
572MATCHER_P4(Stats,
Name, UsesMemory, PreambleBuilds, ASTBuilds,
"") {
573 return arg.first() ==
Name &&
574 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
576 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
577 std::tie(PreambleBuilds, ASTBuilds);
580TEST(ClangdServerTest, FileStats) {
583 MockCompilationDatabase CDB;
587 const auto *SourceContents = R
"cpp(
594 FS.Files[FooCpp] =
"";
595 FS.Files[BarCpp] =
"";
597 EXPECT_THAT(Server.fileStats(), IsEmpty());
599 Server.addDocument(FooCpp, SourceContents);
600 Server.addDocument(BarCpp, SourceContents);
601 ASSERT_TRUE(Server.blockUntilIdleForTest());
603 EXPECT_THAT(Server.fileStats(),
604 UnorderedElementsAre(Stats(FooCpp,
true, 1, 1),
605 Stats(BarCpp,
true, 1, 1)));
607 Server.removeDocument(FooCpp);
608 ASSERT_TRUE(Server.blockUntilIdleForTest());
609 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp,
true, 1, 1)));
611 Server.removeDocument(BarCpp);
612 ASSERT_TRUE(Server.blockUntilIdleForTest());
613 EXPECT_THAT(Server.fileStats(), IsEmpty());
616TEST(ClangdServerTest, InvalidCompileCommand) {
619 MockCompilationDatabase CDB;
625 CDB.ExtraClangFlags.push_back(
"-###");
630 EXPECT_EQ(
dumpAST(Server, FooCpp),
"<no-ast>");
634 clangd::RenameOptions()));
639 clangd::CodeCompleteOptions()))
645TEST(ClangdThreadingTest, StressTest) {
647 static const unsigned FilesCount = 5;
648 const unsigned RequestsCount = 500;
652 const unsigned BlockingRequestInterval = 40;
654 const auto *SourceContentsWithoutErrors = R
"cpp(
661 const auto *SourceContentsWithErrors = R
"cpp(
671 unsigned MaxLineForFileRequests = 7;
672 unsigned MaxColumnForFileRequests = 10;
674 std::vector<std::string> FilePaths;
676 for (
unsigned I = 0; I < FilesCount; ++I) {
677 std::string
Name = std::string(
"Foo") + std::to_string(I) +
".cpp";
683 unsigned HitsWithoutErrors = 0;
684 unsigned HitsWithErrors = 0;
685 bool HadErrorsInLastDiags =
false;
688 class TestDiagConsumer :
public ClangdServer::Callbacks {
690 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
692 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
694 StringRef FileIndexStr = llvm::sys::path::stem(File);
695 ASSERT_TRUE(FileIndexStr.consume_front(
"Foo"));
697 unsigned long FileIndex = std::stoul(FileIndexStr.str());
701 std::lock_guard<std::mutex> Lock(Mutex);
703 Stats[FileIndex].HitsWithErrors++;
705 Stats[FileIndex].HitsWithoutErrors++;
706 Stats[FileIndex].HadErrorsInLastDiags = HadError;
709 std::vector<FileStat> takeFileStats() {
710 std::lock_guard<std::mutex> Lock(Mutex);
711 return std::move(Stats);
716 std::vector<FileStat> Stats;
719 struct RequestStats {
720 unsigned RequestsWithoutErrors = 0;
721 unsigned RequestsWithErrors = 0;
722 bool LastContentsHadErrors =
false;
723 bool FileIsRemoved =
true;
726 std::vector<RequestStats> ReqStats;
727 ReqStats.reserve(FilesCount);
728 for (
unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
729 ReqStats.emplace_back();
733 MockCompilationDatabase CDB;
737 std::random_device RandGen;
739 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
742 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
744 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
745 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
748 auto UpdateStatsOnAddDocument = [&](
unsigned FileIndex,
bool HadErrors) {
749 auto &Stats = ReqStats[FileIndex];
752 ++Stats.RequestsWithErrors;
754 ++Stats.RequestsWithoutErrors;
755 Stats.LastContentsHadErrors = HadErrors;
756 Stats.FileIsRemoved =
false;
759 auto UpdateStatsOnRemoveDocument = [&](
unsigned FileIndex) {
760 auto &Stats = ReqStats[FileIndex];
762 Stats.FileIsRemoved =
true;
765 auto AddDocument = [&](
unsigned FileIndex,
bool SkipCache) {
766 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
767 Server.addDocument(FilePaths[FileIndex],
768 ShouldHaveErrors ? SourceContentsWithErrors
769 : SourceContentsWithoutErrors);
770 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
774 auto AddDocumentRequest = [&]() {
775 unsigned FileIndex = FileIndexDist(RandGen);
776 AddDocument(FileIndex,
false);
779 auto ForceReparseRequest = [&]() {
780 unsigned FileIndex = FileIndexDist(RandGen);
781 AddDocument(FileIndex,
true);
784 auto RemoveDocumentRequest = [&]() {
785 unsigned FileIndex = FileIndexDist(RandGen);
787 if (ReqStats[FileIndex].FileIsRemoved)
788 AddDocument(FileIndex,
false);
790 Server.removeDocument(FilePaths[FileIndex]);
791 UpdateStatsOnRemoveDocument(FileIndex);
794 auto CodeCompletionRequest = [&]() {
795 unsigned FileIndex = FileIndexDist(RandGen);
797 if (ReqStats[FileIndex].FileIsRemoved)
798 AddDocument(FileIndex,
false);
801 Pos.line = LineDist(RandGen);
802 Pos.character = ColumnDist(RandGen);
810 clangd::CodeCompleteOptions()));
813 auto LocateSymbolRequest = [&]() {
814 unsigned FileIndex = FileIndexDist(RandGen);
816 if (ReqStats[FileIndex].FileIsRemoved)
817 AddDocument(FileIndex,
false);
820 Pos.line = LineDist(RandGen);
821 Pos.character = ColumnDist(RandGen);
826 std::vector<std::function<void()>> AsyncRequests = {
827 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
828 std::vector<std::function<void()>> BlockingRequests = {
829 CodeCompletionRequest, LocateSymbolRequest};
832 std::uniform_int_distribution<int> AsyncRequestIndexDist(
833 0, AsyncRequests.size() - 1);
834 std::uniform_int_distribution<int> BlockingRequestIndexDist(
835 0, BlockingRequests.size() - 1);
836 for (
unsigned I = 1; I <= RequestsCount; ++I) {
837 if (I % BlockingRequestInterval != 0) {
839 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
840 AsyncRequests[RequestIndex]();
843 auto RequestIndex = BlockingRequestIndexDist(RandGen);
844 BlockingRequests[RequestIndex]();
847 ASSERT_TRUE(Server.blockUntilIdleForTest());
851 std::vector<FileStat> Stats =
DiagConsumer.takeFileStats();
852 for (
unsigned I = 0; I < FilesCount; ++I) {
853 if (!ReqStats[I].FileIsRemoved) {
854 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
855 ReqStats[I].LastContentsHadErrors);
858 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
859 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
863TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
864 class NoConcurrentAccessDiagConsumer :
public ClangdServer::Callbacks {
866 std::atomic<int> Count = {0};
868 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
869 : StartSecondReparse(std::move(StartSecondReparse)) {}
871 void onDiagnosticsReady(PathRef, llvm::StringRef,
872 llvm::ArrayRef<Diag>)
override {
874 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
875 ASSERT_TRUE(Lock.owns_lock())
876 <<
"Detected concurrent onDiagnosticsReady calls for the same file.";
881 FirstRequest =
false;
882 StartSecondReparse.set_value();
884 std::this_thread::sleep_for(std::chrono::milliseconds(50));
890 bool FirstRequest =
true;
891 std::promise<void> StartSecondReparse;
894 const auto *SourceContentsWithoutErrors = R
"cpp(
901 const auto *SourceContentsWithErrors = R
"cpp(
910 FS.Files[FooCpp] =
"";
912 std::promise<void> StartSecondPromise;
913 std::future<void> StartSecond = StartSecondPromise.get_future();
915 NoConcurrentAccessDiagConsumer
DiagConsumer(std::move(StartSecondPromise));
916 MockCompilationDatabase CDB;
918 Server.addDocument(FooCpp, SourceContentsWithErrors);
920 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
921 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
925TEST(ClangdServerTest, FormatCode) {
928 MockCompilationDatabase CDB;
932 std::string Code = R
"cpp(
944 FS.Files[Path] = Code;
948 EXPECT_TRUE(
static_cast<bool>(
Replaces));
950 EXPECT_TRUE(
static_cast<bool>(
Changed));
954TEST(ClangdServerTest, ChangedHeaderFromISystem) {
957 MockCompilationDatabase CDB;
960 auto SourcePath =
testPath(
"source/foo.cpp");
961 auto HeaderPath =
testPath(
"headers/foo.h");
962 FS.Files[HeaderPath] =
"struct X { int bar; };";
963 Annotations Code(R
"cpp(
969 CDB.ExtraClangFlags.push_back("-xc++");
970 CDB.ExtraClangFlags.push_back(
"-isystem" +
testPath(
"headers"));
973 auto Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
974 clangd::CodeCompleteOptions()))
979 FS.Files[HeaderPath] =
"struct X { int bar; int baz; };";
981 Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
982 clangd::CodeCompleteOptions()))
993TEST(ClangdTests, PreambleVFSStatCache) {
994 class StatRecordingFS :
public ThreadsafeFS {
995 llvm::StringMap<unsigned> &CountStats;
999 llvm::StringMap<std::string> Files;
1001 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1002 : CountStats(CountStats) {}
1005 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
1006 class StatRecordingVFS :
public llvm::vfs::ProxyFileSystem {
1008 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1009 llvm::StringMap<unsigned> &CountStats)
1010 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1012 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1013 openFileForRead(
const Twine &
Path)
override {
1014 ++CountStats[llvm::sys::path::filename(
Path.str())];
1015 return ProxyFileSystem::openFileForRead(
Path);
1017 llvm::ErrorOr<llvm::vfs::Status> status(
const Twine &
Path)
override {
1018 ++CountStats[llvm::sys::path::filename(
Path.str())];
1019 return ProxyFileSystem::status(
Path);
1023 llvm::StringMap<unsigned> &CountStats;
1026 return IntrusiveRefCntPtr<StatRecordingVFS>(
1027 new StatRecordingVFS(
buildTestFS(Files), CountStats));
1031 llvm::StringMap<unsigned> CountStats;
1032 StatRecordingFS FS(CountStats);
1034 MockCompilationDatabase CDB;
1037 auto SourcePath =
testPath(
"foo.cpp");
1038 auto HeaderPath =
testPath(
"foo.h");
1039 FS.Files[HeaderPath] =
"struct TestSym {};";
1040 Annotations Code(R
"cpp(
1049 unsigned Before = CountStats[
"foo.h"];
1050 EXPECT_GT(Before, 0u);
1051 auto Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
1052 clangd::CodeCompleteOptions()))
1054 EXPECT_EQ(CountStats[
"foo.h"], Before);
1055 EXPECT_THAT(Completions,
1060TEST(ClangdServerTest, 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 = {
"-###"};
1080 Server.addDocument(FooCpp, Code.code());
1081 ASSERT_TRUE(Server.blockUntilIdleForTest());
1082 auto Res = cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts));
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());
1094 cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1101 cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1106TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1110 class DelayedCompilationDatabase :
public GlobalCompilationDatabase {
1112 DelayedCompilationDatabase(Notification &CanReturnCommand)
1113 : CanReturnCommand(CanReturnCommand) {}
1115 std::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",
1123 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
1127 std::vector<std::string> ExtraClangFlags;
1130 Notification &CanReturnCommand;
1133 Notification CanReturnCommand;
1134 DelayedCompilationDatabase CDB(CanReturnCommand);
1138 Annotations Code(R
"cpp(
1139 namespace ns { int xyz; }
1144 FS.Files[FooCpp] = FooCpp;
1145 Server.addDocument(FooCpp, Code.code());
1149 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1150 auto Opts = clangd::CodeCompleteOptions();
1153 auto Res = cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts));
1154 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1156 CanReturnCommand.notify();
1157 ASSERT_TRUE(Server.blockUntilIdleForTest());
1159 clangd::CodeCompleteOptions()))
1165TEST(ClangdServerTest, CustomAction) {
1166 OverlayCDB CDB(
nullptr);
1170 Server.addDocument(
testPath(
"foo.cc"),
"void x();");
1171 Decl::Kind XKind = Decl::TranslationUnit;
1173 [&](InputsAndAST
AST) {
1177 EXPECT_EQ(XKind, Decl::Function);
1182#if !defined(__has_feature) || !__has_feature(address_sanitizer)
1183TEST(ClangdServerTest, TestStackOverflow) {
1186 MockCompilationDatabase CDB;
1189 const char *SourceContents = R
"cpp(
1190 constexpr int foo() { return foo(); }
1191 static_assert(foo());
1195 FS.Files[FooCpp] = SourceContents;
1197 Server.addDocument(FooCpp, SourceContents);
1198 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
1205TEST(ClangdServer, TidyOverrideTest) {
1206 struct DiagsCheckingCallback :
public ClangdServer::Callbacks {
1208 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
1210 std::lock_guard<std::mutex> Lock(Mutex);
1215 bool HadDiagsInLastCallback =
false;
1221 FS.Files[
testPath(
".clang-tidy")] = R
"(
1222 Checks: -*,bugprone-use-after-move,llvm-header-guard
1224 MockCompilationDatabase CDB;
1225 std::vector<TidyProvider> Stack;
1229 CDB.ExtraClangFlags = {"-xc++"};
1231 Opts.ClangTidyProvider = Provider;
1233 const char *SourceContents = R
"cpp(
1234 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1235 namespace std { Foo&& move(Foo&); }
1238 Foo y = std::move(x);
1241 Server.addDocument(testPath("foo.h"), SourceContents);
1242 ASSERT_TRUE(Server.blockUntilIdleForTest());
1246TEST(ClangdServer, MemoryUsageTest) {
1248 MockCompilationDatabase CDB;
1252 Server.addDocument(FooCpp,
"");
1253 ASSERT_TRUE(Server.blockUntilIdleForTest());
1255 llvm::BumpPtrAllocator Alloc;
1256 MemoryTree MT(&Alloc);
1258 ASSERT_TRUE(MT.children().count(
"tuscheduler"));
1259 EXPECT_TRUE(MT.child(
"tuscheduler").children().count(FooCpp));
1262TEST(ClangdServer, RespectsTweakFormatting) {
1263 static constexpr const char *TweakID =
"ModuleTweak";
1264 static constexpr const char *NewContents =
"{not;\nformatted;}";
1268 struct TweakContributingModule final :
public FeatureModule {
1269 struct ModuleTweak final :
public Tweak {
1270 const char *id()
const override {
return TweakID; }
1271 bool prepare(
const Selection &Sel)
override {
return true; }
1272 Expected<Effect> apply(
const Selection &Sel)
override {
1273 auto &SM = Sel.AST->getSourceManager();
1274 llvm::StringRef FilePath = SM.getFilename(Sel.Cursor);
1275 tooling::Replacements Reps;
1277 Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents)));
1278 auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps)));
1279 E.FormatEdits =
false;
1282 std::string title()
const override {
return id(); }
1283 llvm::StringLiteral kind()
const override {
1284 return llvm::StringLiteral(
"");
1288 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &
Out)
override {
1289 Out.emplace_back(
new ModuleTweak);
1294 MockCompilationDatabase CDB;
1296 FeatureModuleSet Set;
1297 Set.add(std::make_unique<TweakContributingModule>());
1298 Opts.FeatureModules = &Set;
1299 ClangdServer Server(CDB, FS, Opts);
1302 Server.addDocument(FooCpp,
"");
1303 ASSERT_TRUE(Server.blockUntilIdleForTest());
1307 Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected<Tweak::Effect>
E) {
1308 ASSERT_TRUE(
static_cast<bool>(
E));
1309 EXPECT_THAT(llvm::cantFail(
E->ApplyEdits.lookup(FooCpp).apply()),
1316TEST(ClangdServer, InactiveRegions) {
1317 struct InactiveRegionsCallback : ClangdServer::Callbacks {
1318 std::vector<std::vector<Range>> FoundInactiveRegions;
1320 void onInactiveRegionsReady(PathRef FIle,
1321 std::vector<Range> InactiveRegions)
override {
1322 FoundInactiveRegions.push_back(std::move(InactiveRegions));
1327 MockCompilationDatabase CDB;
1328 CDB.ExtraClangFlags.push_back(
"-DCMDMACRO");
1330 Opts.PublishInactiveRegions =
true;
1332 ClangdServer Server(CDB, FS, Opts, &
Callback);
1333 Annotations Source(R
"cpp(
1334#define PREAMBLEMACRO 42
1335#if PREAMBLEMACRO > 40
1338$inactive1[[ #define INACTIVE]]
1342$inactive2[[ int inactiveInt;]]
1346$inactive3[[ int inactiveInt2;]]
1347#elif PREAMBLEMACRO > 0
1351$inactive4[[ int inactiveInt3;]]
1354#endif // empty inactive range, gets dropped
1356 Server.addDocument(testPath("foo.cpp"), Source.code());
1357 ASSERT_TRUE(Server.blockUntilIdleForTest());
1358 EXPECT_THAT(
Callback.FoundInactiveRegions,
1359 ElementsAre(ElementsAre(
1360 Source.range(
"inactive1"), Source.range(
"inactive2"),
1361 Source.range(
"inactive3"), Source.range(
"inactive4"))));
llvm::SmallString< 256U > Name
CompiledFragmentImpl & Out
CharSourceRange Range
SourceRange for the file name.
std::vector< HeaderHandle > Path
DiagnosticConsumer DiagConsumer
std::vector< const char * > Expected
std::vector< llvm::StringRef > CommandLine
WantDiagnostics Diagnostics
#define EXPECT_ERROR(expectedValue)
static std::function< Context(PathRef)> createConfiguredContextProvider(const config::Provider *Provider, ClangdServer::Callbacks *)
Creates a context provider that loads and installs config.
static Options optsForTest()
@ Changed
The file got changed.
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
llvm::Expected< tooling::Replacements > runFormatFile(ClangdServer &Server, PathRef File, std::optional< Range > Rng)
TidyProvider combine(std::vector< TidyProvider > Providers)
ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, const ASTContext &Ctx)
std::string Path
A typedef to represent a file path.
llvm::Expected< RenameResult > runRename(ClangdServer &Server, PathRef File, Position Pos, llvm::StringRef NewName, const RenameOptions &RenameOpts)
MATCHER_P2(hasFlag, Flag, Path, "")
llvm::Expected< CodeCompleteResult > runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, clangd::CodeCompleteOptions Opts)
llvm::Expected< SignatureHelp > runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos, MarkupKind DocumentationFormat)
std::string testPath(PathRef File, llvm::sys::path::Style Style)
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
TEST(BackgroundQueueTest, Priority)
void runAddDocument(ClangdServer &Server, PathRef File, llvm::StringRef Contents, llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild)
llvm::Expected< std::vector< LocatedSymbol > > runLocateSymbolAt(ClangdServer &Server, PathRef File, Position Pos)
TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS)
Provider that searches for .clang-tidy configuration files in the directory tree.
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > buildTestFS(llvm::StringMap< std::string > const &Files, llvm::StringMap< time_t > const &Timestamps)
TidyProvider disableUnusableChecks(llvm::ArrayRef< std::string > ExtraBadChecks)
Provider that will disable checks known to not work with clangd.
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
llvm::Error runCustomAction(ClangdServer &Server, PathRef File, llvm::function_ref< void(InputsAndAST)> Action)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
llvm::Expected< std::vector< DocumentHighlight > > runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
@ ParseIfReady
Run the parser if inputs (preamble) are ready.
@ NeverParse
Always use text-based completion.
enum clang::clangd::CodeCompleteOptions::CodeCompletionParse RunParser
static CommandMangler forTests()
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.