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/ArrayRef.h"
29 #include "llvm/ADT/SmallVector.h"
30 #include "llvm/ADT/StringMap.h"
31 #include "llvm/ADT/StringRef.h"
32 #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
33 #include "llvm/Support/Allocator.h"
34 #include "llvm/Support/Error.h"
35 #include "llvm/Support/Path.h"
36 #include "llvm/Support/Regex.h"
37 #include "llvm/Support/VirtualFileSystem.h"
38 #include "llvm/Testing/Support/Error.h"
39 #include "gmock/gmock.h"
40 #include "gtest/gtest.h"
55 using ::testing::AllOf
;
56 using ::testing::ElementsAre
;
57 using ::testing::Field
;
58 using ::testing::IsEmpty
;
59 using ::testing::Pair
;
60 using ::testing::SizeIs
;
61 using ::testing::UnorderedElementsAre
;
63 MATCHER_P2(DeclAt
, File
, Range
, "") {
64 return arg
.PreferredDeclaration
==
65 Location
{URIForFile::canonicalize(File
, testRoot()), Range
};
68 bool diagsContainErrors(const std::vector
<Diag
> &Diagnostics
) {
69 for (auto D
: Diagnostics
) {
70 if (D
.Severity
== DiagnosticsEngine::Error
||
71 D
.Severity
== DiagnosticsEngine::Fatal
)
77 class ErrorCheckingCallbacks
: public ClangdServer::Callbacks
{
79 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
80 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
81 bool HadError
= diagsContainErrors(Diagnostics
);
82 std::lock_guard
<std::mutex
> Lock(Mutex
);
83 HadErrorInLastDiags
= HadError
;
86 bool hadErrorInLastDiags() {
87 std::lock_guard
<std::mutex
> Lock(Mutex
);
88 return HadErrorInLastDiags
;
93 bool HadErrorInLastDiags
= false;
96 /// For each file, record whether the last published diagnostics contained at
98 class MultipleErrorCheckingCallbacks
: public ClangdServer::Callbacks
{
100 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
101 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
102 bool HadError
= diagsContainErrors(Diagnostics
);
104 std::lock_guard
<std::mutex
> Lock(Mutex
);
105 LastDiagsHadError
[File
] = HadError
;
108 /// Exposes all files consumed by onDiagnosticsReady in an unspecified order.
109 /// For each file, a bool value indicates whether the last diagnostics
110 /// contained an error.
111 std::vector
<std::pair
<Path
, bool>> filesWithDiags() const {
112 std::vector
<std::pair
<Path
, bool>> Result
;
113 std::lock_guard
<std::mutex
> Lock(Mutex
);
114 for (const auto &It
: LastDiagsHadError
)
115 Result
.emplace_back(std::string(It
.first()), It
.second
);
120 std::lock_guard
<std::mutex
> Lock(Mutex
);
121 LastDiagsHadError
.clear();
125 mutable std::mutex Mutex
;
126 llvm::StringMap
<bool> LastDiagsHadError
;
129 /// Replaces all patterns of the form 0x123abc with spaces
130 std::string
replacePtrsInDump(std::string
const &Dump
) {
131 llvm::Regex
RE("0x[0-9a-fA-F]+");
132 llvm::SmallVector
<llvm::StringRef
, 1> Matches
;
133 llvm::StringRef Pending
= Dump
;
136 while (RE
.match(Pending
, &Matches
)) {
137 assert(Matches
.size() == 1 && "Exactly one match expected");
138 auto MatchPos
= Matches
[0].data() - Pending
.data();
140 Result
+= Pending
.take_front(MatchPos
);
141 Pending
= Pending
.drop_front(MatchPos
+ Matches
[0].size());
148 std::string
dumpAST(ClangdServer
&Server
, PathRef File
) {
151 Server
.customAction(File
, "DumpAST", [&](llvm::Expected
<InputsAndAST
> AST
) {
153 llvm::raw_string_ostream
ResultOS(Result
);
154 AST
->AST
.getASTContext().getTranslationUnitDecl()->dump(ResultOS
, true);
156 llvm::consumeError(AST
.takeError());
165 std::string
dumpASTWithoutMemoryLocs(ClangdServer
&Server
, PathRef File
) {
166 return replacePtrsInDump(dumpAST(Server
, File
));
169 std::string
parseSourceAndDumpAST(
170 PathRef SourceFileRelPath
, llvm::StringRef SourceContents
,
171 std::vector
<std::pair
<PathRef
, llvm::StringRef
>> ExtraFiles
= {},
172 bool ExpectErrors
= false) {
174 ErrorCheckingCallbacks DiagConsumer
;
175 MockCompilationDatabase CDB
;
176 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
177 for (const auto &FileWithContents
: ExtraFiles
)
178 FS
.Files
[testPath(FileWithContents
.first
)] =
179 std::string(FileWithContents
.second
);
181 auto SourceFilename
= testPath(SourceFileRelPath
);
182 Server
.addDocument(SourceFilename
, SourceContents
);
183 auto Result
= dumpASTWithoutMemoryLocs(Server
, SourceFilename
);
184 EXPECT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
185 EXPECT_EQ(ExpectErrors
, DiagConsumer
.hadErrorInLastDiags());
189 TEST(ClangdServerTest
, Parse
) {
190 // FIXME: figure out a stable format for AST dumps, so that we can check the
191 // output of the dump itself is equal to the expected one, not just that it's
193 auto Empty
= parseSourceAndDumpAST("foo.cpp", "");
194 auto OneDecl
= parseSourceAndDumpAST("foo.cpp", "int a;");
195 auto SomeDecls
= parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
196 EXPECT_NE(Empty
, OneDecl
);
197 EXPECT_NE(Empty
, SomeDecls
);
198 EXPECT_NE(SomeDecls
, OneDecl
);
200 auto Empty2
= parseSourceAndDumpAST("foo.cpp", "");
201 auto OneDecl2
= parseSourceAndDumpAST("foo.cpp", "int a;");
202 auto SomeDecls2
= parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
203 EXPECT_EQ(Empty
, Empty2
);
204 EXPECT_EQ(OneDecl
, OneDecl2
);
205 EXPECT_EQ(SomeDecls
, SomeDecls2
);
208 TEST(ClangdServerTest
, ParseWithHeader
) {
209 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {},
210 /*ExpectErrors=*/true);
211 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}},
212 /*ExpectErrors=*/false);
214 const auto *SourceContents
= R
"cpp(
218 parseSourceAndDumpAST("foo.cpp", SourceContents
, {{"foo.h", ""}},
219 /*ExpectErrors=*/true);
220 parseSourceAndDumpAST("foo.cpp", SourceContents
, {{"foo.h", "int a;"}},
221 /*ExpectErrors=*/false);
224 TEST(ClangdServerTest
, Reparse
) {
226 ErrorCheckingCallbacks DiagConsumer
;
227 MockCompilationDatabase CDB
;
228 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
230 const auto *SourceContents
= R
"cpp(
235 auto FooCpp
= testPath("foo.cpp");
237 FS
.Files
[testPath("foo.h")] = "int a;";
238 FS
.Files
[FooCpp
] = SourceContents
;
240 Server
.addDocument(FooCpp
, SourceContents
);
241 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
242 auto DumpParse1
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
243 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
245 Server
.addDocument(FooCpp
, "");
246 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
247 auto DumpParseEmpty
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
248 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
250 Server
.addDocument(FooCpp
, SourceContents
);
251 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
252 auto DumpParse2
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
253 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
255 EXPECT_EQ(DumpParse1
, DumpParse2
);
256 EXPECT_NE(DumpParse1
, DumpParseEmpty
);
259 TEST(ClangdServerTest
, ReparseOnHeaderChange
) {
261 ErrorCheckingCallbacks DiagConsumer
;
262 MockCompilationDatabase CDB
;
263 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
265 const auto *SourceContents
= R
"cpp(
270 auto FooCpp
= testPath("foo.cpp");
271 auto FooH
= testPath("foo.h");
273 FS
.Files
[FooH
] = "int a;";
274 FS
.Files
[FooCpp
] = SourceContents
;
276 Server
.addDocument(FooCpp
, SourceContents
);
277 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
278 auto DumpParse1
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
279 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
282 Server
.addDocument(FooCpp
, SourceContents
);
283 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
284 auto DumpParseDifferent
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
285 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
287 FS
.Files
[FooH
] = "int a;";
288 Server
.addDocument(FooCpp
, SourceContents
);
289 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
290 auto DumpParse2
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
291 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
293 EXPECT_EQ(DumpParse1
, DumpParse2
);
294 EXPECT_NE(DumpParse1
, DumpParseDifferent
);
297 TEST(ClangdServerTest
, PropagatesContexts
) {
298 static Key
<int> Secret
;
299 struct ContextReadingFS
: public ThreadsafeFS
{
303 IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> viewImpl() const override
{
304 Got
= Context::current().getExisting(Secret
);
305 return buildTestFS({});
308 struct Callbacks
: public ClangdServer::Callbacks
{
309 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
310 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
311 Got
= Context::current().getExisting(Secret
);
315 MockCompilationDatabase CDB
;
317 // Verify that the context is plumbed to the FS provider and diagnostics.
318 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &Callbacks
);
320 WithContextValue
Entrypoint(Secret
, 42);
321 Server
.addDocument(testPath("foo.cpp"), "void main(){}");
323 ASSERT_TRUE(Server
.blockUntilIdleForTest());
324 EXPECT_EQ(FS
.Got
, 42);
325 EXPECT_EQ(Callbacks
.Got
, 42);
328 TEST(ClangdServerTest
, RespectsConfig
) {
329 // Go-to-definition will resolve as marked if FOO is defined.
330 Annotations
Example(R
"cpp(
338 // Provide conditional config that defines FOO for foo.cc.
339 class ConfigProvider
: public config::Provider
{
340 std::vector
<config::CompiledFragment
>
341 getFragments(const config::Params
&,
342 config::DiagnosticCallback DC
) const override
{
344 F
.If
.PathMatch
.emplace_back(".*foo.cc");
345 F
.CompileFlags
.Add
.emplace_back("-DFOO=1");
346 return {std::move(F
).compile(DC
)};
350 auto Opts
= ClangdServer::optsForTest();
351 Opts
.ContextProvider
=
352 ClangdServer::createConfiguredContextProvider(&CfgProvider
, nullptr);
353 OverlayCDB
CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
354 CommandMangler::forTests());
356 ClangdServer
Server(CDB
, FS
, Opts
);
357 // foo.cc sees the expected definition, as FOO is defined.
358 Server
.addDocument(testPath("foo.cc"), Example
.code());
359 auto Result
= runLocateSymbolAt(Server
, testPath("foo.cc"), Example
.point());
360 ASSERT_TRUE(bool(Result
)) << Result
.takeError();
361 ASSERT_THAT(*Result
, SizeIs(1));
362 EXPECT_EQ(Result
->front().PreferredDeclaration
.range
, Example
.range());
363 // bar.cc gets a different result, as FOO is not defined.
364 Server
.addDocument(testPath("bar.cc"), Example
.code());
365 Result
= runLocateSymbolAt(Server
, testPath("bar.cc"), Example
.point());
366 ASSERT_TRUE(bool(Result
)) << Result
.takeError();
367 ASSERT_THAT(*Result
, SizeIs(1));
368 EXPECT_NE(Result
->front().PreferredDeclaration
.range
, Example
.range());
371 TEST(ClangdServerTest
, PropagatesVersion
) {
372 MockCompilationDatabase CDB
;
374 struct Callbacks
: public ClangdServer::Callbacks
{
375 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
376 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
379 std::string Got
= "";
382 // Verify that the version is plumbed to diagnostics.
383 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &Callbacks
);
384 runAddDocument(Server
, testPath("foo.cpp"), "void main(){}", "42");
385 EXPECT_EQ(Callbacks
.Got
, "42");
388 // Only enable this test on Unix
390 TEST(ClangdServerTest
, SearchLibDir
) {
391 // Checks that searches for GCC installation is done through vfs.
393 ErrorCheckingCallbacks DiagConsumer
;
394 MockCompilationDatabase CDB
;
395 CDB
.ExtraClangFlags
.insert(CDB
.ExtraClangFlags
.end(),
396 {"-xc++", "--target=x86_64-unknown-linux-gnu",
397 "-m64", "--gcc-toolchain=/randomusr",
398 "-stdlib=libstdc++"});
399 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
401 // Just a random gcc version string
402 SmallString
<8> Version("4.9.3");
404 // A lib dir for gcc installation
405 SmallString
<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu");
406 llvm::sys::path::append(LibDir
, Version
);
408 // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc
409 // installation there.
410 SmallString
<64> MockLibFile
;
411 llvm::sys::path::append(MockLibFile
, LibDir
, "64", "crtbegin.o");
412 FS
.Files
[MockLibFile
] = "";
414 SmallString
<64> IncludeDir("/randomusr/include/c++");
415 llvm::sys::path::append(IncludeDir
, Version
);
417 SmallString
<64> StringPath
;
418 llvm::sys::path::append(StringPath
, IncludeDir
, "string");
419 FS
.Files
[StringPath
] = "class mock_string {};";
421 auto FooCpp
= testPath("foo.cpp");
422 const auto *SourceContents
= R
"cpp(
426 FS
.Files
[FooCpp
] = SourceContents
;
428 runAddDocument(Server
, FooCpp
, SourceContents
);
429 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
431 const auto *SourceContentsWithError
= R
"cpp(
435 runAddDocument(Server
, FooCpp
, SourceContentsWithError
);
436 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
438 #endif // LLVM_ON_UNIX
440 TEST(ClangdServerTest
, ForceReparseCompileCommand
) {
442 ErrorCheckingCallbacks DiagConsumer
;
443 MockCompilationDatabase CDB
;
444 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
446 auto FooCpp
= testPath("foo.cpp");
447 const auto *SourceContents1
= R
"cpp(
451 const auto *SourceContents2
= R
"cpp(
456 FS
.Files
[FooCpp
] = "";
458 // First parse files in C mode and check they produce errors.
459 CDB
.ExtraClangFlags
= {"-xc"};
460 runAddDocument(Server
, FooCpp
, SourceContents1
);
461 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
462 runAddDocument(Server
, FooCpp
, SourceContents2
);
463 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
465 // Now switch to C++ mode.
466 CDB
.ExtraClangFlags
= {"-xc++"};
467 runAddDocument(Server
, FooCpp
, SourceContents2
);
468 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
469 // Subsequent addDocument calls should finish without errors too.
470 runAddDocument(Server
, FooCpp
, SourceContents1
);
471 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
472 runAddDocument(Server
, FooCpp
, SourceContents2
);
473 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
476 TEST(ClangdServerTest
, ForceReparseCompileCommandDefines
) {
478 ErrorCheckingCallbacks DiagConsumer
;
479 MockCompilationDatabase CDB
;
480 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
482 auto FooCpp
= testPath("foo.cpp");
483 const auto *SourceContents
= R
"cpp(
488 int main() { return 0; }
490 FS
.Files
[FooCpp
] = "";
492 // Parse with define, we expect to see the errors.
493 CDB
.ExtraClangFlags
= {"-DWITH_ERROR"};
494 runAddDocument(Server
, FooCpp
, SourceContents
);
495 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
497 // Parse without the define, no errors should be produced.
498 CDB
.ExtraClangFlags
= {};
499 runAddDocument(Server
, FooCpp
, SourceContents
);
500 ASSERT_TRUE(Server
.blockUntilIdleForTest());
501 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
502 // Subsequent addDocument call should finish without errors too.
503 runAddDocument(Server
, FooCpp
, SourceContents
);
504 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
507 // Test ClangdServer.reparseOpenedFiles.
508 TEST(ClangdServerTest
, ReparseOpenedFiles
) {
509 Annotations
FooSource(R
"cpp(
511 static void $one[[bob]]() {}
513 static void $two[[bob]]() {}
516 int main () { bo^b (); return 0; }
519 Annotations
BarSource(R
"cpp(
525 Annotations
BazSource(R
"cpp(
530 MockCompilationDatabase CDB
;
531 MultipleErrorCheckingCallbacks DiagConsumer
;
532 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
534 auto FooCpp
= testPath("foo.cpp");
535 auto BarCpp
= testPath("bar.cpp");
536 auto BazCpp
= testPath("baz.cpp");
538 FS
.Files
[FooCpp
] = "";
539 FS
.Files
[BarCpp
] = "";
540 FS
.Files
[BazCpp
] = "";
542 CDB
.ExtraClangFlags
= {"-DMACRO=1"};
543 Server
.addDocument(FooCpp
, FooSource
.code());
544 Server
.addDocument(BarCpp
, BarSource
.code());
545 Server
.addDocument(BazCpp
, BazSource
.code());
546 ASSERT_TRUE(Server
.blockUntilIdleForTest());
548 EXPECT_THAT(DiagConsumer
.filesWithDiags(),
549 UnorderedElementsAre(Pair(FooCpp
, false), Pair(BarCpp
, true),
550 Pair(BazCpp
, false)));
552 auto Locations
= runLocateSymbolAt(Server
, FooCpp
, FooSource
.point());
553 EXPECT_TRUE(bool(Locations
));
554 EXPECT_THAT(*Locations
, ElementsAre(DeclAt(FooCpp
, FooSource
.range("one"))));
556 // Undefine MACRO, close baz.cpp.
557 CDB
.ExtraClangFlags
.clear();
558 DiagConsumer
.clear();
559 Server
.removeDocument(BazCpp
);
560 Server
.addDocument(FooCpp
, FooSource
.code());
561 Server
.addDocument(BarCpp
, BarSource
.code());
562 ASSERT_TRUE(Server
.blockUntilIdleForTest());
564 EXPECT_THAT(DiagConsumer
.filesWithDiags(),
565 UnorderedElementsAre(Pair(FooCpp
, false), Pair(BarCpp
, false)));
567 Locations
= runLocateSymbolAt(Server
, FooCpp
, FooSource
.point());
568 EXPECT_TRUE(bool(Locations
));
569 EXPECT_THAT(*Locations
, ElementsAre(DeclAt(FooCpp
, FooSource
.range("two"))));
572 MATCHER_P4(Stats
, Name
, UsesMemory
, PreambleBuilds
, ASTBuilds
, "") {
573 return arg
.first() == Name
&&
574 (arg
.second
.UsedBytesAST
+ arg
.second
.UsedBytesPreamble
!= 0) ==
576 std::tie(arg
.second
.PreambleBuilds
, ASTBuilds
) ==
577 std::tie(PreambleBuilds
, ASTBuilds
);
580 TEST(ClangdServerTest
, FileStats
) {
582 ErrorCheckingCallbacks DiagConsumer
;
583 MockCompilationDatabase CDB
;
584 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
586 Path FooCpp
= testPath("foo.cpp");
587 const auto *SourceContents
= R
"cpp(
592 Path BarCpp
= testPath("bar.cpp");
594 FS
.Files
[FooCpp
] = "";
595 FS
.Files
[BarCpp
] = "";
597 EXPECT_THAT(Server
.fileStats(), IsEmpty());
599 Server
.addDocument(FooCpp
, SourceContents
);
600 Server
.addDocument(BarCpp
, SourceContents
);
601 ASSERT_TRUE(Server
.blockUntilIdleForTest());
603 EXPECT_THAT(Server
.fileStats(),
604 UnorderedElementsAre(Stats(FooCpp
, true, 1, 1),
605 Stats(BarCpp
, true, 1, 1)));
607 Server
.removeDocument(FooCpp
);
608 ASSERT_TRUE(Server
.blockUntilIdleForTest());
609 EXPECT_THAT(Server
.fileStats(), ElementsAre(Stats(BarCpp
, true, 1, 1)));
611 Server
.removeDocument(BarCpp
);
612 ASSERT_TRUE(Server
.blockUntilIdleForTest());
613 EXPECT_THAT(Server
.fileStats(), IsEmpty());
616 TEST(ClangdServerTest
, InvalidCompileCommand
) {
618 ErrorCheckingCallbacks DiagConsumer
;
619 MockCompilationDatabase CDB
;
621 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
623 auto FooCpp
= testPath("foo.cpp");
624 // clang cannot create CompilerInvocation in this case.
625 CDB
.ExtraClangFlags
.push_back("-###");
627 // Clang can't parse command args in that case, but we shouldn't crash.
628 runAddDocument(Server
, FooCpp
, "int main() {}");
630 EXPECT_EQ(dumpAST(Server
, FooCpp
), "<no-ast>");
631 EXPECT_ERROR(runLocateSymbolAt(Server
, FooCpp
, Position()));
632 EXPECT_ERROR(runFindDocumentHighlights(Server
, FooCpp
, Position()));
633 EXPECT_ERROR(runRename(Server
, FooCpp
, Position(), "new_name",
634 clangd::RenameOptions()));
636 runSignatureHelp(Server
, FooCpp
, Position(), MarkupKind::PlainText
));
637 // Identifier-based fallback completion.
638 EXPECT_THAT(cantFail(runCodeComplete(Server
, FooCpp
, Position(),
639 clangd::CodeCompleteOptions()))
641 ElementsAre(Field(&CodeCompletion::Name
, "int"),
642 Field(&CodeCompletion::Name
, "main")));
645 TEST(ClangdThreadingTest
, StressTest
) {
646 // Without 'static' clang gives an error for a usage inside TestDiagConsumer.
647 static const unsigned FilesCount
= 5;
648 const unsigned RequestsCount
= 500;
649 // Blocking requests wait for the parsing to complete, they slow down the test
650 // dramatically, so they are issued rarely. Each
651 // BlockingRequestInterval-request will be a blocking one.
652 const unsigned BlockingRequestInterval
= 40;
654 const auto *SourceContentsWithoutErrors
= R
"cpp(
661 const auto *SourceContentsWithErrors
= R
"cpp(
668 // Giving invalid line and column number should not crash ClangdServer, but
669 // just to make sure we're sometimes hitting the bounds inside the file we
670 // limit the intervals of line and column number that are generated.
671 unsigned MaxLineForFileRequests
= 7;
672 unsigned MaxColumnForFileRequests
= 10;
674 std::vector
<std::string
> FilePaths
;
676 for (unsigned I
= 0; I
< FilesCount
; ++I
) {
677 std::string Name
= std::string("Foo") + std::to_string(I
) + ".cpp";
679 FilePaths
.push_back(testPath(Name
));
683 unsigned HitsWithoutErrors
= 0;
684 unsigned HitsWithErrors
= 0;
685 bool HadErrorsInLastDiags
= false;
688 class TestDiagConsumer
: public ClangdServer::Callbacks
{
690 TestDiagConsumer() : Stats(FilesCount
, FileStat()) {}
692 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
693 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
694 StringRef FileIndexStr
= llvm::sys::path::stem(File
);
695 ASSERT_TRUE(FileIndexStr
.consume_front("Foo"));
697 unsigned long FileIndex
= std::stoul(FileIndexStr
.str());
699 bool HadError
= diagsContainErrors(Diagnostics
);
701 std::lock_guard
<std::mutex
> Lock(Mutex
);
703 Stats
[FileIndex
].HitsWithErrors
++;
705 Stats
[FileIndex
].HitsWithoutErrors
++;
706 Stats
[FileIndex
].HadErrorsInLastDiags
= HadError
;
709 std::vector
<FileStat
> takeFileStats() {
710 std::lock_guard
<std::mutex
> Lock(Mutex
);
711 return std::move(Stats
);
716 std::vector
<FileStat
> Stats
;
719 struct RequestStats
{
720 unsigned RequestsWithoutErrors
= 0;
721 unsigned RequestsWithErrors
= 0;
722 bool LastContentsHadErrors
= false;
723 bool FileIsRemoved
= true;
726 std::vector
<RequestStats
> ReqStats
;
727 ReqStats
.reserve(FilesCount
);
728 for (unsigned FileIndex
= 0; FileIndex
< FilesCount
; ++FileIndex
)
729 ReqStats
.emplace_back();
731 TestDiagConsumer DiagConsumer
;
733 MockCompilationDatabase CDB
;
734 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
736 // Prepare some random distributions for the test.
737 std::random_device RandGen
;
739 std::uniform_int_distribution
<unsigned> FileIndexDist(0, FilesCount
- 1);
740 // Pass a text that contains compiler errors to addDocument in about 20% of
742 std::bernoulli_distribution
ShouldHaveErrorsDist(0.2);
743 // Line and Column numbers for requests that need them.
744 std::uniform_int_distribution
<int> LineDist(0, MaxLineForFileRequests
);
745 std::uniform_int_distribution
<int> ColumnDist(0, MaxColumnForFileRequests
);
748 auto UpdateStatsOnAddDocument
= [&](unsigned FileIndex
, bool HadErrors
) {
749 auto &Stats
= ReqStats
[FileIndex
];
752 ++Stats
.RequestsWithErrors
;
754 ++Stats
.RequestsWithoutErrors
;
755 Stats
.LastContentsHadErrors
= HadErrors
;
756 Stats
.FileIsRemoved
= false;
759 auto UpdateStatsOnRemoveDocument
= [&](unsigned FileIndex
) {
760 auto &Stats
= ReqStats
[FileIndex
];
762 Stats
.FileIsRemoved
= true;
765 auto AddDocument
= [&](unsigned FileIndex
, bool SkipCache
) {
766 bool ShouldHaveErrors
= ShouldHaveErrorsDist(RandGen
);
767 Server
.addDocument(FilePaths
[FileIndex
],
768 ShouldHaveErrors
? SourceContentsWithErrors
769 : SourceContentsWithoutErrors
);
770 UpdateStatsOnAddDocument(FileIndex
, ShouldHaveErrors
);
773 // Various requests that we would randomly run.
774 auto AddDocumentRequest
= [&]() {
775 unsigned FileIndex
= FileIndexDist(RandGen
);
776 AddDocument(FileIndex
, /*SkipCache=*/false);
779 auto ForceReparseRequest
= [&]() {
780 unsigned FileIndex
= FileIndexDist(RandGen
);
781 AddDocument(FileIndex
, /*SkipCache=*/true);
784 auto RemoveDocumentRequest
= [&]() {
785 unsigned FileIndex
= FileIndexDist(RandGen
);
786 // Make sure we don't violate the ClangdServer's contract.
787 if (ReqStats
[FileIndex
].FileIsRemoved
)
788 AddDocument(FileIndex
, /*SkipCache=*/false);
790 Server
.removeDocument(FilePaths
[FileIndex
]);
791 UpdateStatsOnRemoveDocument(FileIndex
);
794 auto CodeCompletionRequest
= [&]() {
795 unsigned FileIndex
= FileIndexDist(RandGen
);
796 // Make sure we don't violate the ClangdServer's contract.
797 if (ReqStats
[FileIndex
].FileIsRemoved
)
798 AddDocument(FileIndex
, /*SkipCache=*/false);
801 Pos
.line
= LineDist(RandGen
);
802 Pos
.character
= ColumnDist(RandGen
);
803 // FIXME(ibiryukov): Also test async completion requests.
804 // Simply putting CodeCompletion into async requests now would make
805 // tests slow, since there's no way to cancel previous completion
806 // requests as opposed to AddDocument/RemoveDocument, which are implicitly
807 // cancelled by any subsequent AddDocument/RemoveDocument request to the
809 cantFail(runCodeComplete(Server
, FilePaths
[FileIndex
], Pos
,
810 clangd::CodeCompleteOptions()));
813 auto LocateSymbolRequest
= [&]() {
814 unsigned FileIndex
= FileIndexDist(RandGen
);
815 // Make sure we don't violate the ClangdServer's contract.
816 if (ReqStats
[FileIndex
].FileIsRemoved
)
817 AddDocument(FileIndex
, /*SkipCache=*/false);
820 Pos
.line
= LineDist(RandGen
);
821 Pos
.character
= ColumnDist(RandGen
);
823 ASSERT_TRUE(!!runLocateSymbolAt(Server
, FilePaths
[FileIndex
], Pos
));
826 std::vector
<std::function
<void()>> AsyncRequests
= {
827 AddDocumentRequest
, ForceReparseRequest
, RemoveDocumentRequest
};
828 std::vector
<std::function
<void()>> BlockingRequests
= {
829 CodeCompletionRequest
, LocateSymbolRequest
};
831 // Bash requests to ClangdServer in a loop.
832 std::uniform_int_distribution
<int> AsyncRequestIndexDist(
833 0, AsyncRequests
.size() - 1);
834 std::uniform_int_distribution
<int> BlockingRequestIndexDist(
835 0, BlockingRequests
.size() - 1);
836 for (unsigned I
= 1; I
<= RequestsCount
; ++I
) {
837 if (I
% BlockingRequestInterval
!= 0) {
838 // Issue an async request most of the time. It should be fast.
839 unsigned RequestIndex
= AsyncRequestIndexDist(RandGen
);
840 AsyncRequests
[RequestIndex
]();
842 // Issue a blocking request once in a while.
843 auto RequestIndex
= BlockingRequestIndexDist(RandGen
);
844 BlockingRequests
[RequestIndex
]();
847 ASSERT_TRUE(Server
.blockUntilIdleForTest());
850 // Check some invariants about the state of the program.
851 std::vector
<FileStat
> Stats
= DiagConsumer
.takeFileStats();
852 for (unsigned I
= 0; I
< FilesCount
; ++I
) {
853 if (!ReqStats
[I
].FileIsRemoved
) {
854 ASSERT_EQ(Stats
[I
].HadErrorsInLastDiags
,
855 ReqStats
[I
].LastContentsHadErrors
);
858 ASSERT_LE(Stats
[I
].HitsWithErrors
, ReqStats
[I
].RequestsWithErrors
);
859 ASSERT_LE(Stats
[I
].HitsWithoutErrors
, ReqStats
[I
].RequestsWithoutErrors
);
863 TEST(ClangdThreadingTest
, NoConcurrentDiagnostics
) {
864 class NoConcurrentAccessDiagConsumer
: public ClangdServer::Callbacks
{
866 std::atomic
<int> Count
= {0};
868 NoConcurrentAccessDiagConsumer(std::promise
<void> StartSecondReparse
)
869 : StartSecondReparse(std::move(StartSecondReparse
)) {}
871 void onDiagnosticsReady(PathRef
, llvm::StringRef
,
872 llvm::ArrayRef
<Diag
>) override
{
874 std::unique_lock
<std::mutex
> Lock(Mutex
, std::try_to_lock_t());
875 ASSERT_TRUE(Lock
.owns_lock())
876 << "Detected concurrent onDiagnosticsReady calls for the same file.";
878 // If we started the second parse immediately, it might cancel the first.
879 // So we don't allow it to start until the first has delivered diags...
881 FirstRequest
= false;
882 StartSecondReparse
.set_value();
883 // ... but then we wait long enough that the callbacks would overlap.
884 std::this_thread::sleep_for(std::chrono::milliseconds(50));
890 bool FirstRequest
= true;
891 std::promise
<void> StartSecondReparse
;
894 const auto *SourceContentsWithoutErrors
= R
"cpp(
901 const auto *SourceContentsWithErrors
= R
"cpp(
908 auto FooCpp
= testPath("foo.cpp");
910 FS
.Files
[FooCpp
] = "";
912 std::promise
<void> StartSecondPromise
;
913 std::future
<void> StartSecond
= StartSecondPromise
.get_future();
915 NoConcurrentAccessDiagConsumer
DiagConsumer(std::move(StartSecondPromise
));
916 MockCompilationDatabase CDB
;
917 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
918 Server
.addDocument(FooCpp
, SourceContentsWithErrors
);
920 Server
.addDocument(FooCpp
, SourceContentsWithoutErrors
);
921 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
922 ASSERT_EQ(DiagConsumer
.Count
, 2); // Sanity check - we actually ran both?
925 TEST(ClangdServerTest
, FormatCode
) {
927 ErrorCheckingCallbacks DiagConsumer
;
928 MockCompilationDatabase CDB
;
929 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
931 auto Path
= testPath("foo.cpp");
932 std::string Code
= R
"cpp(
938 std::string Expected
= R
"cpp(
944 FS
.Files
[Path
] = Code
;
945 runAddDocument(Server
, Path
, Code
);
947 auto Replaces
= runFormatFile(Server
, Path
, /*Rng=*/std::nullopt
);
948 EXPECT_TRUE(static_cast<bool>(Replaces
));
949 auto Changed
= tooling::applyAllReplacements(Code
, *Replaces
);
950 EXPECT_TRUE(static_cast<bool>(Changed
));
951 EXPECT_EQ(Expected
, *Changed
);
954 TEST(ClangdServerTest
, ChangedHeaderFromISystem
) {
956 ErrorCheckingCallbacks DiagConsumer
;
957 MockCompilationDatabase CDB
;
958 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
960 auto SourcePath
= testPath("source/foo.cpp");
961 auto HeaderPath
= testPath("headers/foo.h");
962 FS
.Files
[HeaderPath
] = "struct X { int bar; };";
963 Annotations
Code(R
"cpp(
969 CDB
.ExtraClangFlags
.push_back("-xc++");
970 CDB
.ExtraClangFlags
.push_back("-isystem" + testPath("headers"));
972 runAddDocument(Server
, SourcePath
, Code
.code());
973 auto Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
974 clangd::CodeCompleteOptions()))
976 EXPECT_THAT(Completions
, ElementsAre(Field(&CodeCompletion::Name
, "bar")));
977 // Update the header and rerun addDocument to make sure we get the updated
979 FS
.Files
[HeaderPath
] = "struct X { int bar; int baz; };";
980 runAddDocument(Server
, SourcePath
, Code
.code());
981 Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
982 clangd::CodeCompleteOptions()))
984 // We want to make sure we see the updated version.
985 EXPECT_THAT(Completions
, ElementsAre(Field(&CodeCompletion::Name
, "bar"),
986 Field(&CodeCompletion::Name
, "baz")));
989 // FIXME(ioeric): make this work for windows again.
991 // Check that running code completion doesn't stat() a bunch of files from the
992 // preamble again. (They should be using the preamble's stat-cache)
993 TEST(ClangdTests
, PreambleVFSStatCache
) {
994 class StatRecordingFS
: public ThreadsafeFS
{
995 llvm::StringMap
<unsigned> &CountStats
;
998 // If relative paths are used, they are resolved with testPath().
999 llvm::StringMap
<std::string
> Files
;
1001 StatRecordingFS(llvm::StringMap
<unsigned> &CountStats
)
1002 : CountStats(CountStats
) {}
1005 IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> viewImpl() const override
{
1006 class StatRecordingVFS
: public llvm::vfs::ProxyFileSystem
{
1008 StatRecordingVFS(IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> FS
,
1009 llvm::StringMap
<unsigned> &CountStats
)
1010 : ProxyFileSystem(std::move(FS
)), CountStats(CountStats
) {}
1012 llvm::ErrorOr
<std::unique_ptr
<llvm::vfs::File
>>
1013 openFileForRead(const Twine
&Path
) override
{
1014 ++CountStats
[llvm::sys::path::filename(Path
.str())];
1015 return ProxyFileSystem::openFileForRead(Path
);
1017 llvm::ErrorOr
<llvm::vfs::Status
> status(const Twine
&Path
) override
{
1018 ++CountStats
[llvm::sys::path::filename(Path
.str())];
1019 return ProxyFileSystem::status(Path
);
1023 llvm::StringMap
<unsigned> &CountStats
;
1026 return IntrusiveRefCntPtr
<StatRecordingVFS
>(
1027 new StatRecordingVFS(buildTestFS(Files
), CountStats
));
1031 llvm::StringMap
<unsigned> CountStats
;
1032 StatRecordingFS
FS(CountStats
);
1033 ErrorCheckingCallbacks DiagConsumer
;
1034 MockCompilationDatabase CDB
;
1035 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1037 auto SourcePath
= testPath("foo.cpp");
1038 auto HeaderPath
= testPath("foo.h");
1039 FS
.Files
[HeaderPath
] = "struct TestSym {};";
1040 Annotations
Code(R
"cpp(
1047 runAddDocument(Server
, SourcePath
, Code
.code());
1049 unsigned Before
= CountStats
["foo.h"];
1050 EXPECT_GT(Before
, 0u);
1051 auto Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
1052 clangd::CodeCompleteOptions()))
1054 EXPECT_EQ(CountStats
["foo.h"], Before
);
1055 EXPECT_THAT(Completions
,
1056 ElementsAre(Field(&CodeCompletion::Name
, "TestSym")));
1060 TEST(ClangdServerTest
, FallbackWhenPreambleIsNotReady
) {
1062 ErrorCheckingCallbacks DiagConsumer
;
1063 MockCompilationDatabase CDB
;
1064 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1066 auto FooCpp
= testPath("foo.cpp");
1067 Annotations
Code(R
"cpp(
1068 namespace ns { int xyz; }
1073 FS
.Files
[FooCpp
] = FooCpp
;
1075 auto Opts
= clangd::CodeCompleteOptions();
1076 Opts
.RunParser
= CodeCompleteOptions::ParseIfReady
;
1078 // This will make compile command broken and preamble absent.
1079 CDB
.ExtraClangFlags
= {"-###"};
1080 Server
.addDocument(FooCpp
, Code
.code());
1081 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1082 auto Res
= cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
));
1083 EXPECT_EQ(Res
.Context
, CodeCompletionContext::CCC_Recovery
);
1084 // Identifier-based fallback completion doesn't know about "symbol" scope.
1085 EXPECT_THAT(Res
.Completions
,
1086 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1087 Field(&CodeCompletion::Scope
, ""))));
1089 // Make the compile command work again.
1090 CDB
.ExtraClangFlags
= {"-std=c++11"};
1091 Server
.addDocument(FooCpp
, Code
.code());
1092 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1094 cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
)).Completions
,
1095 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1096 Field(&CodeCompletion::Scope
, "ns::"))));
1098 // Now force identifier-based completion.
1099 Opts
.RunParser
= CodeCompleteOptions::NeverParse
;
1101 cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
)).Completions
,
1102 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1103 Field(&CodeCompletion::Scope
, ""))));
1106 TEST(ClangdServerTest
, FallbackWhenWaitingForCompileCommand
) {
1108 ErrorCheckingCallbacks DiagConsumer
;
1109 // Returns compile command only when notified.
1110 class DelayedCompilationDatabase
: public GlobalCompilationDatabase
{
1112 DelayedCompilationDatabase(Notification
&CanReturnCommand
)
1113 : CanReturnCommand(CanReturnCommand
) {}
1115 std::optional
<tooling::CompileCommand
>
1116 getCompileCommand(PathRef File
) const override
{
1117 // FIXME: make this timeout and fail instead of waiting forever in case
1118 // something goes wrong.
1119 CanReturnCommand
.wait();
1120 auto FileName
= llvm::sys::path::filename(File
);
1121 std::vector
<std::string
> CommandLine
= {"clangd", "-ffreestanding",
1123 return {tooling::CompileCommand(llvm::sys::path::parent_path(File
),
1124 FileName
, std::move(CommandLine
), "")};
1127 std::vector
<std::string
> ExtraClangFlags
;
1130 Notification
&CanReturnCommand
;
1133 Notification CanReturnCommand
;
1134 DelayedCompilationDatabase
CDB(CanReturnCommand
);
1135 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1137 auto FooCpp
= testPath("foo.cpp");
1138 Annotations
Code(R
"cpp(
1139 namespace ns { int xyz; }
1144 FS
.Files
[FooCpp
] = FooCpp
;
1145 Server
.addDocument(FooCpp
, Code
.code());
1147 // Sleep for some time to make sure code completion is not run because update
1148 // hasn't been scheduled.
1149 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1150 auto Opts
= clangd::CodeCompleteOptions();
1151 Opts
.RunParser
= CodeCompleteOptions::ParseIfReady
;
1153 auto Res
= cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
));
1154 EXPECT_EQ(Res
.Context
, CodeCompletionContext::CCC_Recovery
);
1156 CanReturnCommand
.notify();
1157 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1158 EXPECT_THAT(cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(),
1159 clangd::CodeCompleteOptions()))
1161 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1162 Field(&CodeCompletion::Scope
, "ns::"))));
1165 TEST(ClangdServerTest
, CustomAction
) {
1166 OverlayCDB
CDB(/*Base=*/nullptr);
1168 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest());
1170 Server
.addDocument(testPath("foo.cc"), "void x();");
1171 Decl::Kind XKind
= Decl::TranslationUnit
;
1172 EXPECT_THAT_ERROR(runCustomAction(Server
, testPath("foo.cc"),
1173 [&](InputsAndAST AST
) {
1174 XKind
= findDecl(AST
.AST
, "x").getKind();
1177 EXPECT_EQ(XKind
, Decl::Function
);
1180 // Tests fails when built with asan due to stack overflow. So skip running the
1181 // test as a workaround.
1182 #if !defined(__has_feature) || !__has_feature(address_sanitizer)
1183 TEST(ClangdServerTest
, TestStackOverflow
) {
1185 ErrorCheckingCallbacks DiagConsumer
;
1186 MockCompilationDatabase CDB
;
1187 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1189 const char *SourceContents
= R
"cpp(
1190 constexpr int foo() { return foo(); }
1191 static_assert(foo());
1194 auto FooCpp
= testPath("foo.cpp");
1195 FS
.Files
[FooCpp
] = SourceContents
;
1197 Server
.addDocument(FooCpp
, SourceContents
);
1198 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
1199 // check that we got a constexpr depth error, and not crashed by stack
1201 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
1205 TEST(ClangdServer
, TidyOverrideTest
) {
1206 struct DiagsCheckingCallback
: public ClangdServer::Callbacks
{
1208 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
1209 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
1210 std::lock_guard
<std::mutex
> Lock(Mutex
);
1211 HadDiagsInLastCallback
= !Diagnostics
.empty();
1215 bool HadDiagsInLastCallback
= false;
1219 // These checks don't work well in clangd, even if configured they shouldn't
1221 FS
.Files
[testPath(".clang-tidy")] = R
"(
1222 Checks: -*,bugprone-use-after-move,llvm-header-guard
1224 MockCompilationDatabase CDB
;
1225 std::vector
<TidyProvider
> Stack
;
1226 Stack
.push_back(provideClangTidyFiles(FS
));
1227 Stack
.push_back(disableUnusableChecks());
1228 TidyProvider Provider
= combine(std::move(Stack
));
1229 CDB
.ExtraClangFlags
= {"-xc++"};
1230 auto Opts
= ClangdServer::optsForTest();
1231 Opts
.ClangTidyProvider
= Provider
;
1232 ClangdServer
Server(CDB
, FS
, Opts
, &DiagConsumer
);
1233 const char *SourceContents
= R
"cpp(
1234 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1235 namespace std { Foo&& move(Foo&); }
1238 Foo y = std::move(x);
1241 Server
.addDocument(testPath("foo.h"), SourceContents
);
1242 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1243 EXPECT_FALSE(DiagConsumer
.HadDiagsInLastCallback
);
1246 TEST(ClangdServer
, MemoryUsageTest
) {
1248 MockCompilationDatabase CDB
;
1249 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest());
1251 auto FooCpp
= testPath("foo.cpp");
1252 Server
.addDocument(FooCpp
, "");
1253 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1255 llvm::BumpPtrAllocator Alloc
;
1256 MemoryTree
MT(&Alloc
);
1258 ASSERT_TRUE(MT
.children().count("tuscheduler"));
1259 EXPECT_TRUE(MT
.child("tuscheduler").children().count(FooCpp
));
1262 TEST(ClangdServer
, RespectsTweakFormatting
) {
1263 static constexpr const char *TweakID
= "ModuleTweak";
1264 static constexpr const char *NewContents
= "{not;\nformatted;}";
1266 // Contributes a tweak that generates a non-formatted insertion and disables
1268 struct TweakContributingModule final
: public FeatureModule
{
1269 struct ModuleTweak final
: public Tweak
{
1270 const char *id() const override
{ return TweakID
; }
1271 bool prepare(const Selection
&Sel
) override
{ return true; }
1272 Expected
<Effect
> apply(const Selection
&Sel
) override
{
1273 auto &SM
= Sel
.AST
->getSourceManager();
1274 llvm::StringRef FilePath
= SM
.getFilename(Sel
.Cursor
);
1275 tooling::Replacements Reps
;
1277 Reps
.add(tooling::Replacement(FilePath
, 0, 0, NewContents
)));
1278 auto E
= llvm::cantFail(Effect::mainFileEdit(SM
, std::move(Reps
)));
1279 E
.FormatEdits
= false;
1282 std::string
title() const override
{ return id(); }
1283 llvm::StringLiteral
kind() const override
{
1284 return llvm::StringLiteral("");
1288 void contributeTweaks(std::vector
<std::unique_ptr
<Tweak
>> &Out
) override
{
1289 Out
.emplace_back(new ModuleTweak
);
1294 MockCompilationDatabase CDB
;
1295 auto Opts
= ClangdServer::optsForTest();
1296 FeatureModuleSet Set
;
1297 Set
.add(std::make_unique
<TweakContributingModule
>());
1298 Opts
.FeatureModules
= &Set
;
1299 ClangdServer
Server(CDB
, FS
, Opts
);
1301 auto FooCpp
= testPath("foo.cpp");
1302 Server
.addDocument(FooCpp
, "");
1303 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1305 // Ensure that disabled formatting is respected.
1307 Server
.applyTweak(FooCpp
, {}, TweakID
, [&](llvm::Expected
<Tweak::Effect
> E
) {
1308 ASSERT_TRUE(static_cast<bool>(E
));
1309 EXPECT_THAT(llvm::cantFail(E
->ApplyEdits
.lookup(FooCpp
).apply()),
1316 TEST(ClangdServer
, InactiveRegions
) {
1317 struct InactiveRegionsCallback
: ClangdServer::Callbacks
{
1318 std::vector
<std::vector
<Range
>> FoundInactiveRegions
;
1320 void onInactiveRegionsReady(PathRef FIle
,
1321 std::vector
<Range
> InactiveRegions
) override
{
1322 FoundInactiveRegions
.push_back(std::move(InactiveRegions
));
1327 MockCompilationDatabase CDB
;
1328 CDB
.ExtraClangFlags
.push_back("-DCMDMACRO");
1329 auto Opts
= ClangdServer::optsForTest();
1330 Opts
.PublishInactiveRegions
= true;
1331 InactiveRegionsCallback Callback
;
1332 ClangdServer
Server(CDB
, FS
, Opts
, &Callback
);
1333 Annotations
Source(R
"cpp(
1334 #define PREAMBLEMACRO 42
1335 #if PREAMBLEMACRO > 40
1338 $inactive1[[ #define INACTIVE]]
1342 $inactive2[[ int inactiveInt;]]
1346 $inactive3[[ int inactiveInt2;]]
1347 #elif PREAMBLEMACRO > 0
1351 $inactive4[[ int inactiveInt3;]]
1354 #endif // empty inactive range, gets dropped
1356 Server
.addDocument(testPath("foo.cpp"), Source
.code());
1357 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1358 EXPECT_THAT(Callback
.FoundInactiveRegions
,
1359 ElementsAre(ElementsAre(
1360 Source
.range("inactive1"), Source
.range("inactive2"),
1361 Source
.range("inactive3"), Source
.range("inactive4"))));
1365 } // namespace clangd
1366 } // namespace clang