1//===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
9#include "Annotations.h"
10#include "ClangdServer.h"
11#include "CodeComplete.h"
12#include "CompileCommands.h"
13#include "ConfigFragment.h"
14#include "GlobalCompilationDatabase.h"
15#include "Matchers.h"
16#include "SyncAPI.h"
17#include "TestFS.h"
18#include "TestTU.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"
40#include <algorithm>
41#include <chrono>
42#include <iostream>
43#include <optional>
44#include <random>
45#include <string>
46#include <thread>
47#include <vector>
48
49namespace clang {
50namespace clangd {
51
52namespace {
53
54using ::testing::AllOf;
55using ::testing::ElementsAre;
56using ::testing::Field;
57using ::testing::IsEmpty;
58using ::testing::Pair;
59using ::testing::SizeIs;
60using ::testing::UnorderedElementsAre;
61
62MATCHER_P2(DeclAt, File, Range, "") {
63 return arg.PreferredDeclaration ==
64 Location{URIForFile::canonicalize(AbsPath: File, TUPath: testRoot()), Range};
65}
66
67bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
68 for (auto D : Diagnostics) {
69 if (D.Severity == DiagnosticsEngine::Error ||
70 D.Severity == DiagnosticsEngine::Fatal)
71 return true;
72 }
73 return false;
74}
75
76class ErrorCheckingCallbacks : public ClangdServer::Callbacks {
77public:
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;
83 }
84
85 bool hadErrorInLastDiags() {
86 std::lock_guard<std::mutex> Lock(Mutex);
87 return HadErrorInLastDiags;
88 }
89
90private:
91 std::mutex Mutex;
92 bool HadErrorInLastDiags = false;
93};
94
95/// For each file, record whether the last published diagnostics contained at
96/// least one error.
97class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
98public:
99 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
100 llvm::ArrayRef<Diag> Diagnostics) override {
101 bool HadError = diagsContainErrors(Diagnostics);
102
103 std::lock_guard<std::mutex> Lock(Mutex);
104 LastDiagsHadError[File] = HadError;
105 }
106
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(args: std::string(It.first()), args: It.second);
115 return Result;
116 }
117
118 void clear() {
119 std::lock_guard<std::mutex> Lock(Mutex);
120 LastDiagsHadError.clear();
121 }
122
123private:
124 mutable std::mutex Mutex;
125 llvm::StringMap<bool> LastDiagsHadError;
126};
127
128/// Replaces all patterns of the form 0x123abc with spaces
129std::string replacePtrsInDump(std::string const &Dump) {
130 llvm::Regex RE("0x[0-9a-fA-F]+");
131 llvm::SmallVector<llvm::StringRef, 1> Matches;
132 llvm::StringRef Pending = Dump;
133
134 std::string Result;
135 while (RE.match(String: Pending, Matches: &Matches)) {
136 assert(Matches.size() == 1 && "Exactly one match expected");
137 auto MatchPos = Matches[0].data() - Pending.data();
138
139 Result += Pending.take_front(N: MatchPos);
140 Pending = Pending.drop_front(N: MatchPos + Matches[0].size());
141 }
142 Result += Pending;
143
144 return Result;
145}
146
147std::string dumpAST(ClangdServer &Server, PathRef File) {
148 std::string Result;
149 Notification Done;
150 Server.customAction(File, Name: "DumpAST", Action: [&](llvm::Expected<InputsAndAST> AST) {
151 if (AST) {
152 llvm::raw_string_ostream ResultOS(Result);
153 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS, true);
154 } else {
155 llvm::consumeError(Err: AST.takeError());
156 Result = "<no-ast>";
157 }
158 Done.notify();
159 });
160 Done.wait();
161 return Result;
162}
163
164std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) {
165 return replacePtrsInDump(Dump: dumpAST(Server, File));
166}
167
168std::string parseSourceAndDumpAST(
169 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
170 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
171 bool ExpectErrors = false) {
172 MockFS FS;
173 ErrorCheckingCallbacks DiagConsumer;
174 MockCompilationDatabase CDB;
175 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
176 for (const auto &FileWithContents : ExtraFiles)
177 FS.Files[testPath(File: FileWithContents.first)] =
178 std::string(FileWithContents.second);
179
180 auto SourceFilename = testPath(File: SourceFileRelPath);
181 Server.addDocument(File: SourceFilename, Contents: SourceContents);
182 auto Result = dumpASTWithoutMemoryLocs(Server, File: SourceFilename);
183 EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
184 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
185 return Result;
186}
187
188TEST(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
191 // different.
192 auto Empty = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "");
193 auto OneDecl = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "int a;");
194 auto SomeDecls = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "int a; int b; int c;");
195 EXPECT_NE(Empty, OneDecl);
196 EXPECT_NE(Empty, SomeDecls);
197 EXPECT_NE(SomeDecls, OneDecl);
198
199 auto Empty2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "");
200 auto OneDecl2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "int a;");
201 auto SomeDecls2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "int a; int b; int c;");
202 EXPECT_EQ(Empty, Empty2);
203 EXPECT_EQ(OneDecl, OneDecl2);
204 EXPECT_EQ(SomeDecls, SomeDecls2);
205}
206
207TEST(ClangdServerTest, ParseWithHeader) {
208 parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "#include \"foo.h\"", ExtraFiles: {},
209 /*ExpectErrors=*/true);
210 parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents: "#include \"foo.h\"", ExtraFiles: {{"foo.h", ""}},
211 /*ExpectErrors=*/false);
212
213 const auto *SourceContents = R"cpp(
214#include "foo.h"
215int b = a;
216)cpp";
217 parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents, ExtraFiles: {{"foo.h", ""}},
218 /*ExpectErrors=*/true);
219 parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp", SourceContents, ExtraFiles: {{"foo.h", "int a;"}},
220 /*ExpectErrors=*/false);
221}
222
223TEST(ClangdServerTest, Reparse) {
224 MockFS FS;
225 ErrorCheckingCallbacks DiagConsumer;
226 MockCompilationDatabase CDB;
227 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
228
229 const auto *SourceContents = R"cpp(
230#include "foo.h"
231int b = a;
232)cpp";
233
234 auto FooCpp = testPath(File: "foo.cpp");
235
236 FS.Files[testPath(File: "foo.h")] = "int a;";
237 FS.Files[FooCpp] = SourceContents;
238
239 Server.addDocument(File: FooCpp, Contents: SourceContents);
240 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
241 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
242 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
243
244 Server.addDocument(File: FooCpp, Contents: "");
245 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
246 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
247 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
248
249 Server.addDocument(File: FooCpp, Contents: SourceContents);
250 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
251 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
252 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
253
254 EXPECT_EQ(DumpParse1, DumpParse2);
255 EXPECT_NE(DumpParse1, DumpParseEmpty);
256}
257
258TEST(ClangdServerTest, ReparseOnHeaderChange) {
259 MockFS FS;
260 ErrorCheckingCallbacks DiagConsumer;
261 MockCompilationDatabase CDB;
262 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
263
264 const auto *SourceContents = R"cpp(
265#include "foo.h"
266int b = a;
267)cpp";
268
269 auto FooCpp = testPath(File: "foo.cpp");
270 auto FooH = testPath(File: "foo.h");
271
272 FS.Files[FooH] = "int a;";
273 FS.Files[FooCpp] = SourceContents;
274
275 Server.addDocument(File: FooCpp, Contents: SourceContents);
276 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
277 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
278 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
279
280 FS.Files[FooH] = "";
281 Server.addDocument(File: FooCpp, Contents: SourceContents);
282 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
283 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
284 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
285
286 FS.Files[FooH] = "int a;";
287 Server.addDocument(File: FooCpp, Contents: SourceContents);
288 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
289 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, File: FooCpp);
290 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
291
292 EXPECT_EQ(DumpParse1, DumpParse2);
293 EXPECT_NE(DumpParse1, DumpParseDifferent);
294}
295
296TEST(ClangdServerTest, PropagatesContexts) {
297 static Key<int> Secret;
298 struct ContextReadingFS : public ThreadsafeFS {
299 mutable int Got;
300
301 private:
302 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
303 Got = Context::current().getExisting(Key: Secret);
304 return buildTestFS(Files: {});
305 }
306 } FS;
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(Key: Secret);
311 }
312 int Got;
313 } Callbacks;
314 MockCompilationDatabase CDB;
315
316 // Verify that the context is plumbed to the FS provider and diagnostics.
317 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
318 {
319 WithContextValue Entrypoint(Secret, 42);
320 Server.addDocument(File: testPath(File: "foo.cpp"), Contents: "void main(){}");
321 }
322 ASSERT_TRUE(Server.blockUntilIdleForTest());
323 EXPECT_EQ(FS.Got, 42);
324 EXPECT_EQ(Callbacks.Got, 42);
325}
326
327TEST(ClangdServerTest, RespectsConfig) {
328 // Go-to-definition will resolve as marked if FOO is defined.
329 Annotations Example(R"cpp(
330 #ifdef FOO
331 int [[x]];
332 #else
333 int x;
334 #endif
335 int y = ^x;
336 )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 {
342 config::Fragment F;
343 F.If.PathMatch.emplace_back(args: ".*foo.cc");
344 F.CompileFlags.Add.emplace_back(args: "-DFOO=1");
345 return {std::move(F).compile(DC)};
346 }
347 } CfgProvider;
348
349 auto Opts = ClangdServer::optsForTest();
350 Opts.ContextProvider =
351 ClangdServer::createConfiguredContextProvider(Provider: &CfgProvider, nullptr);
352 OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
353 CommandMangler::forTests());
354 MockFS FS;
355 ClangdServer Server(CDB, FS, Opts);
356 // foo.cc sees the expected definition, as FOO is defined.
357 Server.addDocument(File: testPath(File: "foo.cc"), Contents: Example.code());
358 auto Result = runLocateSymbolAt(Server, File: testPath(File: "foo.cc"), Pos: 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(File: testPath(File: "bar.cc"), Contents: Example.code());
364 Result = runLocateSymbolAt(Server, File: testPath(File: "bar.cc"), Pos: Example.point());
365 ASSERT_TRUE(bool(Result)) << Result.takeError();
366 ASSERT_THAT(*Result, SizeIs(1));
367 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
368}
369
370TEST(ClangdServerTest, PropagatesVersion) {
371 MockCompilationDatabase CDB;
372 MockFS FS;
373 struct Callbacks : public ClangdServer::Callbacks {
374 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
375 llvm::ArrayRef<Diag> Diagnostics) override {
376 Got = Version.str();
377 }
378 std::string Got = "";
379 } Callbacks;
380
381 // Verify that the version is plumbed to diagnostics.
382 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
383 runAddDocument(Server, File: testPath(File: "foo.cpp"), Contents: "void main(){}", Version: "42");
384 EXPECT_EQ(Callbacks.Got, "42");
385}
386
387// Only enable this test on Unix
388#ifdef LLVM_ON_UNIX
389TEST(ClangdServerTest, SearchLibDir) {
390 // Checks that searches for GCC installation is done through vfs.
391 MockFS FS;
392 ErrorCheckingCallbacks DiagConsumer;
393 MockCompilationDatabase CDB;
394 CDB.ExtraClangFlags.insert(position: CDB.ExtraClangFlags.end(),
395 l: {"-xc++", "-target", "x86_64-linux-unknown",
396 "-m64", "--gcc-toolchain=/randomusr",
397 "-stdlib=libstdc++"});
398 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
399
400 // Just a random gcc version string
401 SmallString<8> Version("4.9.3");
402
403 // A lib dir for gcc installation
404 SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu");
405 llvm::sys::path::append(path&: LibDir, a: Version);
406
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(path&: MockLibFile, a: LibDir, b: "64", c: "crtbegin.o");
411 FS.Files[MockLibFile] = "";
412
413 SmallString<64> IncludeDir("/randomusr/include/c++");
414 llvm::sys::path::append(path&: IncludeDir, a: Version);
415
416 SmallString<64> StringPath;
417 llvm::sys::path::append(path&: StringPath, a: IncludeDir, b: "string");
418 FS.Files[StringPath] = "class mock_string {};";
419
420 auto FooCpp = testPath(File: "foo.cpp");
421 const auto *SourceContents = R"cpp(
422#include <string>
423mock_string x;
424)cpp";
425 FS.Files[FooCpp] = SourceContents;
426
427 runAddDocument(Server, File: FooCpp, Contents: SourceContents);
428 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
429
430 const auto *SourceContentsWithError = R"cpp(
431#include <string>
432std::string x;
433)cpp";
434 runAddDocument(Server, File: FooCpp, Contents: SourceContentsWithError);
435 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
436}
437#endif // LLVM_ON_UNIX
438
439TEST(ClangdServerTest, ForceReparseCompileCommand) {
440 MockFS FS;
441 ErrorCheckingCallbacks DiagConsumer;
442 MockCompilationDatabase CDB;
443 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
444
445 auto FooCpp = testPath(File: "foo.cpp");
446 const auto *SourceContents1 = R"cpp(
447template <class T>
448struct foo { T x; };
449)cpp";
450 const auto *SourceContents2 = R"cpp(
451template <class T>
452struct bar { T x; };
453)cpp";
454
455 FS.Files[FooCpp] = "";
456
457 // First parse files in C mode and check they produce errors.
458 CDB.ExtraClangFlags = {"-xc"};
459 runAddDocument(Server, File: FooCpp, Contents: SourceContents1);
460 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
461 runAddDocument(Server, File: FooCpp, Contents: SourceContents2);
462 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
463
464 // Now switch to C++ mode.
465 CDB.ExtraClangFlags = {"-xc++"};
466 runAddDocument(Server, File: FooCpp, Contents: SourceContents2);
467 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
468 // Subsequent addDocument calls should finish without errors too.
469 runAddDocument(Server, File: FooCpp, Contents: SourceContents1);
470 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
471 runAddDocument(Server, File: FooCpp, Contents: SourceContents2);
472 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
473}
474
475TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
476 MockFS FS;
477 ErrorCheckingCallbacks DiagConsumer;
478 MockCompilationDatabase CDB;
479 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
480
481 auto FooCpp = testPath(File: "foo.cpp");
482 const auto *SourceContents = R"cpp(
483#ifdef WITH_ERROR
484this
485#endif
486
487int main() { return 0; }
488)cpp";
489 FS.Files[FooCpp] = "";
490
491 // Parse with define, we expect to see the errors.
492 CDB.ExtraClangFlags = {"-DWITH_ERROR"};
493 runAddDocument(Server, File: FooCpp, Contents: SourceContents);
494 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
495
496 // Parse without the define, no errors should be produced.
497 CDB.ExtraClangFlags = {};
498 runAddDocument(Server, File: FooCpp, Contents: SourceContents);
499 ASSERT_TRUE(Server.blockUntilIdleForTest());
500 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
501 // Subsequent addDocument call should finish without errors too.
502 runAddDocument(Server, File: FooCpp, Contents: SourceContents);
503 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
504}
505
506// Test ClangdServer.reparseOpenedFiles.
507TEST(ClangdServerTest, ReparseOpenedFiles) {
508 Annotations FooSource(R"cpp(
509#ifdef MACRO
510static void $one[[bob]]() {}
511#else
512static void $two[[bob]]() {}
513#endif
514
515int main () { bo^b (); return 0; }
516)cpp");
517
518 Annotations BarSource(R"cpp(
519#ifdef MACRO
520this is an error
521#endif
522)cpp");
523
524 Annotations BazSource(R"cpp(
525int hello;
526)cpp");
527
528 MockFS FS;
529 MockCompilationDatabase CDB;
530 MultipleErrorCheckingCallbacks DiagConsumer;
531 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
532
533 auto FooCpp = testPath(File: "foo.cpp");
534 auto BarCpp = testPath(File: "bar.cpp");
535 auto BazCpp = testPath(File: "baz.cpp");
536
537 FS.Files[FooCpp] = "";
538 FS.Files[BarCpp] = "";
539 FS.Files[BazCpp] = "";
540
541 CDB.ExtraClangFlags = {"-DMACRO=1"};
542 Server.addDocument(File: FooCpp, Contents: FooSource.code());
543 Server.addDocument(File: BarCpp, Contents: BarSource.code());
544 Server.addDocument(File: BazCpp, Contents: BazSource.code());
545 ASSERT_TRUE(Server.blockUntilIdleForTest());
546
547 EXPECT_THAT(DiagConsumer.filesWithDiags(),
548 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true),
549 Pair(BazCpp, false)));
550
551 auto Locations = runLocateSymbolAt(Server, File: FooCpp, Pos: FooSource.point());
552 EXPECT_TRUE(bool(Locations));
553 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one"))));
554
555 // Undefine MACRO, close baz.cpp.
556 CDB.ExtraClangFlags.clear();
557 DiagConsumer.clear();
558 Server.removeDocument(File: BazCpp);
559 Server.addDocument(File: FooCpp, Contents: FooSource.code());
560 Server.addDocument(File: BarCpp, Contents: BarSource.code());
561 ASSERT_TRUE(Server.blockUntilIdleForTest());
562
563 EXPECT_THAT(DiagConsumer.filesWithDiags(),
564 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false)));
565
566 Locations = runLocateSymbolAt(Server, File: FooCpp, Pos: FooSource.point());
567 EXPECT_TRUE(bool(Locations));
568 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two"))));
569}
570
571MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "") {
572 return arg.first() == Name &&
573 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
574 UsesMemory &&
575 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
576 std::tie(PreambleBuilds, ASTBuilds);
577}
578
579TEST(ClangdServerTest, FileStats) {
580 MockFS FS;
581 ErrorCheckingCallbacks DiagConsumer;
582 MockCompilationDatabase CDB;
583 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
584
585 Path FooCpp = testPath(File: "foo.cpp");
586 const auto *SourceContents = R"cpp(
587struct Something {
588 int method();
589};
590)cpp";
591 Path BarCpp = testPath(File: "bar.cpp");
592
593 FS.Files[FooCpp] = "";
594 FS.Files[BarCpp] = "";
595
596 EXPECT_THAT(Server.fileStats(), IsEmpty());
597
598 Server.addDocument(File: FooCpp, Contents: SourceContents);
599 Server.addDocument(File: BarCpp, Contents: SourceContents);
600 ASSERT_TRUE(Server.blockUntilIdleForTest());
601
602 EXPECT_THAT(Server.fileStats(),
603 UnorderedElementsAre(Stats(FooCpp, true, 1, 1),
604 Stats(BarCpp, true, 1, 1)));
605
606 Server.removeDocument(File: FooCpp);
607 ASSERT_TRUE(Server.blockUntilIdleForTest());
608 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp, true, 1, 1)));
609
610 Server.removeDocument(File: BarCpp);
611 ASSERT_TRUE(Server.blockUntilIdleForTest());
612 EXPECT_THAT(Server.fileStats(), IsEmpty());
613}
614
615TEST(ClangdServerTest, InvalidCompileCommand) {
616 MockFS FS;
617 ErrorCheckingCallbacks DiagConsumer;
618 MockCompilationDatabase CDB;
619
620 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
621
622 auto FooCpp = testPath(File: "foo.cpp");
623 // clang cannot create CompilerInvocation in this case.
624 CDB.ExtraClangFlags.push_back(x: "-###");
625
626 // Clang can't parse command args in that case, but we shouldn't crash.
627 runAddDocument(Server, File: FooCpp, Contents: "int main() {}");
628
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()));
634 EXPECT_ERROR(
635 runSignatureHelp(Server, FooCpp, Position(), MarkupKind::PlainText));
636 // Identifier-based fallback completion.
637 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
638 clangd::CodeCompleteOptions()))
639 .Completions,
640 ElementsAre(Field(&CodeCompletion::Name, "int"),
641 Field(&CodeCompletion::Name, "main")));
642}
643
644TEST(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;
652
653 const auto *SourceContentsWithoutErrors = R"cpp(
654int a;
655int b;
656int c;
657int d;
658)cpp";
659
660 const auto *SourceContentsWithErrors = R"cpp(
661int a = x;
662int b;
663int c;
664int d;
665)cpp";
666
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;
672
673 std::vector<std::string> FilePaths;
674 MockFS FS;
675 for (unsigned I = 0; I < FilesCount; ++I) {
676 std::string Name = std::string("Foo") + std::to_string(val: I) + ".cpp";
677 FS.Files[Name] = "";
678 FilePaths.push_back(x: testPath(File: Name));
679 }
680
681 struct FileStat {
682 unsigned HitsWithoutErrors = 0;
683 unsigned HitsWithErrors = 0;
684 bool HadErrorsInLastDiags = false;
685 };
686
687 class TestDiagConsumer : public ClangdServer::Callbacks {
688 public:
689 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
690
691 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
692 llvm::ArrayRef<Diag> Diagnostics) override {
693 StringRef FileIndexStr = llvm::sys::path::stem(path: File);
694 ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
695
696 unsigned long FileIndex = std::stoul(str: FileIndexStr.str());
697
698 bool HadError = diagsContainErrors(Diagnostics);
699
700 std::lock_guard<std::mutex> Lock(Mutex);
701 if (HadError)
702 Stats[FileIndex].HitsWithErrors++;
703 else
704 Stats[FileIndex].HitsWithoutErrors++;
705 Stats[FileIndex].HadErrorsInLastDiags = HadError;
706 }
707
708 std::vector<FileStat> takeFileStats() {
709 std::lock_guard<std::mutex> Lock(Mutex);
710 return std::move(Stats);
711 }
712
713 private:
714 std::mutex Mutex;
715 std::vector<FileStat> Stats;
716 };
717
718 struct RequestStats {
719 unsigned RequestsWithoutErrors = 0;
720 unsigned RequestsWithErrors = 0;
721 bool LastContentsHadErrors = false;
722 bool FileIsRemoved = true;
723 };
724
725 std::vector<RequestStats> ReqStats;
726 ReqStats.reserve(n: FilesCount);
727 for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
728 ReqStats.emplace_back();
729
730 TestDiagConsumer DiagConsumer;
731 {
732 MockCompilationDatabase CDB;
733 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
734
735 // Prepare some random distributions for the test.
736 std::random_device RandGen;
737
738 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
739 // Pass a text that contains compiler errors to addDocument in about 20% of
740 // all requests.
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);
745
746 // Some helpers.
747 auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) {
748 auto &Stats = ReqStats[FileIndex];
749
750 if (HadErrors)
751 ++Stats.RequestsWithErrors;
752 else
753 ++Stats.RequestsWithoutErrors;
754 Stats.LastContentsHadErrors = HadErrors;
755 Stats.FileIsRemoved = false;
756 };
757
758 auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) {
759 auto &Stats = ReqStats[FileIndex];
760
761 Stats.FileIsRemoved = true;
762 };
763
764 auto AddDocument = [&](unsigned FileIndex, bool SkipCache) {
765 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
766 Server.addDocument(File: FilePaths[FileIndex],
767 Contents: ShouldHaveErrors ? SourceContentsWithErrors
768 : SourceContentsWithoutErrors);
769 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
770 };
771
772 // Various requests that we would randomly run.
773 auto AddDocumentRequest = [&]() {
774 unsigned FileIndex = FileIndexDist(RandGen);
775 AddDocument(FileIndex, /*SkipCache=*/false);
776 };
777
778 auto ForceReparseRequest = [&]() {
779 unsigned FileIndex = FileIndexDist(RandGen);
780 AddDocument(FileIndex, /*SkipCache=*/true);
781 };
782
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);
788
789 Server.removeDocument(File: FilePaths[FileIndex]);
790 UpdateStatsOnRemoveDocument(FileIndex);
791 };
792
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);
798
799 Position Pos;
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
807 // same file.
808 cantFail(ValOrErr: runCodeComplete(Server, File: FilePaths[FileIndex], Pos,
809 Opts: clangd::CodeCompleteOptions()));
810 };
811
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);
817
818 Position Pos;
819 Pos.line = LineDist(RandGen);
820 Pos.character = ColumnDist(RandGen);
821
822 ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos));
823 };
824
825 std::vector<std::function<void()>> AsyncRequests = {
826 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
827 std::vector<std::function<void()>> BlockingRequests = {
828 CodeCompletionRequest, LocateSymbolRequest};
829
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]();
840 } else {
841 // Issue a blocking request once in a while.
842 auto RequestIndex = BlockingRequestIndexDist(RandGen);
843 BlockingRequests[RequestIndex]();
844 }
845 }
846 ASSERT_TRUE(Server.blockUntilIdleForTest());
847 }
848
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);
855 }
856
857 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
858 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
859 }
860}
861
862TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
863 class NoConcurrentAccessDiagConsumer : public ClangdServer::Callbacks {
864 public:
865 std::atomic<int> Count = {0};
866
867 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
868 : StartSecondReparse(std::move(StartSecondReparse)) {}
869
870 void onDiagnosticsReady(PathRef, llvm::StringRef,
871 llvm::ArrayRef<Diag>) override {
872 ++Count;
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.";
876
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...
879 if (FirstRequest) {
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(rtime: std::chrono::milliseconds(50));
884 }
885 }
886
887 private:
888 std::mutex Mutex;
889 bool FirstRequest = true;
890 std::promise<void> StartSecondReparse;
891 };
892
893 const auto *SourceContentsWithoutErrors = R"cpp(
894int a;
895int b;
896int c;
897int d;
898)cpp";
899
900 const auto *SourceContentsWithErrors = R"cpp(
901int a = x;
902int b;
903int c;
904int d;
905)cpp";
906
907 auto FooCpp = testPath(File: "foo.cpp");
908 MockFS FS;
909 FS.Files[FooCpp] = "";
910
911 std::promise<void> StartSecondPromise;
912 std::future<void> StartSecond = StartSecondPromise.get_future();
913
914 NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise));
915 MockCompilationDatabase CDB;
916 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
917 Server.addDocument(File: FooCpp, Contents: SourceContentsWithErrors);
918 StartSecond.wait();
919 Server.addDocument(File: FooCpp, Contents: SourceContentsWithoutErrors);
920 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
921 ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both?
922}
923
924TEST(ClangdServerTest, FormatCode) {
925 MockFS FS;
926 ErrorCheckingCallbacks DiagConsumer;
927 MockCompilationDatabase CDB;
928 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
929
930 auto Path = testPath(File: "foo.cpp");
931 std::string Code = R"cpp(
932#include "x.h"
933#include "y.h"
934
935void f( ) {}
936)cpp";
937 std::string Expected = R"cpp(
938#include "x.h"
939#include "y.h"
940
941void f() {}
942)cpp";
943 FS.Files[Path] = Code;
944 runAddDocument(Server, File: Path, Contents: Code);
945
946 auto Replaces = runFormatFile(Server, File: Path, /*Rng=*/std::nullopt);
947 EXPECT_TRUE(static_cast<bool>(Replaces));
948 auto Changed = tooling::applyAllReplacements(Code, Replaces: *Replaces);
949 EXPECT_TRUE(static_cast<bool>(Changed));
950 EXPECT_EQ(Expected, *Changed);
951}
952
953TEST(ClangdServerTest, ChangedHeaderFromISystem) {
954 MockFS FS;
955 ErrorCheckingCallbacks DiagConsumer;
956 MockCompilationDatabase CDB;
957 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
958
959 auto SourcePath = testPath(File: "source/foo.cpp");
960 auto HeaderPath = testPath(File: "headers/foo.h");
961 FS.Files[HeaderPath] = "struct X { int bar; };";
962 Annotations Code(R"cpp(
963 #include "foo.h"
964
965 int main() {
966 X().ba^
967 })cpp");
968 CDB.ExtraClangFlags.push_back(x: "-xc++");
969 CDB.ExtraClangFlags.push_back(x: "-isystem" + testPath(File: "headers"));
970
971 runAddDocument(Server, File: SourcePath, Contents: Code.code());
972 auto Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(),
973 Opts: clangd::CodeCompleteOptions()))
974 .Completions;
975 EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar")));
976 // Update the header and rerun addDocument to make sure we get the updated
977 // files.
978 FS.Files[HeaderPath] = "struct X { int bar; int baz; };";
979 runAddDocument(Server, File: SourcePath, Contents: Code.code());
980 Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(),
981 Opts: clangd::CodeCompleteOptions()))
982 .Completions;
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")));
986}
987
988// FIXME(ioeric): make this work for windows again.
989#ifndef _WIN32
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)
992TEST(ClangdTests, PreambleVFSStatCache) {
993 class StatRecordingFS : public ThreadsafeFS {
994 llvm::StringMap<unsigned> &CountStats;
995
996 public:
997 // If relative paths are used, they are resolved with testPath().
998 llvm::StringMap<std::string> Files;
999
1000 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1001 : CountStats(CountStats) {}
1002
1003 private:
1004 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
1005 class StatRecordingVFS : public llvm::vfs::ProxyFileSystem {
1006 public:
1007 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1008 llvm::StringMap<unsigned> &CountStats)
1009 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1010
1011 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1012 openFileForRead(const Twine &Path) override {
1013 ++CountStats[llvm::sys::path::filename(path: Path.str())];
1014 return ProxyFileSystem::openFileForRead(Path);
1015 }
1016 llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {
1017 ++CountStats[llvm::sys::path::filename(path: Path.str())];
1018 return ProxyFileSystem::status(Path);
1019 }
1020
1021 private:
1022 llvm::StringMap<unsigned> &CountStats;
1023 };
1024
1025 return IntrusiveRefCntPtr<StatRecordingVFS>(
1026 new StatRecordingVFS(buildTestFS(Files), CountStats));
1027 }
1028 };
1029
1030 llvm::StringMap<unsigned> CountStats;
1031 StatRecordingFS FS(CountStats);
1032 ErrorCheckingCallbacks DiagConsumer;
1033 MockCompilationDatabase CDB;
1034 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1035
1036 auto SourcePath = testPath(File: "foo.cpp");
1037 auto HeaderPath = testPath(File: "foo.h");
1038 FS.Files[HeaderPath] = "struct TestSym {};";
1039 Annotations Code(R"cpp(
1040 #include "foo.h"
1041
1042 int main() {
1043 TestSy^
1044 })cpp");
1045
1046 runAddDocument(Server, File: SourcePath, Contents: Code.code());
1047
1048 unsigned Before = CountStats["foo.h"];
1049 EXPECT_GT(Before, 0u);
1050 auto Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(),
1051 Opts: clangd::CodeCompleteOptions()))
1052 .Completions;
1053 EXPECT_EQ(CountStats["foo.h"], Before);
1054 EXPECT_THAT(Completions,
1055 ElementsAre(Field(&CodeCompletion::Name, "TestSym")));
1056}
1057#endif
1058
1059TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) {
1060 MockFS FS;
1061 ErrorCheckingCallbacks DiagConsumer;
1062 MockCompilationDatabase CDB;
1063 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1064
1065 auto FooCpp = testPath(File: "foo.cpp");
1066 Annotations Code(R"cpp(
1067 namespace ns { int xyz; }
1068 using namespace ns;
1069 int main() {
1070 xy^
1071 })cpp");
1072 FS.Files[FooCpp] = FooCpp;
1073
1074 auto Opts = clangd::CodeCompleteOptions();
1075 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1076
1077 // This will make compile command broken and preamble absent.
1078 CDB.ExtraClangFlags = {"-###"};
1079 Server.addDocument(File: FooCpp, Contents: Code.code());
1080 ASSERT_TRUE(Server.blockUntilIdleForTest());
1081 auto Res = cantFail(ValOrErr: runCodeComplete(Server, File: FooCpp, Pos: 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, ""))));
1087
1088 // Make the compile command work again.
1089 CDB.ExtraClangFlags = {"-std=c++11"};
1090 Server.addDocument(File: FooCpp, Contents: Code.code());
1091 ASSERT_TRUE(Server.blockUntilIdleForTest());
1092 EXPECT_THAT(
1093 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1094 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1095 Field(&CodeCompletion::Scope, "ns::"))));
1096
1097 // Now force identifier-based completion.
1098 Opts.RunParser = CodeCompleteOptions::NeverParse;
1099 EXPECT_THAT(
1100 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1101 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1102 Field(&CodeCompletion::Scope, ""))));
1103}
1104
1105TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1106 MockFS FS;
1107 ErrorCheckingCallbacks DiagConsumer;
1108 // Returns compile command only when notified.
1109 class DelayedCompilationDatabase : public GlobalCompilationDatabase {
1110 public:
1111 DelayedCompilationDatabase(Notification &CanReturnCommand)
1112 : CanReturnCommand(CanReturnCommand) {}
1113
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(path: File);
1120 std::vector<std::string> CommandLine = {"clangd", "-ffreestanding",
1121 std::string(File)};
1122 return {tooling::CompileCommand(llvm::sys::path::parent_path(path: File),
1123 FileName, std::move(CommandLine), "")};
1124 }
1125
1126 std::vector<std::string> ExtraClangFlags;
1127
1128 private:
1129 Notification &CanReturnCommand;
1130 };
1131
1132 Notification CanReturnCommand;
1133 DelayedCompilationDatabase CDB(CanReturnCommand);
1134 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1135
1136 auto FooCpp = testPath(File: "foo.cpp");
1137 Annotations Code(R"cpp(
1138 namespace ns { int xyz; }
1139 using namespace ns;
1140 int main() {
1141 xy^
1142 })cpp");
1143 FS.Files[FooCpp] = FooCpp;
1144 Server.addDocument(File: FooCpp, Contents: Code.code());
1145
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(rtime: std::chrono::milliseconds(10));
1149 auto Opts = clangd::CodeCompleteOptions();
1150 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1151
1152 auto Res = cantFail(ValOrErr: runCodeComplete(Server, File: FooCpp, Pos: Code.point(), Opts));
1153 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1154
1155 CanReturnCommand.notify();
1156 ASSERT_TRUE(Server.blockUntilIdleForTest());
1157 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(),
1158 clangd::CodeCompleteOptions()))
1159 .Completions,
1160 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1161 Field(&CodeCompletion::Scope, "ns::"))));
1162}
1163
1164TEST(ClangdServerTest, CustomAction) {
1165 OverlayCDB CDB(/*Base=*/nullptr);
1166 MockFS FS;
1167 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1168
1169 Server.addDocument(File: testPath(File: "foo.cc"), Contents: "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();
1174 }),
1175 llvm::Succeeded());
1176 EXPECT_EQ(XKind, Decl::Function);
1177}
1178
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)
1182TEST(ClangdServerTest, TestStackOverflow) {
1183 MockFS FS;
1184 ErrorCheckingCallbacks DiagConsumer;
1185 MockCompilationDatabase CDB;
1186 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1187
1188 const char *SourceContents = R"cpp(
1189 constexpr int foo() { return foo(); }
1190 static_assert(foo());
1191 )cpp";
1192
1193 auto FooCpp = testPath(File: "foo.cpp");
1194 FS.Files[FooCpp] = SourceContents;
1195
1196 Server.addDocument(File: FooCpp, Contents: SourceContents);
1197 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
1198 // check that we got a constexpr depth error, and not crashed by stack
1199 // overflow
1200 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
1201}
1202#endif
1203
1204TEST(ClangdServer, TidyOverrideTest) {
1205 struct DiagsCheckingCallback : public ClangdServer::Callbacks {
1206 public:
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();
1211 }
1212
1213 std::mutex Mutex;
1214 bool HadDiagsInLastCallback = false;
1215 } DiagConsumer;
1216
1217 MockFS FS;
1218 // These checks don't work well in clangd, even if configured they shouldn't
1219 // run.
1220 FS.Files[testPath(File: ".clang-tidy")] = R"(
1221 Checks: -*,bugprone-use-after-move,llvm-header-guard
1222 )";
1223 MockCompilationDatabase CDB;
1224 std::vector<TidyProvider> Stack;
1225 Stack.push_back(x: provideClangTidyFiles(FS));
1226 Stack.push_back(x: disableUnusableChecks());
1227 TidyProvider Provider = combine(Providers: 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&); }
1235 void foo() {
1236 Foo x;
1237 Foo y = std::move(x);
1238 Foo z = x;
1239 })cpp";
1240 Server.addDocument(File: testPath(File: "foo.h"), Contents: SourceContents);
1241 ASSERT_TRUE(Server.blockUntilIdleForTest());
1242 EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback);
1243}
1244
1245TEST(ClangdServer, MemoryUsageTest) {
1246 MockFS FS;
1247 MockCompilationDatabase CDB;
1248 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1249
1250 auto FooCpp = testPath(File: "foo.cpp");
1251 Server.addDocument(File: FooCpp, Contents: "");
1252 ASSERT_TRUE(Server.blockUntilIdleForTest());
1253
1254 llvm::BumpPtrAllocator Alloc;
1255 MemoryTree MT(&Alloc);
1256 Server.profile(MT);
1257 ASSERT_TRUE(MT.children().count("tuscheduler"));
1258 EXPECT_TRUE(MT.child("tuscheduler").children().count(FooCpp));
1259}
1260
1261TEST(ClangdServer, RespectsTweakFormatting) {
1262 static constexpr const char *TweakID = "ModuleTweak";
1263 static constexpr const char *NewContents = "{not;\nformatted;}";
1264
1265 // Contributes a tweak that generates a non-formatted insertion and disables
1266 // formatting.
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(SpellingLoc: Sel.Cursor);
1274 tooling::Replacements Reps;
1275 llvm::cantFail(
1276 Err: Reps.add(R: tooling::Replacement(FilePath, 0, 0, NewContents)));
1277 auto E = llvm::cantFail(ValOrErr: Effect::mainFileEdit(SM, Replacements: std::move(Reps)));
1278 E.FormatEdits = false;
1279 return E;
1280 }
1281 std::string title() const override { return id(); }
1282 llvm::StringLiteral kind() const override {
1283 return llvm::StringLiteral("");
1284 };
1285 };
1286
1287 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
1288 Out.emplace_back(args: new ModuleTweak);
1289 }
1290 };
1291
1292 MockFS FS;
1293 MockCompilationDatabase CDB;
1294 auto Opts = ClangdServer::optsForTest();
1295 FeatureModuleSet Set;
1296 Set.add(M: std::make_unique<TweakContributingModule>());
1297 Opts.FeatureModules = &Set;
1298 ClangdServer Server(CDB, FS, Opts);
1299
1300 auto FooCpp = testPath(File: "foo.cpp");
1301 Server.addDocument(File: FooCpp, Contents: "");
1302 ASSERT_TRUE(Server.blockUntilIdleForTest());
1303
1304 // Ensure that disabled formatting is respected.
1305 Notification N;
1306 Server.applyTweak(File: FooCpp, Sel: {}, ID: TweakID, CB: [&](llvm::Expected<Tweak::Effect> E) {
1307 ASSERT_TRUE(static_cast<bool>(E));
1308 EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()),
1309 NewContents);
1310 N.notify();
1311 });
1312 N.wait();
1313}
1314
1315TEST(ClangdServer, InactiveRegions) {
1316 struct InactiveRegionsCallback : ClangdServer::Callbacks {
1317 std::vector<std::vector<Range>> FoundInactiveRegions;
1318
1319 void onInactiveRegionsReady(PathRef FIle,
1320 std::vector<Range> InactiveRegions) override {
1321 FoundInactiveRegions.push_back(x: std::move(InactiveRegions));
1322 }
1323 };
1324
1325 MockFS FS;
1326 MockCompilationDatabase CDB;
1327 CDB.ExtraClangFlags.push_back(x: "-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
1335 #define ACTIVE
1336#else
1337$inactive1[[ #define INACTIVE]]
1338#endif
1339int endPreamble;
1340#ifndef CMDMACRO
1341$inactive2[[ int inactiveInt;]]
1342#endif
1343#undef CMDMACRO
1344#ifdef CMDMACRO
1345$inactive3[[ int inactiveInt2;]]
1346#elif PREAMBLEMACRO > 0
1347 int activeInt1;
1348 int activeInt2;
1349#else
1350$inactive4[[ int inactiveInt3;]]
1351#endif
1352#ifdef CMDMACRO
1353#endif // empty inactive range, gets dropped
1354 )cpp");
1355 Server.addDocument(File: testPath(File: "foo.cpp"), Contents: 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"))));
1361}
1362
1363} // namespace
1364} // namespace clangd
1365} // namespace clang
1366

source code of clang-tools-extra/clangd/unittests/ClangdTests.cpp