1 //===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "Annotations.h"
10 #include "ClangdServer.h"
11 #include "CodeComplete.h"
12 #include "CompileCommands.h"
13 #include "ConfigFragment.h"
14 #include "GlobalCompilationDatabase.h"
19 #include "TidyProvider.h"
20 #include "refactor/Tweak.h"
21 #include "support/MemoryTree.h"
22 #include "support/Path.h"
23 #include "support/Threading.h"
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/SmallVector.h"
29 #include "llvm/ADT/StringMap.h"
30 #include "llvm/ADT/StringRef.h"
31 #include "llvm/Support/Allocator.h"
32 #include "llvm/Support/Error.h"
33 #include "llvm/Support/Path.h"
34 #include "llvm/Support/Regex.h"
35 #include "llvm/Support/VirtualFileSystem.h"
36 #include "llvm/Testing/Support/Error.h"
37 #include "gmock/gmock.h"
38 #include "gtest/gtest.h"
53 using ::testing::AllOf;
54 using ::testing::ElementsAre;
55 using ::testing::Field;
56 using ::testing::IsEmpty;
57 using ::testing::Pair;
58 using ::testing::SizeIs;
59 using ::testing::UnorderedElementsAre;
61 MATCHER_P2(DeclAt, File, Range, "") {
62 return arg.PreferredDeclaration ==
63 Location{URIForFile::canonicalize(File, testRoot()), Range};
66 bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
67 for (auto D : Diagnostics) {
68 if (D.Severity == DiagnosticsEngine::Error ||
69 D.Severity == DiagnosticsEngine::Fatal)
75 class ErrorCheckingCallbacks : public ClangdServer::Callbacks {
77 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
78 std::vector<Diag> Diagnostics) override {
79 bool HadError = diagsContainErrors(Diagnostics);
80 std::lock_guard<std::mutex> Lock(Mutex);
81 HadErrorInLastDiags = HadError;
84 bool hadErrorInLastDiags() {
85 std::lock_guard<std::mutex> Lock(Mutex);
86 return HadErrorInLastDiags;
91 bool HadErrorInLastDiags = false;
94 /// For each file, record whether the last published diagnostics contained at
96 class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
98 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
99 std::vector<Diag> Diagnostics) override {
100 bool HadError = diagsContainErrors(Diagnostics);
102 std::lock_guard<std::mutex> Lock(Mutex);
103 LastDiagsHadError[File] = HadError;
106 /// Exposes all files consumed by onDiagnosticsReady in an unspecified order.
107 /// For each file, a bool value indicates whether the last diagnostics
108 /// contained an error.
109 std::vector<std::pair<Path, bool>> filesWithDiags() const {
110 std::vector<std::pair<Path, bool>> Result;
111 std::lock_guard<std::mutex> Lock(Mutex);
112 for (const auto &It : LastDiagsHadError)
113 Result.emplace_back(std::string(It.first()), It.second);
118 std::lock_guard<std::mutex> Lock(Mutex);
119 LastDiagsHadError.clear();
123 mutable std::mutex Mutex;
124 llvm::StringMap<bool> LastDiagsHadError;
127 /// Replaces all patterns of the form 0x123abc with spaces
128 std::string replacePtrsInDump(std::string const &Dump) {
129 llvm::Regex RE("0x[0-9a-fA-F]+");
130 llvm::SmallVector<llvm::StringRef, 1> Matches;
131 llvm::StringRef Pending = Dump;
134 while (RE.match(Pending, &Matches)) {
135 assert(Matches.size() == 1 && "Exactly one match expected");
136 auto MatchPos = Matches[0].data() - Pending.data();
138 Result += Pending.take_front(MatchPos);
139 Pending = Pending.drop_front(MatchPos + Matches[0].size());
146 std::string dumpAST(ClangdServer &Server, PathRef File) {
149 Server.customAction(File, "DumpAST", [&](llvm::Expected<InputsAndAST> AST) {
151 llvm::raw_string_ostream ResultOS(Result);
152 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS, true);
154 llvm::consumeError(AST.takeError());
163 std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) {
164 return replacePtrsInDump(dumpAST(Server, File));
167 std::string parseSourceAndDumpAST(
168 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
169 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
170 bool ExpectErrors = false) {
172 ErrorCheckingCallbacks DiagConsumer;
173 MockCompilationDatabase CDB;
174 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
175 for (const auto &FileWithContents : ExtraFiles)
176 FS.Files[testPath(FileWithContents.first)] =
177 std::string(FileWithContents.second);
179 auto SourceFilename = testPath(SourceFileRelPath);
180 Server.addDocument(SourceFilename, SourceContents);
181 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
182 EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
183 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
187 TEST(ClangdServerTest, Parse) {
188 // FIXME: figure out a stable format for AST dumps, so that we can check the
189 // output of the dump itself is equal to the expected one, not just that it's
191 auto Empty = parseSourceAndDumpAST("foo.cpp", "");
192 auto OneDecl = parseSourceAndDumpAST("foo.cpp", "int a;");
193 auto SomeDecls = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
194 EXPECT_NE(Empty, OneDecl);
195 EXPECT_NE(Empty, SomeDecls);
196 EXPECT_NE(SomeDecls, OneDecl);
198 auto Empty2 = parseSourceAndDumpAST("foo.cpp", "");
199 auto OneDecl2 = parseSourceAndDumpAST("foo.cpp", "int a;");
200 auto SomeDecls2 = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
201 EXPECT_EQ(Empty, Empty2);
202 EXPECT_EQ(OneDecl, OneDecl2);
203 EXPECT_EQ(SomeDecls, SomeDecls2);
206 TEST(ClangdServerTest, ParseWithHeader) {
207 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {},
208 /*ExpectErrors=*/true);
209 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}},
210 /*ExpectErrors=*/false);
212 const auto *SourceContents = R"cpp(
216 parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", ""}},
217 /*ExpectErrors=*/true);
218 parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", "int a;"}},
219 /*ExpectErrors=*/false);
222 TEST(ClangdServerTest, Reparse) {
224 ErrorCheckingCallbacks DiagConsumer;
225 MockCompilationDatabase CDB;
226 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
228 const auto *SourceContents = R"cpp(
233 auto FooCpp = testPath("foo.cpp");
235 FS.Files[testPath("foo.h")] = "int a;";
236 FS.Files[FooCpp] = SourceContents;
238 Server.addDocument(FooCpp, SourceContents);
239 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
240 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
241 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
243 Server.addDocument(FooCpp, "");
244 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
245 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
246 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
248 Server.addDocument(FooCpp, SourceContents);
249 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
250 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
251 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
253 EXPECT_EQ(DumpParse1, DumpParse2);
254 EXPECT_NE(DumpParse1, DumpParseEmpty);
257 TEST(ClangdServerTest, ReparseOnHeaderChange) {
259 ErrorCheckingCallbacks DiagConsumer;
260 MockCompilationDatabase CDB;
261 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
263 const auto *SourceContents = R"cpp(
268 auto FooCpp = testPath("foo.cpp");
269 auto FooH = testPath("foo.h");
271 FS.Files[FooH] = "int a;";
272 FS.Files[FooCpp] = SourceContents;
274 Server.addDocument(FooCpp, SourceContents);
275 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
276 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
277 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
280 Server.addDocument(FooCpp, SourceContents);
281 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
282 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
283 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
285 FS.Files[FooH] = "int a;";
286 Server.addDocument(FooCpp, SourceContents);
287 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
288 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
289 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
291 EXPECT_EQ(DumpParse1, DumpParse2);
292 EXPECT_NE(DumpParse1, DumpParseDifferent);
295 TEST(ClangdServerTest, PropagatesContexts) {
296 static Key<int> Secret;
297 struct ContextReadingFS : public ThreadsafeFS {
301 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
302 Got = Context::current().getExisting(Secret);
303 return buildTestFS({});
306 struct Callbacks : public ClangdServer::Callbacks {
307 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
308 std::vector<Diag> Diagnostics) override {
309 Got = Context::current().getExisting(Secret);
313 MockCompilationDatabase CDB;
315 // Verify that the context is plumbed to the FS provider and diagnostics.
316 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
318 WithContextValue Entrypoint(Secret, 42);
319 Server.addDocument(testPath("foo.cpp"), "void main(){}");
321 ASSERT_TRUE(Server.blockUntilIdleForTest());
322 EXPECT_EQ(FS.Got, 42);
323 EXPECT_EQ(Callbacks.Got, 42);
326 TEST(ClangdServerTest, RespectsConfig) {
327 // Go-to-definition will resolve as marked if FOO is defined.
328 Annotations Example(R"cpp(
336 // Provide conditional config that defines FOO for foo.cc.
337 class ConfigProvider : public config::Provider {
338 std::vector<config::CompiledFragment>
339 getFragments(const config::Params &,
340 config::DiagnosticCallback DC) const override {
342 F.If.PathMatch.emplace_back(".*foo.cc");
343 F.CompileFlags.Add.emplace_back("-DFOO=1");
344 return {std::move(F).compile(DC)};
348 auto Opts = ClangdServer::optsForTest();
349 Opts.ContextProvider =
350 ClangdServer::createConfiguredContextProvider(&CfgProvider, nullptr);
351 OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
352 CommandMangler::forTests());
354 ClangdServer Server(CDB, FS, Opts);
355 // foo.cc sees the expected definition, as FOO is defined.
356 Server.addDocument(testPath("foo.cc"), Example.code());
357 auto Result = runLocateSymbolAt(Server, testPath("foo.cc"), Example.point());
358 ASSERT_TRUE(bool(Result)) << Result.takeError();
359 ASSERT_THAT(*Result, SizeIs(1));
360 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
361 // bar.cc gets a different result, as FOO is not defined.
362 Server.addDocument(testPath("bar.cc"), Example.code());
363 Result = runLocateSymbolAt(Server, testPath("bar.cc"), Example.point());
364 ASSERT_TRUE(bool(Result)) << Result.takeError();
365 ASSERT_THAT(*Result, SizeIs(1));
366 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
369 TEST(ClangdServerTest, PropagatesVersion) {
370 MockCompilationDatabase CDB;
372 struct Callbacks : public ClangdServer::Callbacks {
373 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
374 std::vector<Diag> Diagnostics) override {
377 std::string Got = "";
380 // Verify that the version is plumbed to diagnostics.
381 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
382 runAddDocument(Server, testPath("foo.cpp"), "void main(){}", "42");
383 EXPECT_EQ(Callbacks.Got, "42");
386 // Only enable this test on Unix
388 TEST(ClangdServerTest, SearchLibDir) {
389 // Checks that searches for GCC installation is done through vfs.
391 ErrorCheckingCallbacks DiagConsumer;
392 MockCompilationDatabase CDB;
393 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
394 {"-xc++", "-target", "x86_64-linux-unknown",
395 "-m64", "--gcc-toolchain=/randomusr",
396 "-stdlib=libstdc++"});
397 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
399 // Just a random gcc version string
400 SmallString<8> Version("4.9.3");
402 // A lib dir for gcc installation
403 SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu");
404 llvm::sys::path::append(LibDir, Version);
406 // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc
407 // installation there.
408 SmallString<64> MockLibFile;
409 llvm::sys::path::append(MockLibFile, LibDir, "64", "crtbegin.o");
410 FS.Files[MockLibFile] = "";
412 SmallString<64> IncludeDir("/randomusr/include/c++");
413 llvm::sys::path::append(IncludeDir, Version);
415 SmallString<64> StringPath;
416 llvm::sys::path::append(StringPath, IncludeDir, "string");
417 FS.Files[StringPath] = "class mock_string {};";
419 auto FooCpp = testPath("foo.cpp");
420 const auto *SourceContents = R"cpp(
424 FS.Files[FooCpp] = SourceContents;
426 runAddDocument(Server, FooCpp, SourceContents);
427 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
429 const auto *SourceContentsWithError = R"cpp(
433 runAddDocument(Server, FooCpp, SourceContentsWithError);
434 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
436 #endif // LLVM_ON_UNIX
438 TEST(ClangdServerTest, ForceReparseCompileCommand) {
440 ErrorCheckingCallbacks DiagConsumer;
441 MockCompilationDatabase CDB;
442 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
444 auto FooCpp = testPath("foo.cpp");
445 const auto *SourceContents1 = R"cpp(
449 const auto *SourceContents2 = R"cpp(
454 FS.Files[FooCpp] = "";
456 // First parse files in C mode and check they produce errors.
457 CDB.ExtraClangFlags = {"-xc"};
458 runAddDocument(Server, FooCpp, SourceContents1);
459 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
460 runAddDocument(Server, FooCpp, SourceContents2);
461 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
463 // Now switch to C++ mode.
464 CDB.ExtraClangFlags = {"-xc++"};
465 runAddDocument(Server, FooCpp, SourceContents2);
466 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
467 // Subsequent addDocument calls should finish without errors too.
468 runAddDocument(Server, FooCpp, SourceContents1);
469 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
470 runAddDocument(Server, FooCpp, SourceContents2);
471 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
474 TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
476 ErrorCheckingCallbacks DiagConsumer;
477 MockCompilationDatabase CDB;
478 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
480 auto FooCpp = testPath("foo.cpp");
481 const auto *SourceContents = R"cpp(
486 int main() { return 0; }
488 FS.Files[FooCpp] = "";
490 // Parse with define, we expect to see the errors.
491 CDB.ExtraClangFlags = {"-DWITH_ERROR"};
492 runAddDocument(Server, FooCpp, SourceContents);
493 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
495 // Parse without the define, no errors should be produced.
496 CDB.ExtraClangFlags = {};
497 runAddDocument(Server, FooCpp, SourceContents);
498 ASSERT_TRUE(Server.blockUntilIdleForTest());
499 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
500 // Subsequent addDocument call should finish without errors too.
501 runAddDocument(Server, FooCpp, SourceContents);
502 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
505 // Test ClangdServer.reparseOpenedFiles.
506 TEST(ClangdServerTest, ReparseOpenedFiles) {
507 Annotations FooSource(R"cpp(
509 static void $one[[bob]]() {}
511 static void $two[[bob]]() {}
514 int main () { bo^b (); return 0; }
517 Annotations BarSource(R"cpp(
523 Annotations BazSource(R"cpp(
528 MockCompilationDatabase CDB;
529 MultipleErrorCheckingCallbacks DiagConsumer;
530 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
532 auto FooCpp = testPath("foo.cpp");
533 auto BarCpp = testPath("bar.cpp");
534 auto BazCpp = testPath("baz.cpp");
536 FS.Files[FooCpp] = "";
537 FS.Files[BarCpp] = "";
538 FS.Files[BazCpp] = "";
540 CDB.ExtraClangFlags = {"-DMACRO=1"};
541 Server.addDocument(FooCpp, FooSource.code());
542 Server.addDocument(BarCpp, BarSource.code());
543 Server.addDocument(BazCpp, BazSource.code());
544 ASSERT_TRUE(Server.blockUntilIdleForTest());
546 EXPECT_THAT(DiagConsumer.filesWithDiags(),
547 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true),
548 Pair(BazCpp, false)));
550 auto Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point());
551 EXPECT_TRUE(bool(Locations));
552 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one"))));
554 // Undefine MACRO, close baz.cpp.
555 CDB.ExtraClangFlags.clear();
556 DiagConsumer.clear();
557 Server.removeDocument(BazCpp);
558 Server.addDocument(FooCpp, FooSource.code());
559 Server.addDocument(BarCpp, BarSource.code());
560 ASSERT_TRUE(Server.blockUntilIdleForTest());
562 EXPECT_THAT(DiagConsumer.filesWithDiags(),
563 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false)));
565 Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point());
566 EXPECT_TRUE(bool(Locations));
567 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two"))));
570 MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "") {
571 return arg.first() == Name &&
572 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
574 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
575 std::tie(PreambleBuilds, ASTBuilds);
578 TEST(ClangdServerTest, FileStats) {
580 ErrorCheckingCallbacks DiagConsumer;
581 MockCompilationDatabase CDB;
582 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
584 Path FooCpp = testPath("foo.cpp");
585 const auto *SourceContents = R"cpp(
590 Path BarCpp = testPath("bar.cpp");
592 FS.Files[FooCpp] = "";
593 FS.Files[BarCpp] = "";
595 EXPECT_THAT(Server.fileStats(), IsEmpty());
597 Server.addDocument(FooCpp, SourceContents);
598 Server.addDocument(BarCpp, SourceContents);
599 ASSERT_TRUE(Server.blockUntilIdleForTest());
601 EXPECT_THAT(Server.fileStats(),
602 UnorderedElementsAre(Stats(FooCpp, true, 1, 1),
603 Stats(BarCpp, true, 1, 1)));
605 Server.removeDocument(FooCpp);
606 ASSERT_TRUE(Server.blockUntilIdleForTest());
607 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp, true, 1, 1)));
609 Server.removeDocument(BarCpp);
610 ASSERT_TRUE(Server.blockUntilIdleForTest());
611 EXPECT_THAT(Server.fileStats(), IsEmpty());
614 TEST(ClangdServerTest, InvalidCompileCommand) {
616 ErrorCheckingCallbacks DiagConsumer;
617 MockCompilationDatabase CDB;
619 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
621 auto FooCpp = testPath("foo.cpp");
622 // clang cannot create CompilerInvocation in this case.
623 CDB.ExtraClangFlags.push_back("-###");
625 // Clang can't parse command args in that case, but we shouldn't crash.
626 runAddDocument(Server, FooCpp, "int main() {}");
628 EXPECT_EQ(dumpAST(Server, FooCpp), "<no-ast>");
629 EXPECT_ERROR(runLocateSymbolAt(Server, FooCpp, Position()));
630 EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position()));
631 EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name",
632 clangd::RenameOptions()));
634 runSignatureHelp(Server, FooCpp, Position(), MarkupKind::PlainText));
635 // Identifier-based fallback completion.
636 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
637 clangd::CodeCompleteOptions()))
639 ElementsAre(Field(&CodeCompletion::Name, "int"),
640 Field(&CodeCompletion::Name, "main")));
643 TEST(ClangdThreadingTest, StressTest) {
644 // Without 'static' clang gives an error for a usage inside TestDiagConsumer.
645 static const unsigned FilesCount = 5;
646 const unsigned RequestsCount = 500;
647 // Blocking requests wait for the parsing to complete, they slow down the test
648 // dramatically, so they are issued rarely. Each
649 // BlockingRequestInterval-request will be a blocking one.
650 const unsigned BlockingRequestInterval = 40;
652 const auto *SourceContentsWithoutErrors = R"cpp(
659 const auto *SourceContentsWithErrors = R"cpp(
666 // Giving invalid line and column number should not crash ClangdServer, but
667 // just to make sure we're sometimes hitting the bounds inside the file we
668 // limit the intervals of line and column number that are generated.
669 unsigned MaxLineForFileRequests = 7;
670 unsigned MaxColumnForFileRequests = 10;
672 std::vector<std::string> FilePaths;
674 for (unsigned I = 0; I < FilesCount; ++I) {
675 std::string Name = std::string("Foo") + std::to_string(I) + ".cpp";
677 FilePaths.push_back(testPath(Name));
681 unsigned HitsWithoutErrors = 0;
682 unsigned HitsWithErrors = 0;
683 bool HadErrorsInLastDiags = false;
686 class TestDiagConsumer : public ClangdServer::Callbacks {
688 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
690 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
691 std::vector<Diag> Diagnostics) override {
692 StringRef FileIndexStr = llvm::sys::path::stem(File);
693 ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
695 unsigned long FileIndex = std::stoul(FileIndexStr.str());
697 bool HadError = diagsContainErrors(Diagnostics);
699 std::lock_guard<std::mutex> Lock(Mutex);
701 Stats[FileIndex].HitsWithErrors++;
703 Stats[FileIndex].HitsWithoutErrors++;
704 Stats[FileIndex].HadErrorsInLastDiags = HadError;
707 std::vector<FileStat> takeFileStats() {
708 std::lock_guard<std::mutex> Lock(Mutex);
709 return std::move(Stats);
714 std::vector<FileStat> Stats;
717 struct RequestStats {
718 unsigned RequestsWithoutErrors = 0;
719 unsigned RequestsWithErrors = 0;
720 bool LastContentsHadErrors = false;
721 bool FileIsRemoved = true;
724 std::vector<RequestStats> ReqStats;
725 ReqStats.reserve(FilesCount);
726 for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
727 ReqStats.emplace_back();
729 TestDiagConsumer DiagConsumer;
731 MockCompilationDatabase CDB;
732 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
734 // Prepare some random distributions for the test.
735 std::random_device RandGen;
737 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
738 // Pass a text that contains compiler errors to addDocument in about 20% of
740 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
741 // Line and Column numbers for requests that need them.
742 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
743 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
746 auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) {
747 auto &Stats = ReqStats[FileIndex];
750 ++Stats.RequestsWithErrors;
752 ++Stats.RequestsWithoutErrors;
753 Stats.LastContentsHadErrors = HadErrors;
754 Stats.FileIsRemoved = false;
757 auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) {
758 auto &Stats = ReqStats[FileIndex];
760 Stats.FileIsRemoved = true;
763 auto AddDocument = [&](unsigned FileIndex, bool SkipCache) {
764 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
765 Server.addDocument(FilePaths[FileIndex],
766 ShouldHaveErrors ? SourceContentsWithErrors
767 : SourceContentsWithoutErrors);
768 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
771 // Various requests that we would randomly run.
772 auto AddDocumentRequest = [&]() {
773 unsigned FileIndex = FileIndexDist(RandGen);
774 AddDocument(FileIndex, /*SkipCache=*/false);
777 auto ForceReparseRequest = [&]() {
778 unsigned FileIndex = FileIndexDist(RandGen);
779 AddDocument(FileIndex, /*SkipCache=*/true);
782 auto RemoveDocumentRequest = [&]() {
783 unsigned FileIndex = FileIndexDist(RandGen);
784 // Make sure we don't violate the ClangdServer's contract.
785 if (ReqStats[FileIndex].FileIsRemoved)
786 AddDocument(FileIndex, /*SkipCache=*/false);
788 Server.removeDocument(FilePaths[FileIndex]);
789 UpdateStatsOnRemoveDocument(FileIndex);
792 auto CodeCompletionRequest = [&]() {
793 unsigned FileIndex = FileIndexDist(RandGen);
794 // Make sure we don't violate the ClangdServer's contract.
795 if (ReqStats[FileIndex].FileIsRemoved)
796 AddDocument(FileIndex, /*SkipCache=*/false);
799 Pos.line = LineDist(RandGen);
800 Pos.character = ColumnDist(RandGen);
801 // FIXME(ibiryukov): Also test async completion requests.
802 // Simply putting CodeCompletion into async requests now would make
803 // tests slow, since there's no way to cancel previous completion
804 // requests as opposed to AddDocument/RemoveDocument, which are implicitly
805 // cancelled by any subsequent AddDocument/RemoveDocument request to the
807 cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos,
808 clangd::CodeCompleteOptions()));
811 auto LocateSymbolRequest = [&]() {
812 unsigned FileIndex = FileIndexDist(RandGen);
813 // Make sure we don't violate the ClangdServer's contract.
814 if (ReqStats[FileIndex].FileIsRemoved)
815 AddDocument(FileIndex, /*SkipCache=*/false);
818 Pos.line = LineDist(RandGen);
819 Pos.character = ColumnDist(RandGen);
821 ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos));
824 std::vector<std::function<void()>> AsyncRequests = {
825 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
826 std::vector<std::function<void()>> BlockingRequests = {
827 CodeCompletionRequest, LocateSymbolRequest};
829 // Bash requests to ClangdServer in a loop.
830 std::uniform_int_distribution<int> AsyncRequestIndexDist(
831 0, AsyncRequests.size() - 1);
832 std::uniform_int_distribution<int> BlockingRequestIndexDist(
833 0, BlockingRequests.size() - 1);
834 for (unsigned I = 1; I <= RequestsCount; ++I) {
835 if (I % BlockingRequestInterval != 0) {
836 // Issue an async request most of the time. It should be fast.
837 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
838 AsyncRequests[RequestIndex]();
840 // Issue a blocking request once in a while.
841 auto RequestIndex = BlockingRequestIndexDist(RandGen);
842 BlockingRequests[RequestIndex]();
845 ASSERT_TRUE(Server.blockUntilIdleForTest());
848 // Check some invariants about the state of the program.
849 std::vector<FileStat> Stats = DiagConsumer.takeFileStats();
850 for (unsigned I = 0; I < FilesCount; ++I) {
851 if (!ReqStats[I].FileIsRemoved) {
852 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
853 ReqStats[I].LastContentsHadErrors);
856 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
857 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
861 TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
862 class NoConcurrentAccessDiagConsumer : public ClangdServer::Callbacks {
864 std::atomic<int> Count = {0};
866 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
867 : StartSecondReparse(std::move(StartSecondReparse)) {}
869 void onDiagnosticsReady(PathRef, llvm::StringRef,
870 std::vector<Diag>) override {
872 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
873 ASSERT_TRUE(Lock.owns_lock())
874 << "Detected concurrent onDiagnosticsReady calls for the same file.";
876 // If we started the second parse immediately, it might cancel the first.
877 // So we don't allow it to start until the first has delivered diags...
879 FirstRequest = false;
880 StartSecondReparse.set_value();
881 // ... but then we wait long enough that the callbacks would overlap.
882 std::this_thread::sleep_for(std::chrono::milliseconds(50));
888 bool FirstRequest = true;
889 std::promise<void> StartSecondReparse;
892 const auto *SourceContentsWithoutErrors = R"cpp(
899 const auto *SourceContentsWithErrors = R"cpp(
906 auto FooCpp = testPath("foo.cpp");
908 FS.Files[FooCpp] = "";
910 std::promise<void> StartSecondPromise;
911 std::future<void> StartSecond = StartSecondPromise.get_future();
913 NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise));
914 MockCompilationDatabase CDB;
915 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
916 Server.addDocument(FooCpp, SourceContentsWithErrors);
918 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
919 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
920 ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both?
923 TEST(ClangdServerTest, FormatCode) {
925 ErrorCheckingCallbacks DiagConsumer;
926 MockCompilationDatabase CDB;
927 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
929 auto Path = testPath("foo.cpp");
930 std::string Code = R"cpp(
936 std::string Expected = R"cpp(
942 FS.Files[Path] = Code;
943 runAddDocument(Server, Path, Code);
945 auto Replaces = runFormatFile(Server, Path, /*Rng=*/std::nullopt);
946 EXPECT_TRUE(static_cast<bool>(Replaces));
947 auto Changed = tooling::applyAllReplacements(Code, *Replaces);
948 EXPECT_TRUE(static_cast<bool>(Changed));
949 EXPECT_EQ(Expected, *Changed);
952 TEST(ClangdServerTest, ChangedHeaderFromISystem) {
954 ErrorCheckingCallbacks DiagConsumer;
955 MockCompilationDatabase CDB;
956 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
958 auto SourcePath = testPath("source/foo.cpp");
959 auto HeaderPath = testPath("headers/foo.h");
960 FS.Files[HeaderPath] = "struct X { int bar; };";
961 Annotations Code(R"cpp(
967 CDB.ExtraClangFlags.push_back("-xc++");
968 CDB.ExtraClangFlags.push_back("-isystem" + testPath("headers"));
970 runAddDocument(Server, SourcePath, Code.code());
971 auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
972 clangd::CodeCompleteOptions()))
974 EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar")));
975 // Update the header and rerun addDocument to make sure we get the updated
977 FS.Files[HeaderPath] = "struct X { int bar; int baz; };";
978 runAddDocument(Server, SourcePath, Code.code());
979 Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
980 clangd::CodeCompleteOptions()))
982 // We want to make sure we see the updated version.
983 EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"),
984 Field(&CodeCompletion::Name, "baz")));
987 // FIXME(ioeric): make this work for windows again.
989 // Check that running code completion doesn't stat() a bunch of files from the
990 // preamble again. (They should be using the preamble's stat-cache)
991 TEST(ClangdTests, PreambleVFSStatCache) {
992 class StatRecordingFS : public ThreadsafeFS {
993 llvm::StringMap<unsigned> &CountStats;
996 // If relative paths are used, they are resolved with testPath().
997 llvm::StringMap<std::string> Files;
999 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1000 : CountStats(CountStats) {}
1003 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
1004 class StatRecordingVFS : public llvm::vfs::ProxyFileSystem {
1006 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1007 llvm::StringMap<unsigned> &CountStats)
1008 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1010 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1011 openFileForRead(const Twine &Path) override {
1012 ++CountStats[llvm::sys::path::filename(Path.str())];
1013 return ProxyFileSystem::openFileForRead(Path);
1015 llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {
1016 ++CountStats[llvm::sys::path::filename(Path.str())];
1017 return ProxyFileSystem::status(Path);
1021 llvm::StringMap<unsigned> &CountStats;
1024 return IntrusiveRefCntPtr<StatRecordingVFS>(
1025 new StatRecordingVFS(buildTestFS(Files), CountStats));
1029 llvm::StringMap<unsigned> CountStats;
1030 StatRecordingFS FS(CountStats);
1031 ErrorCheckingCallbacks DiagConsumer;
1032 MockCompilationDatabase CDB;
1033 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1035 auto SourcePath = testPath("foo.cpp");
1036 auto HeaderPath = testPath("foo.h");
1037 FS.Files[HeaderPath] = "struct TestSym {};";
1038 Annotations Code(R"cpp(
1045 runAddDocument(Server, SourcePath, Code.code());
1047 unsigned Before = CountStats["foo.h"];
1048 EXPECT_GT(Before, 0u);
1049 auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
1050 clangd::CodeCompleteOptions()))
1052 EXPECT_EQ(CountStats["foo.h"], Before);
1053 EXPECT_THAT(Completions,
1054 ElementsAre(Field(&CodeCompletion::Name, "TestSym")));
1058 TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) {
1060 ErrorCheckingCallbacks DiagConsumer;
1061 MockCompilationDatabase CDB;
1062 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1064 auto FooCpp = testPath("foo.cpp");
1065 Annotations Code(R"cpp(
1066 namespace ns { int xyz; }
1071 FS.Files[FooCpp] = FooCpp;
1073 auto Opts = clangd::CodeCompleteOptions();
1074 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1076 // This will make compile command broken and preamble absent.
1077 CDB.ExtraClangFlags = {"-###"};
1078 Server.addDocument(FooCpp, Code.code());
1079 ASSERT_TRUE(Server.blockUntilIdleForTest());
1080 auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts));
1081 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1082 // Identifier-based fallback completion doesn't know about "symbol" scope.
1083 EXPECT_THAT(Res.Completions,
1084 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1085 Field(&CodeCompletion::Scope, ""))));
1087 // Make the compile command work again.
1088 CDB.ExtraClangFlags = {"-std=c++11"};
1089 Server.addDocument(FooCpp, Code.code());
1090 ASSERT_TRUE(Server.blockUntilIdleForTest());
1092 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1093 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1094 Field(&CodeCompletion::Scope, "ns::"))));
1096 // Now force identifier-based completion.
1097 Opts.RunParser = CodeCompleteOptions::NeverParse;
1099 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1100 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1101 Field(&CodeCompletion::Scope, ""))));
1104 TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1106 ErrorCheckingCallbacks DiagConsumer;
1107 // Returns compile command only when notified.
1108 class DelayedCompilationDatabase : public GlobalCompilationDatabase {
1110 DelayedCompilationDatabase(Notification &CanReturnCommand)
1111 : CanReturnCommand(CanReturnCommand) {}
1113 std::optional<tooling::CompileCommand>
1114 getCompileCommand(PathRef File) const override {
1115 // FIXME: make this timeout and fail instead of waiting forever in case
1116 // something goes wrong.
1117 CanReturnCommand.wait();
1118 auto FileName = llvm::sys::path::filename(File);
1119 std::vector<std::string> CommandLine = {"clangd", "-ffreestanding",
1121 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
1122 FileName, std::move(CommandLine), "")};
1125 std::vector<std::string> ExtraClangFlags;
1128 Notification &CanReturnCommand;
1131 Notification CanReturnCommand;
1132 DelayedCompilationDatabase CDB(CanReturnCommand);
1133 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1135 auto FooCpp = testPath("foo.cpp");
1136 Annotations Code(R"cpp(
1137 namespace ns { int xyz; }
1142 FS.Files[FooCpp] = FooCpp;
1143 Server.addDocument(FooCpp, Code.code());
1145 // Sleep for some time to make sure code completion is not run because update
1146 // hasn't been scheduled.
1147 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1148 auto Opts = clangd::CodeCompleteOptions();
1149 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1151 auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts));
1152 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1154 CanReturnCommand.notify();
1155 ASSERT_TRUE(Server.blockUntilIdleForTest());
1156 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(),
1157 clangd::CodeCompleteOptions()))
1159 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1160 Field(&CodeCompletion::Scope, "ns::"))));
1163 TEST(ClangdServerTest, CustomAction) {
1164 OverlayCDB CDB(/*Base=*/nullptr);
1166 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1168 Server.addDocument(testPath("foo.cc"), "void x();");
1169 Decl::Kind XKind = Decl::TranslationUnit;
1170 EXPECT_THAT_ERROR(runCustomAction(Server, testPath("foo.cc"),
1171 [&](InputsAndAST AST) {
1172 XKind = findDecl(AST.AST, "x").getKind();
1175 EXPECT_EQ(XKind, Decl::Function);
1178 // Tests fails when built with asan due to stack overflow. So skip running the
1179 // test as a workaround.
1180 #if !defined(__has_feature) || !__has_feature(address_sanitizer)
1181 TEST(ClangdServerTest, TestStackOverflow) {
1183 ErrorCheckingCallbacks DiagConsumer;
1184 MockCompilationDatabase CDB;
1185 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1187 const char *SourceContents = R"cpp(
1188 constexpr int foo() { return foo(); }
1189 static_assert(foo());
1192 auto FooCpp = testPath("foo.cpp");
1193 FS.Files[FooCpp] = SourceContents;
1195 Server.addDocument(FooCpp, SourceContents);
1196 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
1197 // check that we got a constexpr depth error, and not crashed by stack
1199 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
1203 TEST(ClangdServer, TidyOverrideTest) {
1204 struct DiagsCheckingCallback : public ClangdServer::Callbacks {
1206 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
1207 std::vector<Diag> Diagnostics) override {
1208 std::lock_guard<std::mutex> Lock(Mutex);
1209 HadDiagsInLastCallback = !Diagnostics.empty();
1213 bool HadDiagsInLastCallback = false;
1217 // These checks don't work well in clangd, even if configured they shouldn't
1219 FS.Files[testPath(".clang-tidy")] = R"(
1220 Checks: -*,bugprone-use-after-move,llvm-header-guard
1222 MockCompilationDatabase CDB;
1223 std::vector<TidyProvider> Stack;
1224 Stack.push_back(provideClangTidyFiles(FS));
1225 Stack.push_back(disableUnusableChecks());
1226 TidyProvider Provider = combine(std::move(Stack));
1227 CDB.ExtraClangFlags = {"-xc++"};
1228 auto Opts = ClangdServer::optsForTest();
1229 Opts.ClangTidyProvider = Provider;
1230 ClangdServer Server(CDB, FS, Opts, &DiagConsumer);
1231 const char *SourceContents = R"cpp(
1232 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1233 namespace std { Foo&& move(Foo&); }
1236 Foo y = std::move(x);
1239 Server.addDocument(testPath("foo.h"), SourceContents);
1240 ASSERT_TRUE(Server.blockUntilIdleForTest());
1241 EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback);
1244 TEST(ClangdServer, MemoryUsageTest) {
1246 MockCompilationDatabase CDB;
1247 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1249 auto FooCpp = testPath("foo.cpp");
1250 Server.addDocument(FooCpp, "");
1251 ASSERT_TRUE(Server.blockUntilIdleForTest());
1253 llvm::BumpPtrAllocator Alloc;
1254 MemoryTree MT(&Alloc);
1256 ASSERT_TRUE(MT.children().count("tuscheduler"));
1257 EXPECT_TRUE(MT.child("tuscheduler").children().count(FooCpp));
1260 TEST(ClangdServer, RespectsTweakFormatting) {
1261 static constexpr const char *TweakID = "ModuleTweak";
1262 static constexpr const char *NewContents = "{not;\nformatted;}";
1264 // Contributes a tweak that generates a non-formatted insertion and disables
1266 struct TweakContributingModule final : public FeatureModule {
1267 struct ModuleTweak final : public Tweak {
1268 const char *id() const override { return TweakID; }
1269 bool prepare(const Selection &Sel) override { return true; }
1270 Expected<Effect> apply(const Selection &Sel) override {
1271 auto &SM = Sel.AST->getSourceManager();
1272 llvm::StringRef FilePath = SM.getFilename(Sel.Cursor);
1273 tooling::Replacements Reps;
1275 Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents)));
1276 auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps)));
1277 E.FormatEdits = false;
1280 std::string title() const override { return id(); }
1281 llvm::StringLiteral kind() const override {
1282 return llvm::StringLiteral("");
1286 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
1287 Out.emplace_back(new ModuleTweak);
1292 MockCompilationDatabase CDB;
1293 auto Opts = ClangdServer::optsForTest();
1294 FeatureModuleSet Set;
1295 Set.add(std::make_unique<TweakContributingModule>());
1296 Opts.FeatureModules = &Set;
1297 ClangdServer Server(CDB, FS, Opts);
1299 auto FooCpp = testPath("foo.cpp");
1300 Server.addDocument(FooCpp, "");
1301 ASSERT_TRUE(Server.blockUntilIdleForTest());
1303 // Ensure that disabled formatting is respected.
1305 Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected<Tweak::Effect> E) {
1306 ASSERT_TRUE(static_cast<bool>(E));
1307 EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()),
1314 TEST(ClangdServer, InactiveRegions) {
1315 struct InactiveRegionsCallback : ClangdServer::Callbacks {
1316 std::vector<std::vector<Range>> FoundInactiveRegions;
1318 void onInactiveRegionsReady(PathRef FIle,
1319 std::vector<Range> InactiveRegions) override {
1320 FoundInactiveRegions.push_back(std::move(InactiveRegions));
1325 MockCompilationDatabase CDB;
1326 CDB.ExtraClangFlags.push_back("-DCMDMACRO");
1327 auto Opts = ClangdServer::optsForTest();
1328 Opts.PublishInactiveRegions = true;
1329 InactiveRegionsCallback Callback;
1330 ClangdServer Server(CDB, FS, Opts, &Callback);
1331 Annotations Source(R"cpp(
1332 #define PREAMBLEMACRO 42
1333 #if PREAMBLEMACRO > 40
1336 $inactive1[[ #define INACTIVE]]
1340 $inactive2[[ int inactiveInt;]]
1344 $inactive3[[ int inactiveInt2;]]
1345 #elif PREAMBLEMACRO > 0
1349 $inactive4[[ int inactiveInt3;]]
1352 #endif // empty inactive range, gets dropped
1354 Server.addDocument(testPath("foo.cpp"), Source.code());
1355 ASSERT_TRUE(Server.blockUntilIdleForTest());
1356 EXPECT_THAT(Callback.FoundInactiveRegions,
1357 ElementsAre(ElementsAre(
1358 Source.range("inactive1"), Source.range("inactive2"),
1359 Source.range("inactive3"), Source.range("inactive4"))));
1363 } // namespace clangd
1364 } // namespace clang