25#include "clang/Config/config.h"
26#include "clang/Sema/CodeCompleteConsumer.h"
27#include "clang/Tooling/ArgumentsAdjusters.h"
28#include "clang/Tooling/Core/Replacement.h"
29#include "llvm/ADT/ArrayRef.h"
30#include "llvm/ADT/SmallVector.h"
31#include "llvm/ADT/StringMap.h"
32#include "llvm/ADT/StringRef.h"
33#include "llvm/Config/llvm-config.h"
34#include "llvm/Support/Allocator.h"
35#include "llvm/Support/Error.h"
36#include "llvm/Support/Path.h"
37#include "llvm/Support/Regex.h"
38#include "llvm/Support/VirtualFileSystem.h"
39#include "llvm/Testing/Support/Error.h"
40#include "gmock/gmock.h"
41#include "gtest/gtest.h"
56using ::testing::AllOf;
57using ::testing::ElementsAre;
58using ::testing::Field;
59using ::testing::IsEmpty;
61using ::testing::SizeIs;
62using ::testing::UnorderedElementsAre;
65 return arg.PreferredDeclaration ==
69bool diagsContainErrors(
const std::vector<Diag> &Diagnostics) {
70 for (
auto D : Diagnostics) {
71 if (
D.Severity == DiagnosticsEngine::Error ||
72 D.Severity == DiagnosticsEngine::Fatal)
80 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
81 llvm::ArrayRef<Diag> Diagnostics)
override {
82 bool HadError = diagsContainErrors(Diagnostics);
83 std::lock_guard<std::mutex> Lock(Mutex);
84 HadErrorInLastDiags = HadError;
87 bool hadErrorInLastDiags() {
88 std::lock_guard<std::mutex> Lock(Mutex);
89 return HadErrorInLastDiags;
94 bool HadErrorInLastDiags =
false;
101 void onDiagnosticsReady(
PathRef File, llvm::StringRef Version,
102 llvm::ArrayRef<Diag> Diagnostics)
override {
103 bool HadError = diagsContainErrors(Diagnostics);
105 std::lock_guard<std::mutex> Lock(Mutex);
106 LastDiagsHadError[
File] = HadError;
112 std::vector<std::pair<Path, bool>> filesWithDiags()
const {
113 std::vector<std::pair<Path, bool>> Result;
114 std::lock_guard<std::mutex> Lock(Mutex);
115 for (
const auto &It : LastDiagsHadError)
116 Result.emplace_back(std::string(It.first()), It.second);
121 std::lock_guard<std::mutex> Lock(Mutex);
122 LastDiagsHadError.clear();
126 mutable std::mutex Mutex;
127 llvm::StringMap<bool> LastDiagsHadError;
131std::string replacePtrsInDump(std::string
const &Dump) {
132 llvm::Regex RE(
"0x[0-9a-fA-F]+");
133 llvm::SmallVector<llvm::StringRef, 1> Matches;
134 llvm::StringRef Pending = Dump;
137 while (RE.match(Pending, &Matches)) {
138 assert(Matches.size() == 1 &&
"Exactly one match expected");
139 auto MatchPos = Matches[0].data() - Pending.data();
141 Result += Pending.take_front(MatchPos);
142 Pending = Pending.drop_front(MatchPos + Matches[0].size());
152 Server.customAction(
File,
"DumpAST", [&](llvm::Expected<InputsAndAST>
AST) {
154 llvm::raw_string_ostream ResultOS(Result);
155 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS,
true);
157 llvm::consumeError(
AST.takeError());
170std::string parseSourceAndDumpAST(
171 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
172 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
173 bool ExpectErrors =
false) {
175 ErrorCheckingCallbacks DiagConsumer;
178 for (
const auto &FileWithContents : ExtraFiles)
179 FS.Files[
testPath(FileWithContents.first)] =
180 std::string(FileWithContents.second);
182 auto SourceFilename =
testPath(SourceFileRelPath);
183 Server.addDocument(SourceFilename, SourceContents);
184 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
185 EXPECT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
186 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
190TEST(ClangdServerTest, Parse) {
194 auto Empty = parseSourceAndDumpAST(
"foo.cpp",
"");
195 auto OneDecl = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
196 auto SomeDecls = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
197 EXPECT_NE(
Empty, OneDecl);
198 EXPECT_NE(
Empty, SomeDecls);
199 EXPECT_NE(SomeDecls, OneDecl);
201 auto Empty2 = parseSourceAndDumpAST(
"foo.cpp",
"");
202 auto OneDecl2 = parseSourceAndDumpAST(
"foo.cpp",
"int a;");
203 auto SomeDecls2 = parseSourceAndDumpAST(
"foo.cpp",
"int a; int b; int c;");
204 EXPECT_EQ(
Empty, Empty2);
205 EXPECT_EQ(OneDecl, OneDecl2);
206 EXPECT_EQ(SomeDecls, SomeDecls2);
209TEST(ClangdServerTest, ParseWithHeader) {
210 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {},
212 parseSourceAndDumpAST(
"foo.cpp",
"#include \"foo.h\"", {{
"foo.h",
""}},
215 const auto *SourceContents = R
"cpp(
219 parseSourceAndDumpAST("foo.cpp", SourceContents, {{
"foo.h",
""}},
221 parseSourceAndDumpAST(
"foo.cpp", SourceContents, {{
"foo.h",
"int a;"}},
225TEST(ClangdServerTest, Reparse) {
227 ErrorCheckingCallbacks DiagConsumer;
231 const auto *SourceContents = R
"cpp(
238 FS.Files[
testPath(
"foo.h")] =
"int a;";
239 FS.Files[FooCpp] = SourceContents;
241 Server.addDocument(FooCpp, SourceContents);
242 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
243 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
244 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
246 Server.addDocument(FooCpp,
"");
247 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
248 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
249 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
251 Server.addDocument(FooCpp, SourceContents);
252 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
253 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
254 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
256 EXPECT_EQ(DumpParse1, DumpParse2);
257 EXPECT_NE(DumpParse1, DumpParseEmpty);
260TEST(ClangdServerTest, ReparseOnHeaderChange) {
262 ErrorCheckingCallbacks DiagConsumer;
266 const auto *SourceContents = R
"cpp(
274 FS.Files[FooH] =
"int a;";
275 FS.Files[FooCpp] = SourceContents;
277 Server.addDocument(FooCpp, SourceContents);
278 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
279 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
280 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
283 Server.addDocument(FooCpp, SourceContents);
284 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
285 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
286 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
288 FS.Files[FooH] =
"int a;";
289 Server.addDocument(FooCpp, SourceContents);
290 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
291 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
292 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
294 EXPECT_EQ(DumpParse1, DumpParse2);
295 EXPECT_NE(DumpParse1, DumpParseDifferent);
298TEST(ClangdServerTest, PropagatesContexts) {
304 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
305 Got = Context::current().getExisting(Secret);
310 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
311 llvm::ArrayRef<Diag> Diagnostics)
override {
312 Got = Context::current().getExisting(Secret);
322 Server.addDocument(
testPath(
"foo.cpp"),
"void main(){}");
324 ASSERT_TRUE(Server.blockUntilIdleForTest());
325 EXPECT_EQ(FS.Got, 42);
326 EXPECT_EQ(Callbacks.Got, 42);
329TEST(ClangdServerTest, RespectsConfig) {
341 std::vector<config::CompiledFragment>
342 getFragments(
const config::Params &,
343 config::DiagnosticCallback DC)
const override {
345 F.If.PathMatch.emplace_back(
".*foo.cc");
346 F.CompileFlags.Add.emplace_back(
"-DFOO=1");
347 return {std::move(F).compile(DC)};
352 Opts.ContextProvider =
359 Server.addDocument(
testPath(
"foo.cc"), Example.code());
361 ASSERT_TRUE(
bool(Result)) << Result.takeError();
362 ASSERT_THAT(*Result, SizeIs(1));
363 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
365 Server.addDocument(
testPath(
"bar.cc"), Example.code());
367 ASSERT_TRUE(
bool(Result)) << Result.takeError();
368 ASSERT_THAT(*Result, SizeIs(1));
369 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
372TEST(ClangdServerTest, PropagatesVersion) {
376 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
377 llvm::ArrayRef<Diag> Diagnostics)
override {
380 std::string Got =
"";
386 EXPECT_EQ(Callbacks.Got,
"42");
391TEST(ClangdServerTest, SearchLibDir) {
394 ErrorCheckingCallbacks DiagConsumer;
397 {
"-xc++",
"--target=x86_64-unknown-linux-gnu",
398 "-m64",
"--gcc-toolchain=/randomusr",
399 "-stdlib=libstdc++"});
403 SmallString<8> Version(
"4.9.3");
406 SmallString<64> LibDir(
"/randomusr/lib/gcc/x86_64-linux-gnu");
407 llvm::sys::path::append(LibDir, Version);
411 SmallString<64> MockLibFile;
412 llvm::sys::path::append(MockLibFile, LibDir,
"64",
"crtbegin.o");
413 FS.Files[MockLibFile] =
"";
415 SmallString<64> IncludeDir(
"/randomusr/include/c++");
416 llvm::sys::path::append(IncludeDir, Version);
418 SmallString<64> StringPath;
419 llvm::sys::path::append(StringPath, IncludeDir,
"string");
420 FS.Files[StringPath] =
"class mock_string {};";
423 const auto *SourceContents = R
"cpp(
427 FS.Files[FooCpp] = SourceContents;
430 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
432 const auto *SourceContentsWithError = R
"cpp(
437 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
441TEST(ClangdServerTest, ForceReparseCompileCommand) {
443 ErrorCheckingCallbacks DiagConsumer;
448 const auto *SourceContents1 = R
"cpp(
452 const auto *SourceContents2 = R
"cpp(
457 FS.Files[FooCpp] = "";
460 CDB.ExtraClangFlags = {
"-xc"};
462 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
464 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
467 CDB.ExtraClangFlags = {
"-xc++"};
469 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
472 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
474 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
477TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
479 ErrorCheckingCallbacks DiagConsumer;
484 const auto *SourceContents = R
"cpp(
489int main() { return 0; }
491 FS.Files[FooCpp] = "";
494 CDB.ExtraClangFlags = {
"-DWITH_ERROR"};
496 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
499 CDB.ExtraClangFlags = {};
501 ASSERT_TRUE(Server.blockUntilIdleForTest());
502 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
505 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
509TEST(ClangdServerTest, ReparseOpenedFiles) {
512static void $one[[bob]]() {}
514static void $two[[bob]]() {}
517int main () { bo^b (); return 0; }
532 MultipleErrorCheckingCallbacks DiagConsumer;
539 FS.Files[FooCpp] =
"";
540 FS.Files[BarCpp] =
"";
541 FS.Files[BazCpp] =
"";
543 CDB.ExtraClangFlags = {
"-DMACRO=1"};
544 Server.addDocument(FooCpp, FooSource.code());
545 Server.addDocument(BarCpp, BarSource.code());
546 Server.addDocument(BazCpp, BazSource.code());
547 ASSERT_TRUE(Server.blockUntilIdleForTest());
549 EXPECT_THAT(DiagConsumer.filesWithDiags(),
550 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
true),
551 Pair(BazCpp,
false)));
554 EXPECT_TRUE(
bool(Locations));
555 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"one"))));
558 CDB.ExtraClangFlags.clear();
559 DiagConsumer.clear();
560 Server.removeDocument(BazCpp);
561 Server.addDocument(FooCpp, FooSource.code());
562 Server.addDocument(BarCpp, BarSource.code());
563 ASSERT_TRUE(Server.blockUntilIdleForTest());
565 EXPECT_THAT(DiagConsumer.filesWithDiags(),
566 UnorderedElementsAre(Pair(FooCpp,
false), Pair(BarCpp,
false)));
569 EXPECT_TRUE(
bool(Locations));
570 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range(
"two"))));
573MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds,
"") {
574 return arg.first() == Name &&
575 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
577 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
578 std::tie(PreambleBuilds, ASTBuilds);
581TEST(ClangdServerTest, FileStats) {
583 ErrorCheckingCallbacks DiagConsumer;
588 const auto *SourceContents = R
"cpp(
595 FS.Files[FooCpp] =
"";
596 FS.Files[BarCpp] =
"";
598 EXPECT_THAT(Server.fileStats(), IsEmpty());
600 Server.addDocument(FooCpp, SourceContents);
601 Server.addDocument(BarCpp, SourceContents);
602 ASSERT_TRUE(Server.blockUntilIdleForTest());
604 EXPECT_THAT(Server.fileStats(),
605 UnorderedElementsAre(Stats(FooCpp,
true, 1, 1),
606 Stats(BarCpp,
true, 1, 1)));
608 Server.removeDocument(FooCpp);
609 ASSERT_TRUE(Server.blockUntilIdleForTest());
610 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp,
true, 1, 1)));
612 Server.removeDocument(BarCpp);
613 ASSERT_TRUE(Server.blockUntilIdleForTest());
614 EXPECT_THAT(Server.fileStats(), IsEmpty());
617TEST(ClangdServerTest, InvalidCompileCommand) {
619 ErrorCheckingCallbacks DiagConsumer;
626 CDB.ExtraClangFlags.push_back(
"-###");
631 EXPECT_EQ(
dumpAST(Server, FooCpp),
"<no-ast>");
635 clangd::RenameOptions()));
640 clangd::CodeCompleteOptions()))
642 ElementsAre(
Field(&CodeCompletion::Name,
"int"),
643 Field(&CodeCompletion::Name,
"main")));
646TEST(ClangdThreadingTest, StressTest) {
648 static const unsigned FilesCount = 5;
649 const unsigned RequestsCount = 500;
653 const unsigned BlockingRequestInterval = 40;
655 const auto *SourceContentsWithoutErrors = R
"cpp(
662 const auto *SourceContentsWithErrors = R
"cpp(
672 unsigned MaxLineForFileRequests = 7;
673 unsigned MaxColumnForFileRequests = 10;
675 std::vector<std::string> FilePaths;
677 for (
unsigned I = 0; I < FilesCount; ++I) {
678 std::string Name = std::string(
"Foo") + std::to_string(I) +
".cpp";
680 FilePaths.push_back(
testPath(Name));
684 unsigned HitsWithoutErrors = 0;
685 unsigned HitsWithErrors = 0;
686 bool HadErrorsInLastDiags =
false;
691 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
693 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
694 llvm::ArrayRef<Diag> Diagnostics)
override {
695 StringRef FileIndexStr = llvm::sys::path::stem(File);
696 ASSERT_TRUE(FileIndexStr.consume_front(
"Foo"));
698 unsigned long FileIndex = std::stoul(FileIndexStr.str());
700 bool HadError = diagsContainErrors(Diagnostics);
702 std::lock_guard<std::mutex> Lock(Mutex);
704 Stats[FileIndex].HitsWithErrors++;
706 Stats[FileIndex].HitsWithoutErrors++;
707 Stats[FileIndex].HadErrorsInLastDiags = HadError;
710 std::vector<FileStat> takeFileStats() {
711 std::lock_guard<std::mutex> Lock(Mutex);
712 return std::move(Stats);
717 std::vector<FileStat> Stats;
720 struct RequestStats {
721 unsigned RequestsWithoutErrors = 0;
722 unsigned RequestsWithErrors = 0;
723 bool LastContentsHadErrors =
false;
724 bool FileIsRemoved =
true;
727 std::vector<RequestStats> ReqStats;
728 ReqStats.reserve(FilesCount);
730 ReqStats.emplace_back();
732 TestDiagConsumer DiagConsumer;
738 std::random_device RandGen;
740 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
743 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
745 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
746 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
749 auto UpdateStatsOnAddDocument = [&](
unsigned FileIndex,
bool HadErrors) {
753 ++Stats.RequestsWithErrors;
755 ++Stats.RequestsWithoutErrors;
756 Stats.LastContentsHadErrors = HadErrors;
757 Stats.FileIsRemoved =
false;
760 auto UpdateStatsOnRemoveDocument = [&](
unsigned FileIndex) {
763 Stats.FileIsRemoved =
true;
766 auto AddDocument = [&](
unsigned FileIndex,
bool SkipCache) {
767 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
769 ShouldHaveErrors ? SourceContentsWithErrors
770 : SourceContentsWithoutErrors);
771 UpdateStatsOnAddDocument(
FileIndex, ShouldHaveErrors);
775 auto AddDocumentRequest = [&]() {
776 unsigned FileIndex = FileIndexDist(RandGen);
780 auto ForceReparseRequest = [&]() {
781 unsigned FileIndex = FileIndexDist(RandGen);
785 auto RemoveDocumentRequest = [&]() {
786 unsigned FileIndex = FileIndexDist(RandGen);
791 Server.removeDocument(FilePaths[
FileIndex]);
795 auto CodeCompletionRequest = [&]() {
796 unsigned FileIndex = FileIndexDist(RandGen);
802 Pos.
line = LineDist(RandGen);
803 Pos.character = ColumnDist(RandGen);
811 clangd::CodeCompleteOptions()));
814 auto LocateSymbolRequest = [&]() {
815 unsigned FileIndex = FileIndexDist(RandGen);
821 Pos.
line = LineDist(RandGen);
822 Pos.character = ColumnDist(RandGen);
827 std::vector<std::function<void()>> AsyncRequests = {
828 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
829 std::vector<std::function<void()>> BlockingRequests = {
830 CodeCompletionRequest, LocateSymbolRequest};
833 std::uniform_int_distribution<int> AsyncRequestIndexDist(
834 0, AsyncRequests.size() - 1);
835 std::uniform_int_distribution<int> BlockingRequestIndexDist(
836 0, BlockingRequests.size() - 1);
837 for (
unsigned I = 1; I <= RequestsCount; ++I) {
838 if (I % BlockingRequestInterval != 0) {
840 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
841 AsyncRequests[RequestIndex]();
844 auto RequestIndex = BlockingRequestIndexDist(RandGen);
845 BlockingRequests[RequestIndex]();
848 ASSERT_TRUE(Server.blockUntilIdleForTest());
852 std::vector<FileStat> Stats = DiagConsumer.takeFileStats();
853 for (
unsigned I = 0; I < FilesCount; ++I) {
854 if (!ReqStats[I].FileIsRemoved) {
855 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
856 ReqStats[I].LastContentsHadErrors);
859 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
860 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
864TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
867 std::atomic<int> Count = {0};
869 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
870 : StartSecondReparse(std::move(StartSecondReparse)) {}
872 void onDiagnosticsReady(PathRef, llvm::StringRef,
873 llvm::ArrayRef<Diag>)
override {
875 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
876 ASSERT_TRUE(Lock.owns_lock())
877 <<
"Detected concurrent onDiagnosticsReady calls for the same file.";
882 FirstRequest =
false;
883 StartSecondReparse.set_value();
885 std::this_thread::sleep_for(std::chrono::milliseconds(50));
891 bool FirstRequest =
true;
892 std::promise<void> StartSecondReparse;
895 const auto *SourceContentsWithoutErrors = R
"cpp(
902 const auto *SourceContentsWithErrors = R
"cpp(
911 FS.
Files[FooCpp] =
"";
913 std::promise<void> StartSecondPromise;
914 std::future<void> StartSecond = StartSecondPromise.get_future();
916 NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise));
919 Server.addDocument(FooCpp, SourceContentsWithErrors);
921 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
922 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
923 ASSERT_EQ(DiagConsumer.Count, 2);
926TEST(ClangdServerTest, FormatCode) {
928 ErrorCheckingCallbacks DiagConsumer;
933 std::string Code = R
"cpp(
939 std::string Expected = R"cpp(
945 FS.Files[Path] = Code;
949 EXPECT_TRUE(
static_cast<bool>(Replaces));
950 auto Changed = tooling::applyAllReplacements(Code, *Replaces);
951 EXPECT_TRUE(
static_cast<bool>(
Changed));
955TEST(ClangdServerTest, ChangedHeaderFromISystem) {
957 ErrorCheckingCallbacks DiagConsumer;
961 auto SourcePath =
testPath(
"source/foo.cpp");
962 auto HeaderPath =
testPath(
"headers/foo.h");
963 FS.Files[HeaderPath] =
"struct X { int bar; };";
970 CDB.ExtraClangFlags.push_back("-xc++");
971 CDB.ExtraClangFlags.push_back(
"-isystem" +
testPath(
"headers"));
974 auto Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
975 clangd::CodeCompleteOptions()))
977 EXPECT_THAT(Completions, ElementsAre(
Field(&CodeCompletion::Name,
"bar")));
980 FS.Files[HeaderPath] =
"struct X { int bar; int baz; };";
982 Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
983 clangd::CodeCompleteOptions()))
986 EXPECT_THAT(Completions, ElementsAre(
Field(&CodeCompletion::Name,
"bar"),
987 Field(&CodeCompletion::Name,
"baz")));
994TEST(ClangdTests, PreambleVFSStatCache) {
996 llvm::StringMap<unsigned> &CountStats;
1000 llvm::StringMap<std::string> Files;
1002 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1003 : CountStats(CountStats) {}
1006 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl()
const override {
1007 class StatRecordingVFS :
public llvm::vfs::ProxyFileSystem {
1009 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1010 llvm::StringMap<unsigned> &CountStats)
1011 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1013 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1014 openFileForRead(
const Twine &Path)
override {
1015 ++CountStats[llvm::sys::path::filename(
Path.str())];
1016 return ProxyFileSystem::openFileForRead(Path);
1018 llvm::ErrorOr<llvm::vfs::Status> status(
const Twine &Path)
override {
1019 ++CountStats[llvm::sys::path::filename(
Path.str())];
1020 return ProxyFileSystem::status(Path);
1024 llvm::StringMap<unsigned> &CountStats;
1027 return IntrusiveRefCntPtr<StatRecordingVFS>(
1028 new StatRecordingVFS(
buildTestFS(Files), CountStats));
1032 llvm::StringMap<unsigned> CountStats;
1033 StatRecordingFS FS(CountStats);
1034 ErrorCheckingCallbacks DiagConsumer;
1038 auto SourcePath =
testPath(
"foo.cpp");
1039 auto HeaderPath =
testPath(
"foo.h");
1040 FS.Files[HeaderPath] =
"struct TestSym {};";
1050 unsigned Before = CountStats[
"foo.h"];
1051 EXPECT_GT(Before, 0u);
1052 auto Completions = cantFail(
runCodeComplete(Server, SourcePath, Code.point(),
1053 clangd::CodeCompleteOptions()))
1055 EXPECT_EQ(CountStats[
"foo.h"], Before);
1056 EXPECT_THAT(Completions,
1057 ElementsAre(
Field(&CodeCompletion::Name,
"TestSym")));
1061TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) {
1063 ErrorCheckingCallbacks DiagConsumer;
1069 namespace ns { int xyz; }
1074 FS.Files[FooCpp] = FooCpp;
1076 auto Opts = clangd::CodeCompleteOptions();
1077 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1080 CDB.ExtraClangFlags = {
"-###"};
1081 Server.addDocument(FooCpp, Code.code());
1082 ASSERT_TRUE(Server.blockUntilIdleForTest());
1083 auto Res = cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts));
1084 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1086 EXPECT_THAT(Res.Completions,
1087 ElementsAre(AllOf(
Field(&CodeCompletion::Name,
"xyz"),
1088 Field(&CodeCompletion::Scope,
""))));
1091 CDB.ExtraClangFlags = {
"-std=c++11"};
1092 Server.addDocument(FooCpp, Code.code());
1093 ASSERT_TRUE(Server.blockUntilIdleForTest());
1095 cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1096 ElementsAre(AllOf(
Field(&CodeCompletion::Name,
"xyz"),
1097 Field(&CodeCompletion::Scope,
"ns::"))));
1100 Opts.RunParser = CodeCompleteOptions::NeverParse;
1102 cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1103 ElementsAre(AllOf(
Field(&CodeCompletion::Name,
"xyz"),
1104 Field(&CodeCompletion::Scope,
""))));
1107TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1109 ErrorCheckingCallbacks DiagConsumer;
1113 DelayedCompilationDatabase(Notification &CanReturnCommand)
1114 : CanReturnCommand(CanReturnCommand) {}
1116 std::optional<tooling::CompileCommand>
1117 getCompileCommand(PathRef File)
const override {
1120 CanReturnCommand.wait();
1121 auto FileName = llvm::sys::path::filename(File);
1122 std::vector<std::string>
CommandLine = {
"clangd",
"-ffreestanding",
1124 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
1128 std::vector<std::string> ExtraClangFlags;
1131 Notification &CanReturnCommand;
1135 DelayedCompilationDatabase CDB(CanReturnCommand);
1140 namespace ns { int xyz; }
1145 FS.Files[FooCpp] = FooCpp;
1146 Server.addDocument(FooCpp, Code.code());
1150 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1151 auto Opts = clangd::CodeCompleteOptions();
1152 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1154 auto Res = cantFail(
runCodeComplete(Server, FooCpp, Code.point(), Opts));
1155 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1157 CanReturnCommand.notify();
1158 ASSERT_TRUE(Server.blockUntilIdleForTest());
1160 clangd::CodeCompleteOptions()))
1162 ElementsAre(AllOf(
Field(&CodeCompletion::Name,
"xyz"),
1163 Field(&CodeCompletion::Scope,
"ns::"))));
1166TEST(ClangdServerTest, CustomAction) {
1171 Server.addDocument(
testPath(
"foo.cc"),
"void x();");
1172 Decl::Kind XKind = Decl::TranslationUnit;
1178 EXPECT_EQ(XKind, Decl::Function);
1183#if !defined(__has_feature) || !__has_feature(address_sanitizer)
1184TEST(ClangdServerTest, TestStackOverflow) {
1186 ErrorCheckingCallbacks DiagConsumer;
1190 const char *SourceContents = R
"cpp(
1191 constexpr int foo() { return foo(); }
1192 static_assert(foo());
1196 FS.Files[FooCpp] = SourceContents;
1198 Server.addDocument(FooCpp, SourceContents);
1199 ASSERT_TRUE(Server.blockUntilIdleForTest()) <<
"Waiting for diagnostics";
1202 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
1209 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
1210 llvm::ArrayRef<Diag> Diagnostics)
override {
1211 std::lock_guard<std::mutex> Lock(Mutex);
1212 HadDiagsInLastCallback = !Diagnostics.empty();
1216 bool HadDiagsInLastCallback =
false;
1223 Checks: -*,bugprone-use-after-move,llvm-header-guard
1226 std::vector<TidyProvider> Stack;
1230 CDB.ExtraClangFlags = {"-xc++"};
1233 Modules.
add(std::make_unique<ClangTidyFeatureModule>(std::move(Provider)));
1234 Opts.FeatureModules = &Modules;
1236 const char *SourceContents = R
"cpp(
1237 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1238 namespace std { Foo&& move(Foo&); }
1241 Foo y = std::move(x);
1244 Server.addDocument(testPath("foo.h"), SourceContents);
1245 ASSERT_TRUE(Server.blockUntilIdleForTest());
1246 EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback);
1255 Server.addDocument(FooCpp,
"");
1256 ASSERT_TRUE(Server.blockUntilIdleForTest());
1258 llvm::BumpPtrAllocator Alloc;
1261 ASSERT_TRUE(MT.children().count(
"tuscheduler"));
1262 EXPECT_TRUE(MT.child(
"tuscheduler").children().count(FooCpp));
1266 static constexpr const char *TweakID =
"ModuleTweak";
1267 static constexpr const char *NewContents =
"{not;\nformatted;}";
1271 struct TweakContributingModule final :
public FeatureModule {
1272 struct ModuleTweak final :
public Tweak {
1273 const char *id()
const override {
return TweakID; }
1274 bool prepare(
const Selection &Sel)
override {
return true; }
1275 Expected<Effect> apply(
const Selection &Sel)
override {
1276 auto &SM = Sel.AST->getSourceManager();
1277 llvm::StringRef FilePath = SM.getFilename(Sel.Cursor);
1278 tooling::Replacements Reps;
1280 Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents)));
1281 auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps)));
1282 E.FormatEdits =
false;
1285 std::string title()
const override {
return id(); }
1286 llvm::StringLiteral kind()
const override {
1287 return llvm::StringLiteral(
"");
1291 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out)
override {
1292 Out.emplace_back(
new ModuleTweak);
1300 Set.
add(std::make_unique<TweakContributingModule>());
1301 Opts.FeatureModules = &Set;
1305 Server.addDocument(FooCpp,
"");
1306 ASSERT_TRUE(Server.blockUntilIdleForTest());
1310 Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected<Tweak::Effect> E) {
1311 ASSERT_TRUE(
static_cast<bool>(E));
1312 EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()),
1321 std::vector<std::vector<Range>> FoundInactiveRegions;
1323 void onInactiveRegionsReady(PathRef FIle,
1324 std::vector<Range> InactiveRegions)
override {
1325 FoundInactiveRegions.push_back(std::move(InactiveRegions));
1333 Opts.PublishInactiveRegions =
true;
1337#define PREAMBLEMACRO 42
1338#if PREAMBLEMACRO > 40
1341$inactive1[[ #define INACTIVE]]
1345$inactive2[[ int inactiveInt;]]
1349$inactive3[[ int inactiveInt2;]]
1350#elif PREAMBLEMACRO > 0
1354$inactive4[[ int inactiveInt3;]]
1357#endif // empty inactive range, gets dropped
1359 Server.addDocument(testPath("foo.cpp"), Source.code());
1360 ASSERT_TRUE(Server.blockUntilIdleForTest());
1361 EXPECT_THAT(
Callback.FoundInactiveRegions,
1362 ElementsAre(ElementsAre(
1363 Source.range(
"inactive1"), Source.range(
"inactive2"),
1364 Source.range(
"inactive3"), Source.range(
"inactive4"))));
#define EXPECT_ERROR(expectedValue)
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Interface with hooks for users of ClangdServer to be notified of events.
Manages a collection of source files and derived data (ASTs, indexes), and provides language-aware fe...
static std::function< Context(PathRef)> createConfiguredContextProvider(const config::Provider *Provider, ClangdServer::Callbacks *)
Creates a context provider that loads and installs config.
static Options optsForTest()
A FeatureModuleSet is a collection of feature modules installed in clangd.
void add(std::unique_ptr< FeatureModule > M)
A FeatureModule contributes a vertical feature to clangd.
This manages symbols from files and an in-memory index on all symbols.
Provides compilation arguments used for parsing C and C++ files.
Values in a Context are indexed by typed keys.
std::vector< std::string > ExtraClangFlags
llvm::StringMap< std::string > Files
A threadsafe flag that is initially clear.
Wraps another compilation database, and supports overriding the commands using an in-memory mapping.
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
WithContextValue extends Context::current() with a single value.
A source of configuration fragments.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
@ Changed
The file got changed.
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
TidyProvider combine(std::vector< TidyProvider > Providers)
ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, const ASTContext &Ctx)
llvm::Expected< RenameResult > runRename(ClangdServer &Server, PathRef File, Position Pos, llvm::StringRef NewName, const RenameOptions &RenameOpts)
MATCHER_P2(hasFlag, Flag, Path, "")
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
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)
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::StringRef PathRef
A typedef to represent a ref to file path.
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.
std::string Path
A typedef to represent a file path.
llvm::Expected< tooling::Replacements > runFormatFile(ClangdServer &Server, PathRef File, const std::vector< Range > &Rngs)
llvm::Error runCustomAction(ClangdServer &Server, PathRef File, llvm::function_ref< void(InputsAndAST)> Action)
llvm::Expected< std::vector< DocumentHighlight > > runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static CommandMangler forTests()
A tree that can be used to represent memory usage of nested components while preserving the hierarchy...
int line
Line position in a document (zero-based).
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.