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/Support/Allocator.h"
33 #include "llvm/Support/Error.h"
34 #include "llvm/Support/Path.h"
35 #include "llvm/Support/Regex.h"
36 #include "llvm/Support/VirtualFileSystem.h"
37 #include "llvm/Testing/Support/Error.h"
38 #include "gmock/gmock.h"
39 #include "gtest/gtest.h"
54 using ::testing::AllOf
;
55 using ::testing::ElementsAre
;
56 using ::testing::Field
;
57 using ::testing::IsEmpty
;
58 using ::testing::Pair
;
59 using ::testing::SizeIs
;
60 using ::testing::UnorderedElementsAre
;
62 MATCHER_P2(DeclAt
, File
, Range
, "") {
63 return arg
.PreferredDeclaration
==
64 Location
{URIForFile::canonicalize(File
, testRoot()), Range
};
67 bool diagsContainErrors(const std::vector
<Diag
> &Diagnostics
) {
68 for (auto D
: Diagnostics
) {
69 if (D
.Severity
== DiagnosticsEngine::Error
||
70 D
.Severity
== DiagnosticsEngine::Fatal
)
76 class ErrorCheckingCallbacks
: public ClangdServer::Callbacks
{
78 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
79 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
80 bool HadError
= diagsContainErrors(Diagnostics
);
81 std::lock_guard
<std::mutex
> Lock(Mutex
);
82 HadErrorInLastDiags
= HadError
;
85 bool hadErrorInLastDiags() {
86 std::lock_guard
<std::mutex
> Lock(Mutex
);
87 return HadErrorInLastDiags
;
92 bool HadErrorInLastDiags
= false;
95 /// For each file, record whether the last published diagnostics contained at
97 class MultipleErrorCheckingCallbacks
: public ClangdServer::Callbacks
{
99 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
100 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
101 bool HadError
= diagsContainErrors(Diagnostics
);
103 std::lock_guard
<std::mutex
> Lock(Mutex
);
104 LastDiagsHadError
[File
] = HadError
;
107 /// Exposes all files consumed by onDiagnosticsReady in an unspecified order.
108 /// For each file, a bool value indicates whether the last diagnostics
109 /// contained an error.
110 std::vector
<std::pair
<Path
, bool>> filesWithDiags() const {
111 std::vector
<std::pair
<Path
, bool>> Result
;
112 std::lock_guard
<std::mutex
> Lock(Mutex
);
113 for (const auto &It
: LastDiagsHadError
)
114 Result
.emplace_back(std::string(It
.first()), It
.second
);
119 std::lock_guard
<std::mutex
> Lock(Mutex
);
120 LastDiagsHadError
.clear();
124 mutable std::mutex Mutex
;
125 llvm::StringMap
<bool> LastDiagsHadError
;
128 /// Replaces all patterns of the form 0x123abc with spaces
129 std::string
replacePtrsInDump(std::string
const &Dump
) {
130 llvm::Regex
RE("0x[0-9a-fA-F]+");
131 llvm::SmallVector
<llvm::StringRef
, 1> Matches
;
132 llvm::StringRef Pending
= Dump
;
135 while (RE
.match(Pending
, &Matches
)) {
136 assert(Matches
.size() == 1 && "Exactly one match expected");
137 auto MatchPos
= Matches
[0].data() - Pending
.data();
139 Result
+= Pending
.take_front(MatchPos
);
140 Pending
= Pending
.drop_front(MatchPos
+ Matches
[0].size());
147 std::string
dumpAST(ClangdServer
&Server
, PathRef File
) {
150 Server
.customAction(File
, "DumpAST", [&](llvm::Expected
<InputsAndAST
> AST
) {
152 llvm::raw_string_ostream
ResultOS(Result
);
153 AST
->AST
.getASTContext().getTranslationUnitDecl()->dump(ResultOS
, true);
155 llvm::consumeError(AST
.takeError());
164 std::string
dumpASTWithoutMemoryLocs(ClangdServer
&Server
, PathRef File
) {
165 return replacePtrsInDump(dumpAST(Server
, File
));
168 std::string
parseSourceAndDumpAST(
169 PathRef SourceFileRelPath
, llvm::StringRef SourceContents
,
170 std::vector
<std::pair
<PathRef
, llvm::StringRef
>> ExtraFiles
= {},
171 bool ExpectErrors
= false) {
173 ErrorCheckingCallbacks DiagConsumer
;
174 MockCompilationDatabase CDB
;
175 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
176 for (const auto &FileWithContents
: ExtraFiles
)
177 FS
.Files
[testPath(FileWithContents
.first
)] =
178 std::string(FileWithContents
.second
);
180 auto SourceFilename
= testPath(SourceFileRelPath
);
181 Server
.addDocument(SourceFilename
, SourceContents
);
182 auto Result
= dumpASTWithoutMemoryLocs(Server
, SourceFilename
);
183 EXPECT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
184 EXPECT_EQ(ExpectErrors
, DiagConsumer
.hadErrorInLastDiags());
188 TEST(ClangdServerTest
, Parse
) {
189 // FIXME: figure out a stable format for AST dumps, so that we can check the
190 // output of the dump itself is equal to the expected one, not just that it's
192 auto Empty
= parseSourceAndDumpAST("foo.cpp", "");
193 auto OneDecl
= parseSourceAndDumpAST("foo.cpp", "int a;");
194 auto SomeDecls
= parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
195 EXPECT_NE(Empty
, OneDecl
);
196 EXPECT_NE(Empty
, SomeDecls
);
197 EXPECT_NE(SomeDecls
, OneDecl
);
199 auto Empty2
= parseSourceAndDumpAST("foo.cpp", "");
200 auto OneDecl2
= parseSourceAndDumpAST("foo.cpp", "int a;");
201 auto SomeDecls2
= parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
202 EXPECT_EQ(Empty
, Empty2
);
203 EXPECT_EQ(OneDecl
, OneDecl2
);
204 EXPECT_EQ(SomeDecls
, SomeDecls2
);
207 TEST(ClangdServerTest
, ParseWithHeader
) {
208 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {},
209 /*ExpectErrors=*/true);
210 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}},
211 /*ExpectErrors=*/false);
213 const auto *SourceContents
= R
"cpp(
217 parseSourceAndDumpAST("foo.cpp", SourceContents
, {{"foo.h", ""}},
218 /*ExpectErrors=*/true);
219 parseSourceAndDumpAST("foo.cpp", SourceContents
, {{"foo.h", "int a;"}},
220 /*ExpectErrors=*/false);
223 TEST(ClangdServerTest
, Reparse
) {
225 ErrorCheckingCallbacks DiagConsumer
;
226 MockCompilationDatabase CDB
;
227 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
229 const auto *SourceContents
= R
"cpp(
234 auto FooCpp
= testPath("foo.cpp");
236 FS
.Files
[testPath("foo.h")] = "int a;";
237 FS
.Files
[FooCpp
] = SourceContents
;
239 Server
.addDocument(FooCpp
, SourceContents
);
240 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
241 auto DumpParse1
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
242 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
244 Server
.addDocument(FooCpp
, "");
245 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
246 auto DumpParseEmpty
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
247 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
249 Server
.addDocument(FooCpp
, SourceContents
);
250 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
251 auto DumpParse2
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
252 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
254 EXPECT_EQ(DumpParse1
, DumpParse2
);
255 EXPECT_NE(DumpParse1
, DumpParseEmpty
);
258 TEST(ClangdServerTest
, ReparseOnHeaderChange
) {
260 ErrorCheckingCallbacks DiagConsumer
;
261 MockCompilationDatabase CDB
;
262 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
264 const auto *SourceContents
= R
"cpp(
269 auto FooCpp
= testPath("foo.cpp");
270 auto FooH
= testPath("foo.h");
272 FS
.Files
[FooH
] = "int a;";
273 FS
.Files
[FooCpp
] = SourceContents
;
275 Server
.addDocument(FooCpp
, SourceContents
);
276 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
277 auto DumpParse1
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
278 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
281 Server
.addDocument(FooCpp
, SourceContents
);
282 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
283 auto DumpParseDifferent
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
284 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
286 FS
.Files
[FooH
] = "int a;";
287 Server
.addDocument(FooCpp
, SourceContents
);
288 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
289 auto DumpParse2
= dumpASTWithoutMemoryLocs(Server
, FooCpp
);
290 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
292 EXPECT_EQ(DumpParse1
, DumpParse2
);
293 EXPECT_NE(DumpParse1
, DumpParseDifferent
);
296 TEST(ClangdServerTest
, PropagatesContexts
) {
297 static Key
<int> Secret
;
298 struct ContextReadingFS
: public ThreadsafeFS
{
302 IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> viewImpl() const override
{
303 Got
= Context::current().getExisting(Secret
);
304 return buildTestFS({});
307 struct Callbacks
: public ClangdServer::Callbacks
{
308 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
309 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
310 Got
= Context::current().getExisting(Secret
);
314 MockCompilationDatabase CDB
;
316 // Verify that the context is plumbed to the FS provider and diagnostics.
317 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &Callbacks
);
319 WithContextValue
Entrypoint(Secret
, 42);
320 Server
.addDocument(testPath("foo.cpp"), "void main(){}");
322 ASSERT_TRUE(Server
.blockUntilIdleForTest());
323 EXPECT_EQ(FS
.Got
, 42);
324 EXPECT_EQ(Callbacks
.Got
, 42);
327 TEST(ClangdServerTest
, RespectsConfig
) {
328 // Go-to-definition will resolve as marked if FOO is defined.
329 Annotations
Example(R
"cpp(
337 // Provide conditional config that defines FOO for foo.cc.
338 class ConfigProvider
: public config::Provider
{
339 std::vector
<config::CompiledFragment
>
340 getFragments(const config::Params
&,
341 config::DiagnosticCallback DC
) const override
{
343 F
.If
.PathMatch
.emplace_back(".*foo.cc");
344 F
.CompileFlags
.Add
.emplace_back("-DFOO=1");
345 return {std::move(F
).compile(DC
)};
349 auto Opts
= ClangdServer::optsForTest();
350 Opts
.ContextProvider
=
351 ClangdServer::createConfiguredContextProvider(&CfgProvider
, nullptr);
352 OverlayCDB
CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
353 CommandMangler::forTests());
355 ClangdServer
Server(CDB
, FS
, Opts
);
356 // foo.cc sees the expected definition, as FOO is defined.
357 Server
.addDocument(testPath("foo.cc"), Example
.code());
358 auto Result
= runLocateSymbolAt(Server
, testPath("foo.cc"), Example
.point());
359 ASSERT_TRUE(bool(Result
)) << Result
.takeError();
360 ASSERT_THAT(*Result
, SizeIs(1));
361 EXPECT_EQ(Result
->front().PreferredDeclaration
.range
, Example
.range());
362 // bar.cc gets a different result, as FOO is not defined.
363 Server
.addDocument(testPath("bar.cc"), Example
.code());
364 Result
= runLocateSymbolAt(Server
, testPath("bar.cc"), Example
.point());
365 ASSERT_TRUE(bool(Result
)) << Result
.takeError();
366 ASSERT_THAT(*Result
, SizeIs(1));
367 EXPECT_NE(Result
->front().PreferredDeclaration
.range
, Example
.range());
370 TEST(ClangdServerTest
, PropagatesVersion
) {
371 MockCompilationDatabase CDB
;
373 struct Callbacks
: public ClangdServer::Callbacks
{
374 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
375 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
378 std::string Got
= "";
381 // Verify that the version is plumbed to diagnostics.
382 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &Callbacks
);
383 runAddDocument(Server
, testPath("foo.cpp"), "void main(){}", "42");
384 EXPECT_EQ(Callbacks
.Got
, "42");
387 // Only enable this test on Unix
389 TEST(ClangdServerTest
, SearchLibDir
) {
390 // Checks that searches for GCC installation is done through vfs.
392 ErrorCheckingCallbacks DiagConsumer
;
393 MockCompilationDatabase CDB
;
394 CDB
.ExtraClangFlags
.insert(CDB
.ExtraClangFlags
.end(),
395 {"-xc++", "-target", "x86_64-linux-unknown",
396 "-m64", "--gcc-toolchain=/randomusr",
397 "-stdlib=libstdc++"});
398 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
400 // Just a random gcc version string
401 SmallString
<8> Version("4.9.3");
403 // A lib dir for gcc installation
404 SmallString
<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu");
405 llvm::sys::path::append(LibDir
, Version
);
407 // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc
408 // installation there.
409 SmallString
<64> MockLibFile
;
410 llvm::sys::path::append(MockLibFile
, LibDir
, "64", "crtbegin.o");
411 FS
.Files
[MockLibFile
] = "";
413 SmallString
<64> IncludeDir("/randomusr/include/c++");
414 llvm::sys::path::append(IncludeDir
, Version
);
416 SmallString
<64> StringPath
;
417 llvm::sys::path::append(StringPath
, IncludeDir
, "string");
418 FS
.Files
[StringPath
] = "class mock_string {};";
420 auto FooCpp
= testPath("foo.cpp");
421 const auto *SourceContents
= R
"cpp(
425 FS
.Files
[FooCpp
] = SourceContents
;
427 runAddDocument(Server
, FooCpp
, SourceContents
);
428 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
430 const auto *SourceContentsWithError
= R
"cpp(
434 runAddDocument(Server
, FooCpp
, SourceContentsWithError
);
435 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
437 #endif // LLVM_ON_UNIX
439 TEST(ClangdServerTest
, ForceReparseCompileCommand
) {
441 ErrorCheckingCallbacks DiagConsumer
;
442 MockCompilationDatabase CDB
;
443 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
445 auto FooCpp
= testPath("foo.cpp");
446 const auto *SourceContents1
= R
"cpp(
450 const auto *SourceContents2
= R
"cpp(
455 FS
.Files
[FooCpp
] = "";
457 // First parse files in C mode and check they produce errors.
458 CDB
.ExtraClangFlags
= {"-xc"};
459 runAddDocument(Server
, FooCpp
, SourceContents1
);
460 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
461 runAddDocument(Server
, FooCpp
, SourceContents2
);
462 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
464 // Now switch to C++ mode.
465 CDB
.ExtraClangFlags
= {"-xc++"};
466 runAddDocument(Server
, FooCpp
, SourceContents2
);
467 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
468 // Subsequent addDocument calls should finish without errors too.
469 runAddDocument(Server
, FooCpp
, SourceContents1
);
470 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
471 runAddDocument(Server
, FooCpp
, SourceContents2
);
472 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
475 TEST(ClangdServerTest
, ForceReparseCompileCommandDefines
) {
477 ErrorCheckingCallbacks DiagConsumer
;
478 MockCompilationDatabase CDB
;
479 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
481 auto FooCpp
= testPath("foo.cpp");
482 const auto *SourceContents
= R
"cpp(
487 int main() { return 0; }
489 FS
.Files
[FooCpp
] = "";
491 // Parse with define, we expect to see the errors.
492 CDB
.ExtraClangFlags
= {"-DWITH_ERROR"};
493 runAddDocument(Server
, FooCpp
, SourceContents
);
494 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
496 // Parse without the define, no errors should be produced.
497 CDB
.ExtraClangFlags
= {};
498 runAddDocument(Server
, FooCpp
, SourceContents
);
499 ASSERT_TRUE(Server
.blockUntilIdleForTest());
500 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
501 // Subsequent addDocument call should finish without errors too.
502 runAddDocument(Server
, FooCpp
, SourceContents
);
503 EXPECT_FALSE(DiagConsumer
.hadErrorInLastDiags());
506 // Test ClangdServer.reparseOpenedFiles.
507 TEST(ClangdServerTest
, ReparseOpenedFiles
) {
508 Annotations
FooSource(R
"cpp(
510 static void $one[[bob]]() {}
512 static void $two[[bob]]() {}
515 int main () { bo^b (); return 0; }
518 Annotations
BarSource(R
"cpp(
524 Annotations
BazSource(R
"cpp(
529 MockCompilationDatabase CDB
;
530 MultipleErrorCheckingCallbacks DiagConsumer
;
531 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
533 auto FooCpp
= testPath("foo.cpp");
534 auto BarCpp
= testPath("bar.cpp");
535 auto BazCpp
= testPath("baz.cpp");
537 FS
.Files
[FooCpp
] = "";
538 FS
.Files
[BarCpp
] = "";
539 FS
.Files
[BazCpp
] = "";
541 CDB
.ExtraClangFlags
= {"-DMACRO=1"};
542 Server
.addDocument(FooCpp
, FooSource
.code());
543 Server
.addDocument(BarCpp
, BarSource
.code());
544 Server
.addDocument(BazCpp
, BazSource
.code());
545 ASSERT_TRUE(Server
.blockUntilIdleForTest());
547 EXPECT_THAT(DiagConsumer
.filesWithDiags(),
548 UnorderedElementsAre(Pair(FooCpp
, false), Pair(BarCpp
, true),
549 Pair(BazCpp
, false)));
551 auto Locations
= runLocateSymbolAt(Server
, FooCpp
, FooSource
.point());
552 EXPECT_TRUE(bool(Locations
));
553 EXPECT_THAT(*Locations
, ElementsAre(DeclAt(FooCpp
, FooSource
.range("one"))));
555 // Undefine MACRO, close baz.cpp.
556 CDB
.ExtraClangFlags
.clear();
557 DiagConsumer
.clear();
558 Server
.removeDocument(BazCpp
);
559 Server
.addDocument(FooCpp
, FooSource
.code());
560 Server
.addDocument(BarCpp
, BarSource
.code());
561 ASSERT_TRUE(Server
.blockUntilIdleForTest());
563 EXPECT_THAT(DiagConsumer
.filesWithDiags(),
564 UnorderedElementsAre(Pair(FooCpp
, false), Pair(BarCpp
, false)));
566 Locations
= runLocateSymbolAt(Server
, FooCpp
, FooSource
.point());
567 EXPECT_TRUE(bool(Locations
));
568 EXPECT_THAT(*Locations
, ElementsAre(DeclAt(FooCpp
, FooSource
.range("two"))));
571 MATCHER_P4(Stats
, Name
, UsesMemory
, PreambleBuilds
, ASTBuilds
, "") {
572 return arg
.first() == Name
&&
573 (arg
.second
.UsedBytesAST
+ arg
.second
.UsedBytesPreamble
!= 0) ==
575 std::tie(arg
.second
.PreambleBuilds
, ASTBuilds
) ==
576 std::tie(PreambleBuilds
, ASTBuilds
);
579 TEST(ClangdServerTest
, FileStats
) {
581 ErrorCheckingCallbacks DiagConsumer
;
582 MockCompilationDatabase CDB
;
583 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
585 Path FooCpp
= testPath("foo.cpp");
586 const auto *SourceContents
= R
"cpp(
591 Path BarCpp
= testPath("bar.cpp");
593 FS
.Files
[FooCpp
] = "";
594 FS
.Files
[BarCpp
] = "";
596 EXPECT_THAT(Server
.fileStats(), IsEmpty());
598 Server
.addDocument(FooCpp
, SourceContents
);
599 Server
.addDocument(BarCpp
, SourceContents
);
600 ASSERT_TRUE(Server
.blockUntilIdleForTest());
602 EXPECT_THAT(Server
.fileStats(),
603 UnorderedElementsAre(Stats(FooCpp
, true, 1, 1),
604 Stats(BarCpp
, true, 1, 1)));
606 Server
.removeDocument(FooCpp
);
607 ASSERT_TRUE(Server
.blockUntilIdleForTest());
608 EXPECT_THAT(Server
.fileStats(), ElementsAre(Stats(BarCpp
, true, 1, 1)));
610 Server
.removeDocument(BarCpp
);
611 ASSERT_TRUE(Server
.blockUntilIdleForTest());
612 EXPECT_THAT(Server
.fileStats(), IsEmpty());
615 TEST(ClangdServerTest
, InvalidCompileCommand
) {
617 ErrorCheckingCallbacks DiagConsumer
;
618 MockCompilationDatabase CDB
;
620 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
622 auto FooCpp
= testPath("foo.cpp");
623 // clang cannot create CompilerInvocation in this case.
624 CDB
.ExtraClangFlags
.push_back("-###");
626 // Clang can't parse command args in that case, but we shouldn't crash.
627 runAddDocument(Server
, FooCpp
, "int main() {}");
629 EXPECT_EQ(dumpAST(Server
, FooCpp
), "<no-ast>");
630 EXPECT_ERROR(runLocateSymbolAt(Server
, FooCpp
, Position()));
631 EXPECT_ERROR(runFindDocumentHighlights(Server
, FooCpp
, Position()));
632 EXPECT_ERROR(runRename(Server
, FooCpp
, Position(), "new_name",
633 clangd::RenameOptions()));
635 runSignatureHelp(Server
, FooCpp
, Position(), MarkupKind::PlainText
));
636 // Identifier-based fallback completion.
637 EXPECT_THAT(cantFail(runCodeComplete(Server
, FooCpp
, Position(),
638 clangd::CodeCompleteOptions()))
640 ElementsAre(Field(&CodeCompletion::Name
, "int"),
641 Field(&CodeCompletion::Name
, "main")));
644 TEST(ClangdThreadingTest
, StressTest
) {
645 // Without 'static' clang gives an error for a usage inside TestDiagConsumer.
646 static const unsigned FilesCount
= 5;
647 const unsigned RequestsCount
= 500;
648 // Blocking requests wait for the parsing to complete, they slow down the test
649 // dramatically, so they are issued rarely. Each
650 // BlockingRequestInterval-request will be a blocking one.
651 const unsigned BlockingRequestInterval
= 40;
653 const auto *SourceContentsWithoutErrors
= R
"cpp(
660 const auto *SourceContentsWithErrors
= R
"cpp(
667 // Giving invalid line and column number should not crash ClangdServer, but
668 // just to make sure we're sometimes hitting the bounds inside the file we
669 // limit the intervals of line and column number that are generated.
670 unsigned MaxLineForFileRequests
= 7;
671 unsigned MaxColumnForFileRequests
= 10;
673 std::vector
<std::string
> FilePaths
;
675 for (unsigned I
= 0; I
< FilesCount
; ++I
) {
676 std::string Name
= std::string("Foo") + std::to_string(I
) + ".cpp";
678 FilePaths
.push_back(testPath(Name
));
682 unsigned HitsWithoutErrors
= 0;
683 unsigned HitsWithErrors
= 0;
684 bool HadErrorsInLastDiags
= false;
687 class TestDiagConsumer
: public ClangdServer::Callbacks
{
689 TestDiagConsumer() : Stats(FilesCount
, FileStat()) {}
691 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
692 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
693 StringRef FileIndexStr
= llvm::sys::path::stem(File
);
694 ASSERT_TRUE(FileIndexStr
.consume_front("Foo"));
696 unsigned long FileIndex
= std::stoul(FileIndexStr
.str());
698 bool HadError
= diagsContainErrors(Diagnostics
);
700 std::lock_guard
<std::mutex
> Lock(Mutex
);
702 Stats
[FileIndex
].HitsWithErrors
++;
704 Stats
[FileIndex
].HitsWithoutErrors
++;
705 Stats
[FileIndex
].HadErrorsInLastDiags
= HadError
;
708 std::vector
<FileStat
> takeFileStats() {
709 std::lock_guard
<std::mutex
> Lock(Mutex
);
710 return std::move(Stats
);
715 std::vector
<FileStat
> Stats
;
718 struct RequestStats
{
719 unsigned RequestsWithoutErrors
= 0;
720 unsigned RequestsWithErrors
= 0;
721 bool LastContentsHadErrors
= false;
722 bool FileIsRemoved
= true;
725 std::vector
<RequestStats
> ReqStats
;
726 ReqStats
.reserve(FilesCount
);
727 for (unsigned FileIndex
= 0; FileIndex
< FilesCount
; ++FileIndex
)
728 ReqStats
.emplace_back();
730 TestDiagConsumer DiagConsumer
;
732 MockCompilationDatabase CDB
;
733 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
735 // Prepare some random distributions for the test.
736 std::random_device RandGen
;
738 std::uniform_int_distribution
<unsigned> FileIndexDist(0, FilesCount
- 1);
739 // Pass a text that contains compiler errors to addDocument in about 20% of
741 std::bernoulli_distribution
ShouldHaveErrorsDist(0.2);
742 // Line and Column numbers for requests that need them.
743 std::uniform_int_distribution
<int> LineDist(0, MaxLineForFileRequests
);
744 std::uniform_int_distribution
<int> ColumnDist(0, MaxColumnForFileRequests
);
747 auto UpdateStatsOnAddDocument
= [&](unsigned FileIndex
, bool HadErrors
) {
748 auto &Stats
= ReqStats
[FileIndex
];
751 ++Stats
.RequestsWithErrors
;
753 ++Stats
.RequestsWithoutErrors
;
754 Stats
.LastContentsHadErrors
= HadErrors
;
755 Stats
.FileIsRemoved
= false;
758 auto UpdateStatsOnRemoveDocument
= [&](unsigned FileIndex
) {
759 auto &Stats
= ReqStats
[FileIndex
];
761 Stats
.FileIsRemoved
= true;
764 auto AddDocument
= [&](unsigned FileIndex
, bool SkipCache
) {
765 bool ShouldHaveErrors
= ShouldHaveErrorsDist(RandGen
);
766 Server
.addDocument(FilePaths
[FileIndex
],
767 ShouldHaveErrors
? SourceContentsWithErrors
768 : SourceContentsWithoutErrors
);
769 UpdateStatsOnAddDocument(FileIndex
, ShouldHaveErrors
);
772 // Various requests that we would randomly run.
773 auto AddDocumentRequest
= [&]() {
774 unsigned FileIndex
= FileIndexDist(RandGen
);
775 AddDocument(FileIndex
, /*SkipCache=*/false);
778 auto ForceReparseRequest
= [&]() {
779 unsigned FileIndex
= FileIndexDist(RandGen
);
780 AddDocument(FileIndex
, /*SkipCache=*/true);
783 auto RemoveDocumentRequest
= [&]() {
784 unsigned FileIndex
= FileIndexDist(RandGen
);
785 // Make sure we don't violate the ClangdServer's contract.
786 if (ReqStats
[FileIndex
].FileIsRemoved
)
787 AddDocument(FileIndex
, /*SkipCache=*/false);
789 Server
.removeDocument(FilePaths
[FileIndex
]);
790 UpdateStatsOnRemoveDocument(FileIndex
);
793 auto CodeCompletionRequest
= [&]() {
794 unsigned FileIndex
= FileIndexDist(RandGen
);
795 // Make sure we don't violate the ClangdServer's contract.
796 if (ReqStats
[FileIndex
].FileIsRemoved
)
797 AddDocument(FileIndex
, /*SkipCache=*/false);
800 Pos
.line
= LineDist(RandGen
);
801 Pos
.character
= ColumnDist(RandGen
);
802 // FIXME(ibiryukov): Also test async completion requests.
803 // Simply putting CodeCompletion into async requests now would make
804 // tests slow, since there's no way to cancel previous completion
805 // requests as opposed to AddDocument/RemoveDocument, which are implicitly
806 // cancelled by any subsequent AddDocument/RemoveDocument request to the
808 cantFail(runCodeComplete(Server
, FilePaths
[FileIndex
], Pos
,
809 clangd::CodeCompleteOptions()));
812 auto LocateSymbolRequest
= [&]() {
813 unsigned FileIndex
= FileIndexDist(RandGen
);
814 // Make sure we don't violate the ClangdServer's contract.
815 if (ReqStats
[FileIndex
].FileIsRemoved
)
816 AddDocument(FileIndex
, /*SkipCache=*/false);
819 Pos
.line
= LineDist(RandGen
);
820 Pos
.character
= ColumnDist(RandGen
);
822 ASSERT_TRUE(!!runLocateSymbolAt(Server
, FilePaths
[FileIndex
], Pos
));
825 std::vector
<std::function
<void()>> AsyncRequests
= {
826 AddDocumentRequest
, ForceReparseRequest
, RemoveDocumentRequest
};
827 std::vector
<std::function
<void()>> BlockingRequests
= {
828 CodeCompletionRequest
, LocateSymbolRequest
};
830 // Bash requests to ClangdServer in a loop.
831 std::uniform_int_distribution
<int> AsyncRequestIndexDist(
832 0, AsyncRequests
.size() - 1);
833 std::uniform_int_distribution
<int> BlockingRequestIndexDist(
834 0, BlockingRequests
.size() - 1);
835 for (unsigned I
= 1; I
<= RequestsCount
; ++I
) {
836 if (I
% BlockingRequestInterval
!= 0) {
837 // Issue an async request most of the time. It should be fast.
838 unsigned RequestIndex
= AsyncRequestIndexDist(RandGen
);
839 AsyncRequests
[RequestIndex
]();
841 // Issue a blocking request once in a while.
842 auto RequestIndex
= BlockingRequestIndexDist(RandGen
);
843 BlockingRequests
[RequestIndex
]();
846 ASSERT_TRUE(Server
.blockUntilIdleForTest());
849 // Check some invariants about the state of the program.
850 std::vector
<FileStat
> Stats
= DiagConsumer
.takeFileStats();
851 for (unsigned I
= 0; I
< FilesCount
; ++I
) {
852 if (!ReqStats
[I
].FileIsRemoved
) {
853 ASSERT_EQ(Stats
[I
].HadErrorsInLastDiags
,
854 ReqStats
[I
].LastContentsHadErrors
);
857 ASSERT_LE(Stats
[I
].HitsWithErrors
, ReqStats
[I
].RequestsWithErrors
);
858 ASSERT_LE(Stats
[I
].HitsWithoutErrors
, ReqStats
[I
].RequestsWithoutErrors
);
862 TEST(ClangdThreadingTest
, NoConcurrentDiagnostics
) {
863 class NoConcurrentAccessDiagConsumer
: public ClangdServer::Callbacks
{
865 std::atomic
<int> Count
= {0};
867 NoConcurrentAccessDiagConsumer(std::promise
<void> StartSecondReparse
)
868 : StartSecondReparse(std::move(StartSecondReparse
)) {}
870 void onDiagnosticsReady(PathRef
, llvm::StringRef
,
871 llvm::ArrayRef
<Diag
>) override
{
873 std::unique_lock
<std::mutex
> Lock(Mutex
, std::try_to_lock_t());
874 ASSERT_TRUE(Lock
.owns_lock())
875 << "Detected concurrent onDiagnosticsReady calls for the same file.";
877 // If we started the second parse immediately, it might cancel the first.
878 // So we don't allow it to start until the first has delivered diags...
880 FirstRequest
= false;
881 StartSecondReparse
.set_value();
882 // ... but then we wait long enough that the callbacks would overlap.
883 std::this_thread::sleep_for(std::chrono::milliseconds(50));
889 bool FirstRequest
= true;
890 std::promise
<void> StartSecondReparse
;
893 const auto *SourceContentsWithoutErrors
= R
"cpp(
900 const auto *SourceContentsWithErrors
= R
"cpp(
907 auto FooCpp
= testPath("foo.cpp");
909 FS
.Files
[FooCpp
] = "";
911 std::promise
<void> StartSecondPromise
;
912 std::future
<void> StartSecond
= StartSecondPromise
.get_future();
914 NoConcurrentAccessDiagConsumer
DiagConsumer(std::move(StartSecondPromise
));
915 MockCompilationDatabase CDB
;
916 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
917 Server
.addDocument(FooCpp
, SourceContentsWithErrors
);
919 Server
.addDocument(FooCpp
, SourceContentsWithoutErrors
);
920 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
921 ASSERT_EQ(DiagConsumer
.Count
, 2); // Sanity check - we actually ran both?
924 TEST(ClangdServerTest
, FormatCode
) {
926 ErrorCheckingCallbacks DiagConsumer
;
927 MockCompilationDatabase CDB
;
928 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
930 auto Path
= testPath("foo.cpp");
931 std::string Code
= R
"cpp(
937 std::string Expected
= R
"cpp(
943 FS
.Files
[Path
] = Code
;
944 runAddDocument(Server
, Path
, Code
);
946 auto Replaces
= runFormatFile(Server
, Path
, /*Rng=*/std::nullopt
);
947 EXPECT_TRUE(static_cast<bool>(Replaces
));
948 auto Changed
= tooling::applyAllReplacements(Code
, *Replaces
);
949 EXPECT_TRUE(static_cast<bool>(Changed
));
950 EXPECT_EQ(Expected
, *Changed
);
953 TEST(ClangdServerTest
, ChangedHeaderFromISystem
) {
955 ErrorCheckingCallbacks DiagConsumer
;
956 MockCompilationDatabase CDB
;
957 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
959 auto SourcePath
= testPath("source/foo.cpp");
960 auto HeaderPath
= testPath("headers/foo.h");
961 FS
.Files
[HeaderPath
] = "struct X { int bar; };";
962 Annotations
Code(R
"cpp(
968 CDB
.ExtraClangFlags
.push_back("-xc++");
969 CDB
.ExtraClangFlags
.push_back("-isystem" + testPath("headers"));
971 runAddDocument(Server
, SourcePath
, Code
.code());
972 auto Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
973 clangd::CodeCompleteOptions()))
975 EXPECT_THAT(Completions
, ElementsAre(Field(&CodeCompletion::Name
, "bar")));
976 // Update the header and rerun addDocument to make sure we get the updated
978 FS
.Files
[HeaderPath
] = "struct X { int bar; int baz; };";
979 runAddDocument(Server
, SourcePath
, Code
.code());
980 Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
981 clangd::CodeCompleteOptions()))
983 // We want to make sure we see the updated version.
984 EXPECT_THAT(Completions
, ElementsAre(Field(&CodeCompletion::Name
, "bar"),
985 Field(&CodeCompletion::Name
, "baz")));
988 // FIXME(ioeric): make this work for windows again.
990 // Check that running code completion doesn't stat() a bunch of files from the
991 // preamble again. (They should be using the preamble's stat-cache)
992 TEST(ClangdTests
, PreambleVFSStatCache
) {
993 class StatRecordingFS
: public ThreadsafeFS
{
994 llvm::StringMap
<unsigned> &CountStats
;
997 // If relative paths are used, they are resolved with testPath().
998 llvm::StringMap
<std::string
> Files
;
1000 StatRecordingFS(llvm::StringMap
<unsigned> &CountStats
)
1001 : CountStats(CountStats
) {}
1004 IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> viewImpl() const override
{
1005 class StatRecordingVFS
: public llvm::vfs::ProxyFileSystem
{
1007 StatRecordingVFS(IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> FS
,
1008 llvm::StringMap
<unsigned> &CountStats
)
1009 : ProxyFileSystem(std::move(FS
)), CountStats(CountStats
) {}
1011 llvm::ErrorOr
<std::unique_ptr
<llvm::vfs::File
>>
1012 openFileForRead(const Twine
&Path
) override
{
1013 ++CountStats
[llvm::sys::path::filename(Path
.str())];
1014 return ProxyFileSystem::openFileForRead(Path
);
1016 llvm::ErrorOr
<llvm::vfs::Status
> status(const Twine
&Path
) override
{
1017 ++CountStats
[llvm::sys::path::filename(Path
.str())];
1018 return ProxyFileSystem::status(Path
);
1022 llvm::StringMap
<unsigned> &CountStats
;
1025 return IntrusiveRefCntPtr
<StatRecordingVFS
>(
1026 new StatRecordingVFS(buildTestFS(Files
), CountStats
));
1030 llvm::StringMap
<unsigned> CountStats
;
1031 StatRecordingFS
FS(CountStats
);
1032 ErrorCheckingCallbacks DiagConsumer
;
1033 MockCompilationDatabase CDB
;
1034 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1036 auto SourcePath
= testPath("foo.cpp");
1037 auto HeaderPath
= testPath("foo.h");
1038 FS
.Files
[HeaderPath
] = "struct TestSym {};";
1039 Annotations
Code(R
"cpp(
1046 runAddDocument(Server
, SourcePath
, Code
.code());
1048 unsigned Before
= CountStats
["foo.h"];
1049 EXPECT_GT(Before
, 0u);
1050 auto Completions
= cantFail(runCodeComplete(Server
, SourcePath
, Code
.point(),
1051 clangd::CodeCompleteOptions()))
1053 EXPECT_EQ(CountStats
["foo.h"], Before
);
1054 EXPECT_THAT(Completions
,
1055 ElementsAre(Field(&CodeCompletion::Name
, "TestSym")));
1059 TEST(ClangdServerTest
, FallbackWhenPreambleIsNotReady
) {
1061 ErrorCheckingCallbacks DiagConsumer
;
1062 MockCompilationDatabase CDB
;
1063 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1065 auto FooCpp
= testPath("foo.cpp");
1066 Annotations
Code(R
"cpp(
1067 namespace ns { int xyz; }
1072 FS
.Files
[FooCpp
] = FooCpp
;
1074 auto Opts
= clangd::CodeCompleteOptions();
1075 Opts
.RunParser
= CodeCompleteOptions::ParseIfReady
;
1077 // This will make compile command broken and preamble absent.
1078 CDB
.ExtraClangFlags
= {"-###"};
1079 Server
.addDocument(FooCpp
, Code
.code());
1080 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1081 auto Res
= cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
));
1082 EXPECT_EQ(Res
.Context
, CodeCompletionContext::CCC_Recovery
);
1083 // Identifier-based fallback completion doesn't know about "symbol" scope.
1084 EXPECT_THAT(Res
.Completions
,
1085 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1086 Field(&CodeCompletion::Scope
, ""))));
1088 // Make the compile command work again.
1089 CDB
.ExtraClangFlags
= {"-std=c++11"};
1090 Server
.addDocument(FooCpp
, Code
.code());
1091 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1093 cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
)).Completions
,
1094 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1095 Field(&CodeCompletion::Scope
, "ns::"))));
1097 // Now force identifier-based completion.
1098 Opts
.RunParser
= CodeCompleteOptions::NeverParse
;
1100 cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
)).Completions
,
1101 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1102 Field(&CodeCompletion::Scope
, ""))));
1105 TEST(ClangdServerTest
, FallbackWhenWaitingForCompileCommand
) {
1107 ErrorCheckingCallbacks DiagConsumer
;
1108 // Returns compile command only when notified.
1109 class DelayedCompilationDatabase
: public GlobalCompilationDatabase
{
1111 DelayedCompilationDatabase(Notification
&CanReturnCommand
)
1112 : CanReturnCommand(CanReturnCommand
) {}
1114 std::optional
<tooling::CompileCommand
>
1115 getCompileCommand(PathRef File
) const override
{
1116 // FIXME: make this timeout and fail instead of waiting forever in case
1117 // something goes wrong.
1118 CanReturnCommand
.wait();
1119 auto FileName
= llvm::sys::path::filename(File
);
1120 std::vector
<std::string
> CommandLine
= {"clangd", "-ffreestanding",
1122 return {tooling::CompileCommand(llvm::sys::path::parent_path(File
),
1123 FileName
, std::move(CommandLine
), "")};
1126 std::vector
<std::string
> ExtraClangFlags
;
1129 Notification
&CanReturnCommand
;
1132 Notification CanReturnCommand
;
1133 DelayedCompilationDatabase
CDB(CanReturnCommand
);
1134 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1136 auto FooCpp
= testPath("foo.cpp");
1137 Annotations
Code(R
"cpp(
1138 namespace ns { int xyz; }
1143 FS
.Files
[FooCpp
] = FooCpp
;
1144 Server
.addDocument(FooCpp
, Code
.code());
1146 // Sleep for some time to make sure code completion is not run because update
1147 // hasn't been scheduled.
1148 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1149 auto Opts
= clangd::CodeCompleteOptions();
1150 Opts
.RunParser
= CodeCompleteOptions::ParseIfReady
;
1152 auto Res
= cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(), Opts
));
1153 EXPECT_EQ(Res
.Context
, CodeCompletionContext::CCC_Recovery
);
1155 CanReturnCommand
.notify();
1156 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1157 EXPECT_THAT(cantFail(runCodeComplete(Server
, FooCpp
, Code
.point(),
1158 clangd::CodeCompleteOptions()))
1160 ElementsAre(AllOf(Field(&CodeCompletion::Name
, "xyz"),
1161 Field(&CodeCompletion::Scope
, "ns::"))));
1164 TEST(ClangdServerTest
, CustomAction
) {
1165 OverlayCDB
CDB(/*Base=*/nullptr);
1167 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest());
1169 Server
.addDocument(testPath("foo.cc"), "void x();");
1170 Decl::Kind XKind
= Decl::TranslationUnit
;
1171 EXPECT_THAT_ERROR(runCustomAction(Server
, testPath("foo.cc"),
1172 [&](InputsAndAST AST
) {
1173 XKind
= findDecl(AST
.AST
, "x").getKind();
1176 EXPECT_EQ(XKind
, Decl::Function
);
1179 // Tests fails when built with asan due to stack overflow. So skip running the
1180 // test as a workaround.
1181 #if !defined(__has_feature) || !__has_feature(address_sanitizer)
1182 TEST(ClangdServerTest
, TestStackOverflow
) {
1184 ErrorCheckingCallbacks DiagConsumer
;
1185 MockCompilationDatabase CDB
;
1186 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest(), &DiagConsumer
);
1188 const char *SourceContents
= R
"cpp(
1189 constexpr int foo() { return foo(); }
1190 static_assert(foo());
1193 auto FooCpp
= testPath("foo.cpp");
1194 FS
.Files
[FooCpp
] = SourceContents
;
1196 Server
.addDocument(FooCpp
, SourceContents
);
1197 ASSERT_TRUE(Server
.blockUntilIdleForTest()) << "Waiting for diagnostics";
1198 // check that we got a constexpr depth error, and not crashed by stack
1200 EXPECT_TRUE(DiagConsumer
.hadErrorInLastDiags());
1204 TEST(ClangdServer
, TidyOverrideTest
) {
1205 struct DiagsCheckingCallback
: public ClangdServer::Callbacks
{
1207 void onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
1208 llvm::ArrayRef
<Diag
> Diagnostics
) override
{
1209 std::lock_guard
<std::mutex
> Lock(Mutex
);
1210 HadDiagsInLastCallback
= !Diagnostics
.empty();
1214 bool HadDiagsInLastCallback
= false;
1218 // These checks don't work well in clangd, even if configured they shouldn't
1220 FS
.Files
[testPath(".clang-tidy")] = R
"(
1221 Checks: -*,bugprone-use-after-move,llvm-header-guard
1223 MockCompilationDatabase CDB
;
1224 std::vector
<TidyProvider
> Stack
;
1225 Stack
.push_back(provideClangTidyFiles(FS
));
1226 Stack
.push_back(disableUnusableChecks());
1227 TidyProvider Provider
= combine(std::move(Stack
));
1228 CDB
.ExtraClangFlags
= {"-xc++"};
1229 auto Opts
= ClangdServer::optsForTest();
1230 Opts
.ClangTidyProvider
= Provider
;
1231 ClangdServer
Server(CDB
, FS
, Opts
, &DiagConsumer
);
1232 const char *SourceContents
= R
"cpp(
1233 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1234 namespace std { Foo&& move(Foo&); }
1237 Foo y = std::move(x);
1240 Server
.addDocument(testPath("foo.h"), SourceContents
);
1241 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1242 EXPECT_FALSE(DiagConsumer
.HadDiagsInLastCallback
);
1245 TEST(ClangdServer
, MemoryUsageTest
) {
1247 MockCompilationDatabase CDB
;
1248 ClangdServer
Server(CDB
, FS
, ClangdServer::optsForTest());
1250 auto FooCpp
= testPath("foo.cpp");
1251 Server
.addDocument(FooCpp
, "");
1252 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1254 llvm::BumpPtrAllocator Alloc
;
1255 MemoryTree
MT(&Alloc
);
1257 ASSERT_TRUE(MT
.children().count("tuscheduler"));
1258 EXPECT_TRUE(MT
.child("tuscheduler").children().count(FooCpp
));
1261 TEST(ClangdServer
, RespectsTweakFormatting
) {
1262 static constexpr const char *TweakID
= "ModuleTweak";
1263 static constexpr const char *NewContents
= "{not;\nformatted;}";
1265 // Contributes a tweak that generates a non-formatted insertion and disables
1267 struct TweakContributingModule final
: public FeatureModule
{
1268 struct ModuleTweak final
: public Tweak
{
1269 const char *id() const override
{ return TweakID
; }
1270 bool prepare(const Selection
&Sel
) override
{ return true; }
1271 Expected
<Effect
> apply(const Selection
&Sel
) override
{
1272 auto &SM
= Sel
.AST
->getSourceManager();
1273 llvm::StringRef FilePath
= SM
.getFilename(Sel
.Cursor
);
1274 tooling::Replacements Reps
;
1276 Reps
.add(tooling::Replacement(FilePath
, 0, 0, NewContents
)));
1277 auto E
= llvm::cantFail(Effect::mainFileEdit(SM
, std::move(Reps
)));
1278 E
.FormatEdits
= false;
1281 std::string
title() const override
{ return id(); }
1282 llvm::StringLiteral
kind() const override
{
1283 return llvm::StringLiteral("");
1287 void contributeTweaks(std::vector
<std::unique_ptr
<Tweak
>> &Out
) override
{
1288 Out
.emplace_back(new ModuleTweak
);
1293 MockCompilationDatabase CDB
;
1294 auto Opts
= ClangdServer::optsForTest();
1295 FeatureModuleSet Set
;
1296 Set
.add(std::make_unique
<TweakContributingModule
>());
1297 Opts
.FeatureModules
= &Set
;
1298 ClangdServer
Server(CDB
, FS
, Opts
);
1300 auto FooCpp
= testPath("foo.cpp");
1301 Server
.addDocument(FooCpp
, "");
1302 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1304 // Ensure that disabled formatting is respected.
1306 Server
.applyTweak(FooCpp
, {}, TweakID
, [&](llvm::Expected
<Tweak::Effect
> E
) {
1307 ASSERT_TRUE(static_cast<bool>(E
));
1308 EXPECT_THAT(llvm::cantFail(E
->ApplyEdits
.lookup(FooCpp
).apply()),
1315 TEST(ClangdServer
, InactiveRegions
) {
1316 struct InactiveRegionsCallback
: ClangdServer::Callbacks
{
1317 std::vector
<std::vector
<Range
>> FoundInactiveRegions
;
1319 void onInactiveRegionsReady(PathRef FIle
,
1320 std::vector
<Range
> InactiveRegions
) override
{
1321 FoundInactiveRegions
.push_back(std::move(InactiveRegions
));
1326 MockCompilationDatabase CDB
;
1327 CDB
.ExtraClangFlags
.push_back("-DCMDMACRO");
1328 auto Opts
= ClangdServer::optsForTest();
1329 Opts
.PublishInactiveRegions
= true;
1330 InactiveRegionsCallback Callback
;
1331 ClangdServer
Server(CDB
, FS
, Opts
, &Callback
);
1332 Annotations
Source(R
"cpp(
1333 #define PREAMBLEMACRO 42
1334 #if PREAMBLEMACRO > 40
1337 $inactive1[[ #define INACTIVE]]
1341 $inactive2[[ int inactiveInt;]]
1345 $inactive3[[ int inactiveInt2;]]
1346 #elif PREAMBLEMACRO > 0
1350 $inactive4[[ int inactiveInt3;]]
1353 #endif // empty inactive range, gets dropped
1355 Server
.addDocument(testPath("foo.cpp"), Source
.code());
1356 ASSERT_TRUE(Server
.blockUntilIdleForTest());
1357 EXPECT_THAT(Callback
.FoundInactiveRegions
,
1358 ElementsAre(ElementsAre(
1359 Source
.range("inactive1"), Source
.range("inactive2"),
1360 Source
.range("inactive3"), Source
.range("inactive4"))));
1364 } // namespace clangd
1365 } // namespace clang