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/Support/Allocator.h"
33#include "llvm/Support/Error.h"
34#include "llvm/Support/Path.h"
35#include "llvm/Support/Regex.h"
36#include "llvm/Support/VirtualFileSystem.h"
37#include "llvm/Testing/Support/Error.h"
38#include "gmock/gmock.h"
39#include "gtest/gtest.h"
54using ::testing::AllOf;
55using ::testing::ElementsAre;
56using ::testing::Field;
57using ::testing::IsEmpty;
59using ::testing::SizeIs;
60using ::testing::UnorderedElementsAre;
63 return arg.PreferredDeclaration ==
67bool diagsContainErrors(
const std::vector<Diag> &
Diagnostics) {
69 if (D.Severity == DiagnosticsEngine::Error ||
70 D.Severity == DiagnosticsEngine::Fatal)
76class ErrorCheckingCallbacks :
public ClangdServer::Callbacks {
78 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
81 std::lock_guard<std::mutex> Lock(Mutex);
82 HadErrorInLastDiags = HadError;
85 bool hadErrorInLastDiags() {
86 std::lock_guard<std::mutex> Lock(Mutex);
87 return HadErrorInLastDiags;
92 bool HadErrorInLastDiags =
false;
97class MultipleErrorCheckingCallbacks :
public ClangdServer::Callbacks {
99 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
103 std::lock_guard<std::mutex> Lock(Mutex);
104 LastDiagsHadError[
File] = HadError;
110 std::vector<std::pair<Path, bool>> filesWithDiags()
const {
111 std::vector<std::pair<Path, bool>> Result;
112 std::lock_guard<std::mutex> Lock(Mutex);
113 for (
const auto &It : LastDiagsHadError)
114 Result.emplace_back(std::string(It.first()), It.second);
119 std::lock_guard<std::mutex> Lock(Mutex);
120 LastDiagsHadError.clear();
124 mutable std::mutex Mutex;
125 llvm::StringMap<bool> LastDiagsHadError;
129std::string replacePtrsInDump(std::string
const &Dump) {
130 llvm::Regex RE(
"0x[0-9a-fA-F]+");
131 llvm::SmallVector<llvm::StringRef, 1> Matches;
132 llvm::StringRef Pending = Dump;
135 while (RE.match(Pending, &Matches)) {
136 assert(Matches.size() == 1 &&
"Exactly one match expected");
137 auto MatchPos = Matches[0].data() - Pending.data();
139 Result += Pending.take_front(MatchPos);
140 Pending = Pending.drop_front(MatchPos + Matches[0].size());
150 Server.customAction(
File,
"DumpAST", [&](llvm::Expected<InputsAndAST>
AST) {
152 llvm::raw_string_ostream ResultOS(Result);
153 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS,
true);
155 llvm::consumeError(
AST.takeError());
164std::string dumpASTWithoutMemoryLocs(ClangdServer &Server,
PathRef File) {
168std::string parseSourceAndDumpAST(
169 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
170 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
171 bool ExpectErrors =
false) {
174 MockCompilationDatabase CDB;
176 for (
const auto &FileWithContents : ExtraFiles)
177 FS.Files[
testPath(FileWithContents.first)] =
178 std::string(FileWithContents.second);
180 auto SourceFilename =
testPath(SourceFileRelPath);
181 Server.addDocument(SourceFilename, SourceContents);
182 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
183 EXPECT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
184 EXPECT_EQ(ExpectErrors,
DiagConsumer.hadErrorInLastDiags());
188TEST(ClangdServerTest, Parse) {
192 auto Empty = parseSourceAndDumpAST(
"foo.cpp",
"");
193 auto OneDecl = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
194 auto SomeDecls = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
195 EXPECT_NE(
Empty, OneDecl);
196 EXPECT_NE(
Empty, SomeDecls);
197 EXPECT_NE(SomeDecls, OneDecl);
199 auto Empty2 = parseSourceAndDumpAST(
"foo.cpp",
"");
200 auto OneDecl2 = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
201 auto SomeDecls2 = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
202 EXPECT_EQ(
Empty, Empty2);
203 EXPECT_EQ(OneDecl, OneDecl2);
204 EXPECT_EQ(SomeDecls, SomeDecls2);
207TEST(ClangdServerTest, ParseWithHeader) {
208 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {},
210 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {{
"foo.h",
""}},
213 const auto *SourceContents = R
"cpp(
217 parseSourceAndDumpAST("foo.cpp", SourceContents, {{
"foo.h",
""}},
219 parseSourceAndDumpAST(
"foo.cpp", SourceContents, {{
"foo.h",
"int a;"}},
223TEST(ClangdServerTest, Reparse) {
226 MockCompilationDatabase CDB;
229 const auto *SourceContents = R
"cpp(
236 FS.Files[
testPath(
"foo.h")] =
"int a;";
237 FS.Files[FooCpp] = SourceContents;
239 Server.addDocument(FooCpp, SourceContents);
240 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
241 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
244 Server.addDocument(FooCpp,
"");
245 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
246 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
249 Server.addDocument(FooCpp, SourceContents);
250 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
251 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
254 EXPECT_EQ(DumpParse1, DumpParse2);
255 EXPECT_NE(DumpParse1, DumpParseEmpty);
258TEST(ClangdServerTest, ReparseOnHeaderChange) {
261 MockCompilationDatabase CDB;
264 const auto *SourceContents = R
"cpp(
272 FS.Files[FooH] =
"int a;";
273 FS.Files[FooCpp] = SourceContents;
275 Server.addDocument(FooCpp, SourceContents);
276 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
277 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
281 Server.addDocument(FooCpp, SourceContents);
282 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
283 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
286 FS.Files[FooH] =
"int a;";
287 Server.addDocument(FooCpp, SourceContents);
288 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
289 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
292 EXPECT_EQ(DumpParse1, DumpParse2);
293 EXPECT_NE(DumpParse1, DumpParseDifferent);
296TEST(ClangdServerTest, PropagatesContexts) {
297 static Key<int> Secret;
298 struct ContextReadingFS :
public ThreadsafeFS {
302 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
303 Got = Context::current().getExisting(Secret);
307 struct Callbacks :
public ClangdServer::Callbacks {
308 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
310 Got = Context::current().getExisting(Secret);
314 MockCompilationDatabase CDB;
319 WithContextValue Entrypoint(Secret, 42);
320 Server.addDocument(
testPath(
"foo.cpp"),
"void main(){}");
322 ASSERT_TRUE(Server.blockUntilIdleForTest());
323 EXPECT_EQ(FS.Got, 42);
324 EXPECT_EQ(Callbacks.Got, 42);
327TEST(ClangdServerTest, RespectsConfig) {
329 Annotations Example(R
"cpp(
338 class ConfigProvider :
public config::Provider {
339 std::vector<config::CompiledFragment>
340 getFragments(
const config::Params &,
341 config::DiagnosticCallback DC)
const override {
343 F.If.PathMatch.emplace_back(
".*foo.cc");
344 F.CompileFlags.Add.emplace_back(
"-DFOO=1");
345 return {std::move(F).compile(DC)};
350 Opts.ContextProvider =
352 OverlayCDB CDB(
nullptr, {},
355 ClangdServer Server(CDB, FS, Opts);
357 Server.addDocument(
testPath(
"foo.cc"), Example.code());
359 ASSERT_TRUE(
bool(Result)) << Result.takeError();
360 ASSERT_THAT(*Result, SizeIs(1));
361 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
363 Server.addDocument(
testPath(
"bar.cc"), Example.code());
365 ASSERT_TRUE(
bool(Result)) << Result.takeError();
366 ASSERT_THAT(*Result, SizeIs(1));
367 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
370TEST(ClangdServerTest, PropagatesVersion) {
371 MockCompilationDatabase CDB;
373 struct Callbacks :
public ClangdServer::Callbacks {
374 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
378 std::string Got =
"";
384 EXPECT_EQ(Callbacks.Got,
"42");
389TEST(ClangdServerTest, SearchLibDir) {
393 MockCompilationDatabase CDB;
394 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
395 {
"-xc++",
"--target=x86_64-unknown-linux-gnu",
396 "-m64",
"--gcc-toolchain=/randomusr",
397 "-stdlib=libstdc++"});
401 SmallString<8> Version(
"4.9.3");
404 SmallString<64> LibDir(
"/randomusr/lib/gcc/x86_64-linux-gnu");
405 llvm::sys::path::append(LibDir, Version);
409 SmallString<64> MockLibFile;
410 llvm::sys::path::append(MockLibFile, LibDir,
"64",
"crtbegin.o");
411 FS.Files[MockLibFile] =
"";
413 SmallString<64> IncludeDir(
"/randomusr/include/c++");
414 llvm::sys::path::append(IncludeDir, Version);
416 SmallString<64> StringPath;
417 llvm::sys::path::append(StringPath, IncludeDir,
"string");
418 FS.Files[StringPath] =
"class mock_string {};";
421 const auto *SourceContents = R
"cpp(
425 FS.Files[FooCpp] = SourceContents;
430 const auto *SourceContentsWithError = R
"cpp(
439TEST(ClangdServerTest, ForceReparseCompileCommand) {
442 MockCompilationDatabase CDB;
446 const auto *SourceContents1 = R
"cpp(
450 const auto *SourceContents2 = R
"cpp(
455 FS.Files[FooCpp] = "";
458 CDB.ExtraClangFlags = {
"-xc"};
465 CDB.ExtraClangFlags = {
"-xc++"};
475TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
478 MockCompilationDatabase CDB;
482 const auto *SourceContents = R
"cpp(
487int main() { return 0; }
489 FS.Files[FooCpp] = "";
492 CDB.ExtraClangFlags = {
"-DWITH_ERROR"};
497 CDB.ExtraClangFlags = {};
499 ASSERT_TRUE(Server.blockUntilIdleForTest());
507TEST(ClangdServerTest, ReparseOpenedFiles) {
508 Annotations FooSource(R
"cpp(
510static void $one[[bob]]() {}
512static void $two[[bob]]() {}
515int main () { bo^b (); return 0; }
518 Annotations BarSource(R"cpp(
524 Annotations BazSource(R"cpp(
529 MockCompilationDatabase CDB;
537 FS.Files[FooCpp] =
"";
538 FS.Files[BarCpp] =
"";
539 FS.Files[BazCpp] =
"";
541 CDB.ExtraClangFlags = {
"-DMACRO=1"};
542 Server.addDocument(FooCpp, FooSource.code());
543 Server.addDocument(BarCpp, BarSource.code());
544 Server.addDocument(BazCpp, BazSource.code());
545 ASSERT_TRUE(Server.blockUntilIdleForTest());
548 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
true),
549 Pair(BazCpp,
false)));
552 EXPECT_TRUE(
bool(Locations));
553 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"one"))));
556 CDB.ExtraClangFlags.clear();
558 Server.removeDocument(BazCpp);
559 Server.addDocument(FooCpp, FooSource.code());
560 Server.addDocument(BarCpp, BarSource.code());
561 ASSERT_TRUE(Server.blockUntilIdleForTest());
564 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
false)));
567 EXPECT_TRUE(
bool(Locations));
568 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"two"))));
571MATCHER_P4(Stats,
Name, UsesMemory, PreambleBuilds, ASTBuilds,
"") {
572 return arg.first() ==
Name &&
573 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
575 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
576 std::tie(PreambleBuilds, ASTBuilds);
579TEST(ClangdServerTest, FileStats) {
582 MockCompilationDatabase CDB;
586 const auto *SourceContents = R
"cpp(
593 FS.Files[FooCpp] =
"";
594 FS.Files[BarCpp] =
"";
596 EXPECT_THAT(Server.fileStats(), IsEmpty());
598 Server.addDocument(FooCpp, SourceContents);
599 Server.addDocument(BarCpp, SourceContents);
600 ASSERT_TRUE(Server.blockUntilIdleForTest());
602 EXPECT_THAT(Server.fileStats(),
603 UnorderedElementsAre(Stats(FooCpp,
true, 1, 1),
604 Stats(BarCpp,
true, 1, 1)));
606 Server.removeDocument(FooCpp);
607 ASSERT_TRUE(Server.blockUntilIdleForTest());
608 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp,
true, 1, 1)));
610 Server.removeDocument(BarCpp);
611 ASSERT_TRUE(Server.blockUntilIdleForTest());
612 EXPECT_THAT(Server.fileStats(), IsEmpty());
615TEST(ClangdServerTest, InvalidCompileCommand) {
618 MockCompilationDatabase CDB;
624 CDB.ExtraClangFlags.push_back(
"-###");
629 EXPECT_EQ(
dumpAST(Server, FooCpp),
"<no-ast>");
633 clangd::RenameOptions()));
638 clangd::CodeCompleteOptions()))
644TEST(ClangdThreadingTest, StressTest) {
646 static const unsigned FilesCount = 5;
647 const unsigned RequestsCount = 500;
651 const unsigned BlockingRequestInterval = 40;
653 const auto *SourceContentsWithoutErrors = R
"cpp(
660 const auto *SourceContentsWithErrors = R
"cpp(
670 unsigned MaxLineForFileRequests = 7;
671 unsigned MaxColumnForFileRequests = 10;
673 std::vector<std::string> FilePaths;
675 for (
unsigned I = 0; I < FilesCount; ++I) {
676 std::string
Name = std::string(
"Foo") + std::to_string(I) +
".cpp";
682 unsigned HitsWithoutErrors = 0;
683 unsigned HitsWithErrors = 0;
684 bool HadErrorsInLastDiags =
false;
687 class TestDiagConsumer :
public ClangdServer::Callbacks {
689 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
691 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
693 StringRef FileIndexStr = llvm::sys::path::stem(File);
694 ASSERT_TRUE(FileIndexStr.consume_front(
"Foo"));
696 unsigned long FileIndex = std::stoul(FileIndexStr.str());
700 std::lock_guard<std::mutex> Lock(Mutex);
702 Stats[FileIndex].HitsWithErrors++;
704 Stats[FileIndex].HitsWithoutErrors++;
705 Stats[FileIndex].HadErrorsInLastDiags = HadError;
708 std::vector<FileStat> takeFileStats() {
709 std::lock_guard<std::mutex> Lock(Mutex);
710 return std::move(Stats);
715 std::vector<FileStat> Stats;
718 struct RequestStats {
719 unsigned RequestsWithoutErrors = 0;
720 unsigned RequestsWithErrors = 0;
721 bool LastContentsHadErrors =
false;
722 bool FileIsRemoved =
true;
725 std::vector<RequestStats> ReqStats;
726 ReqStats.reserve(FilesCount);
727 for (
unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
728 ReqStats.emplace_back();
732 MockCompilationDatabase CDB;
736 std::random_device RandGen;
738 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
741 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
743 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
744 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
747 auto UpdateStatsOnAddDocument = [&](
unsigned FileIndex,
bool HadErrors) {
748 auto &Stats = ReqStats[FileIndex];
751 ++Stats.RequestsWithErrors;
753 ++Stats.RequestsWithoutErrors;
754 Stats.LastContentsHadErrors = HadErrors;
755 Stats.FileIsRemoved =
false;
758 auto UpdateStatsOnRemoveDocument = [&](
unsigned FileIndex) {
759 auto &Stats = ReqStats[FileIndex];
761 Stats.FileIsRemoved =
true;
764 auto AddDocument = [&](
unsigned FileIndex,
bool SkipCache) {
765 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
766 Server.addDocument(FilePaths[FileIndex],
767 ShouldHaveErrors ? SourceContentsWithErrors
768 : SourceContentsWithoutErrors);
769 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
773 auto AddDocumentRequest = [&]() {
774 unsigned FileIndex = FileIndexDist(RandGen);
775 AddDocument(FileIndex,
false);
778 auto ForceReparseRequest = [&]() {
779 unsigned FileIndex = FileIndexDist(RandGen);
780 AddDocument(FileIndex,
true);
783 auto RemoveDocumentRequest = [&]() {
784 unsigned FileIndex = FileIndexDist(RandGen);
786 if (ReqStats[FileIndex].FileIsRemoved)
787 AddDocument(FileIndex,
false);
789 Server.removeDocument(FilePaths[FileIndex]);
790 UpdateStatsOnRemoveDocument(FileIndex);
793 auto CodeCompletionRequest = [&]() {
794 unsigned FileIndex = FileIndexDist(RandGen);
796 if (ReqStats[FileIndex].FileIsRemoved)
797 AddDocument(FileIndex,
false);
800 Pos.line = LineDist(RandGen);
801 Pos.character = ColumnDist(RandGen);
809 clangd::CodeCompleteOptions()));
812 auto LocateSymbolRequest = [&]() {
813 unsigned FileIndex = FileIndexDist(RandGen);
815 if (ReqStats[FileIndex].FileIsRemoved)
816 AddDocument(FileIndex,
false);
819 Pos.line = LineDist(RandGen);
820 Pos.character = ColumnDist(RandGen);
825 std::vector<std::function<void()>> AsyncRequests = {
826 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
827 std::vector<std::function<void()>> BlockingRequests = {
828 CodeCompletionRequest, LocateSymbolRequest};
831 std::uniform_int_distribution<int> AsyncRequestIndexDist(
832 0, AsyncRequests.size() - 1);
833 std::uniform_int_distribution<int> BlockingRequestIndexDist(
834 0, BlockingRequests.size() - 1);
835 for (
unsigned I = 1; I <= RequestsCount; ++I) {
836 if (I % BlockingRequestInterval != 0) {
838 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
839 AsyncRequests[RequestIndex]();
842 auto RequestIndex = BlockingRequestIndexDist(RandGen);
843 BlockingRequests[RequestIndex]();
846 ASSERT_TRUE(Server.blockUntilIdleForTest());
850 std::vector<FileStat> Stats =
DiagConsumer.takeFileStats();
851 for (
unsigned I = 0; I < FilesCount; ++I) {
852 if (!ReqStats[I].FileIsRemoved) {
853 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
854 ReqStats[I].LastContentsHadErrors);
857 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
858 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
862TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
863 class NoConcurrentAccessDiagConsumer :
public ClangdServer::Callbacks {
865 std::atomic<int> Count = {0};
867 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
868 : StartSecondReparse(std::move(StartSecondReparse)) {}
870 void onDiagnosticsReady(PathRef, llvm::StringRef,
871 llvm::ArrayRef<Diag>)
override {
873 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
874 ASSERT_TRUE(Lock.owns_lock())
875 <<
"Detected concurrent onDiagnosticsReady calls for the same file.";
880 FirstRequest =
false;
881 StartSecondReparse.set_value();
883 std::this_thread::sleep_for(std::chrono::milliseconds(50));
889 bool FirstRequest =
true;
890 std::promise<void> StartSecondReparse;
893 const auto *SourceContentsWithoutErrors = R
"cpp(
900 const auto *SourceContentsWithErrors = R
"cpp(
909 FS.Files[FooCpp] =
"";
911 std::promise<void> StartSecondPromise;
912 std::future<void> StartSecond = StartSecondPromise.get_future();
914 NoConcurrentAccessDiagConsumer
DiagConsumer(std::move(StartSecondPromise));
915 MockCompilationDatabase CDB;
917 Server.addDocument(FooCpp, SourceContentsWithErrors);
919 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
920 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
924TEST(ClangdServerTest, FormatCode) {
927 MockCompilationDatabase CDB;
931 std::string
Code = R
"cpp(
947 EXPECT_TRUE(
static_cast<bool>(
Replaces));
949 EXPECT_TRUE(
static_cast<bool>(
Changed));
953TEST(ClangdServerTest, ChangedHeaderFromISystem) {
956 MockCompilationDatabase CDB;
959 auto SourcePath =
testPath(
"source/foo.cpp");
960 auto HeaderPath =
testPath(
"headers/foo.h");
961 FS.Files[HeaderPath] =
"struct X { int bar; };";
962 Annotations
Code(R
"cpp(
968 CDB.ExtraClangFlags.push_back("-xc++");
969 CDB.ExtraClangFlags.push_back(
"-isystem" +
testPath(
"headers"));
973 clangd::CodeCompleteOptions()))
978 FS.Files[HeaderPath] =
"struct X { int bar; int baz; };";
981 clangd::CodeCompleteOptions()))
992TEST(ClangdTests, PreambleVFSStatCache) {
993 class StatRecordingFS :
public ThreadsafeFS {
994 llvm::StringMap<unsigned> &CountStats;
998 llvm::StringMap<std::string> Files;
1000 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1001 : CountStats(CountStats) {}
1004 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
1005 class StatRecordingVFS :
public llvm::vfs::ProxyFileSystem {
1007 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1008 llvm::StringMap<unsigned> &CountStats)
1009 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1011 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1012 openFileForRead(
const Twine &
Path)
override {
1013 ++CountStats[llvm::sys::path::filename(
Path.str())];
1014 return ProxyFileSystem::openFileForRead(
Path);
1016 llvm::ErrorOr<llvm::vfs::Status> status(
const Twine &
Path)
override {
1017 ++CountStats[llvm::sys::path::filename(
Path.str())];
1018 return ProxyFileSystem::status(
Path);
1022 llvm::StringMap<unsigned> &CountStats;
1025 return IntrusiveRefCntPtr<StatRecordingVFS>(
1026 new StatRecordingVFS(
buildTestFS(Files), CountStats));
1030 llvm::StringMap<unsigned> CountStats;
1031 StatRecordingFS FS(CountStats);
1033 MockCompilationDatabase CDB;
1036 auto SourcePath =
testPath(
"foo.cpp");
1037 auto HeaderPath =
testPath(
"foo.h");
1038 FS.Files[HeaderPath] =
"struct TestSym {};";
1039 Annotations
Code(R
"cpp(
1048 unsigned Before = CountStats[
"foo.h"];
1049 EXPECT_GT(Before, 0u);
1051 clangd::CodeCompleteOptions()))
1053 EXPECT_EQ(CountStats[
"foo.h"], Before);
1054 EXPECT_THAT(Completions,
1059TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) {
1062 MockCompilationDatabase CDB;
1066 Annotations
Code(R
"cpp(
1067 namespace ns { int xyz; }
1072 FS.Files[FooCpp] = FooCpp;
1074 auto Opts = clangd::CodeCompleteOptions();
1078 CDB.ExtraClangFlags = {
"-###"};
1079 Server.addDocument(FooCpp,
Code.code());
1080 ASSERT_TRUE(Server.blockUntilIdleForTest());
1082 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1084 EXPECT_THAT(Res.Completions,
1089 CDB.ExtraClangFlags = {
"-std=c++11"};
1090 Server.addDocument(FooCpp,
Code.code());
1091 ASSERT_TRUE(Server.blockUntilIdleForTest());
1105TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1109 class DelayedCompilationDatabase :
public GlobalCompilationDatabase {
1111 DelayedCompilationDatabase(Notification &CanReturnCommand)
1112 : CanReturnCommand(CanReturnCommand) {}
1114 std::optional<tooling::CompileCommand>
1115 getCompileCommand(PathRef File)
const override {
1118 CanReturnCommand.wait();
1119 auto FileName = llvm::sys::path::filename(File);
1120 std::vector<std::string>
CommandLine = {
"clangd",
"-ffreestanding",
1122 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
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()))
1164TEST(ClangdServerTest, CustomAction) {
1165 OverlayCDB CDB(
nullptr);
1169 Server.addDocument(
testPath(
"foo.cc"),
"void x();");
1170 Decl::Kind XKind = Decl::TranslationUnit;
1172 [&](InputsAndAST
AST) {
1176 EXPECT_EQ(XKind, Decl::Function);
1181#if !defined(__has_feature) || !__has_feature(address_sanitizer)
1182TEST(ClangdServerTest, TestStackOverflow) {
1185 MockCompilationDatabase CDB;
1188 const char *SourceContents = R
"cpp(
1189 constexpr int foo() { return foo(); }
1190 static_assert(foo());
1194 FS.Files[FooCpp] = SourceContents;
1196 Server.addDocument(FooCpp, SourceContents);
1197 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
1204TEST(ClangdServer, TidyOverrideTest) {
1205 struct DiagsCheckingCallback :
public ClangdServer::Callbacks {
1207 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
1209 std::lock_guard<std::mutex> Lock(Mutex);
1214 bool HadDiagsInLastCallback =
false;
1220 FS.Files[
testPath(
".clang-tidy")] = R
"(
1221 Checks: -*,bugprone-use-after-move,llvm-header-guard
1223 MockCompilationDatabase CDB;
1224 std::vector<TidyProvider> Stack;
1228 CDB.ExtraClangFlags = {"-xc++"};
1230 Opts.ClangTidyProvider = Provider;
1232 const char *SourceContents = R
"cpp(
1233 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1234 namespace std { Foo&& move(Foo&); }
1237 Foo y = std::move(x);
1240 Server.addDocument(testPath("foo.h"), SourceContents);
1241 ASSERT_TRUE(Server.blockUntilIdleForTest());
1245TEST(ClangdServer, MemoryUsageTest) {
1247 MockCompilationDatabase CDB;
1251 Server.addDocument(FooCpp,
"");
1252 ASSERT_TRUE(Server.blockUntilIdleForTest());
1254 llvm::BumpPtrAllocator Alloc;
1255 MemoryTree MT(&Alloc);
1257 ASSERT_TRUE(MT.children().count(
"tuscheduler"));
1258 EXPECT_TRUE(MT.child(
"tuscheduler").children().count(FooCpp));
1261TEST(ClangdServer, RespectsTweakFormatting) {
1262 static constexpr const char *TweakID =
"ModuleTweak";
1263 static constexpr const char *NewContents =
"{not;\nformatted;}";
1267 struct TweakContributingModule final :
public FeatureModule {
1268 struct ModuleTweak final :
public Tweak {
1269 const char *id()
const override {
return TweakID; }
1270 bool prepare(
const Selection &Sel)
override {
return true; }
1271 Expected<Effect> apply(
const Selection &Sel)
override {
1272 auto &SM = Sel.AST->getSourceManager();
1273 llvm::StringRef FilePath = SM.getFilename(Sel.Cursor);
1274 tooling::Replacements Reps;
1276 Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents)));
1277 auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps)));
1278 E.FormatEdits =
false;
1281 std::string title()
const override {
return id(); }
1282 llvm::StringLiteral kind()
const override {
1283 return llvm::StringLiteral(
"");
1287 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &
Out)
override {
1288 Out.emplace_back(
new ModuleTweak);
1293 MockCompilationDatabase CDB;
1295 FeatureModuleSet Set;
1296 Set.add(std::make_unique<TweakContributingModule>());
1297 Opts.FeatureModules = &Set;
1298 ClangdServer Server(CDB, FS, Opts);
1301 Server.addDocument(FooCpp,
"");
1302 ASSERT_TRUE(Server.blockUntilIdleForTest());
1306 Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected<Tweak::Effect>
E) {
1307 ASSERT_TRUE(
static_cast<bool>(
E));
1308 EXPECT_THAT(llvm::cantFail(
E->ApplyEdits.lookup(FooCpp).apply()),
1315TEST(ClangdServer, InactiveRegions) {
1316 struct InactiveRegionsCallback : ClangdServer::Callbacks {
1317 std::vector<std::vector<Range>> FoundInactiveRegions;
1319 void onInactiveRegionsReady(PathRef FIle,
1320 std::vector<Range> InactiveRegions)
override {
1321 FoundInactiveRegions.push_back(std::move(InactiveRegions));
1326 MockCompilationDatabase CDB;
1327 CDB.ExtraClangFlags.push_back(
"-DCMDMACRO");
1329 Opts.PublishInactiveRegions =
true;
1331 ClangdServer Server(CDB, FS, Opts, &
Callback);
1332 Annotations Source(R
"cpp(
1333#define PREAMBLEMACRO 42
1334#if PREAMBLEMACRO > 40
1337$inactive1[[ #define INACTIVE]]
1341$inactive2[[ int inactiveInt;]]
1345$inactive3[[ int inactiveInt2;]]
1346#elif PREAMBLEMACRO > 0
1350$inactive4[[ int inactiveInt3;]]
1353#endif // empty inactive range, gets dropped
1355 Server.addDocument(testPath("foo.cpp"), Source.code());
1356 ASSERT_TRUE(Server.blockUntilIdleForTest());
1357 EXPECT_THAT(
Callback.FoundInactiveRegions,
1358 ElementsAre(ElementsAre(
1359 Source.range(
"inactive1"), Source.range(
"inactive2"),
1360 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.