clang-tools 24.0.0git
ClangdTests.cpp
Go to the documentation of this file.
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"
11#include "ClangdServer.h"
12#include "CodeComplete.h"
13#include "CompileCommands.h"
14#include "ConfigFragment.h"
16#include "Matchers.h"
17#include "SyncAPI.h"
18#include "TestFS.h"
19#include "TestTU.h"
20#include "TidyProvider.h"
21#include "refactor/Tweak.h"
22#include "support/MemoryTree.h"
23#include "support/Path.h"
24#include "support/Threading.h"
25#include "clang/Config/config.h"
26#include "clang/Sema/CodeCompleteConsumer.h"
27#include "clang/Tooling/ArgumentsAdjusters.h"
28#include "clang/Tooling/Core/Replacement.h"
29#include "llvm/ADT/ArrayRef.h"
30#include "llvm/ADT/SmallVector.h"
31#include "llvm/ADT/StringMap.h"
32#include "llvm/ADT/StringRef.h"
33#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
34#include "llvm/Support/Allocator.h"
35#include "llvm/Support/Error.h"
36#include "llvm/Support/Path.h"
37#include "llvm/Support/Regex.h"
38#include "llvm/Support/VirtualFileSystem.h"
39#include "llvm/Testing/Support/Error.h"
40#include "gmock/gmock.h"
41#include "gtest/gtest.h"
42#include <algorithm>
43#include <chrono>
44#include <iostream>
45#include <optional>
46#include <random>
47#include <string>
48#include <thread>
49#include <vector>
50
51namespace clang {
52namespace clangd {
53
54namespace {
55
56using ::testing::AllOf;
57using ::testing::ElementsAre;
58using ::testing::Field;
59using ::testing::IsEmpty;
60using ::testing::Pair;
61using ::testing::SizeIs;
62using ::testing::UnorderedElementsAre;
63
64MATCHER_P2(DeclAt, File, Range, "") {
65 return arg.PreferredDeclaration ==
67}
68
69bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
70 for (auto D : Diagnostics) {
71 if (D.Severity == DiagnosticsEngine::Error ||
72 D.Severity == DiagnosticsEngine::Fatal)
73 return true;
74 }
75 return false;
76}
77
78class ErrorCheckingCallbacks : public ClangdServer::Callbacks {
79public:
80 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
81 llvm::ArrayRef<Diag> Diagnostics) override {
82 bool HadError = diagsContainErrors(Diagnostics);
83 std::lock_guard<std::mutex> Lock(Mutex);
84 HadErrorInLastDiags = HadError;
85 }
86
87 bool hadErrorInLastDiags() {
88 std::lock_guard<std::mutex> Lock(Mutex);
89 return HadErrorInLastDiags;
90 }
91
92private:
93 std::mutex Mutex;
94 bool HadErrorInLastDiags = false;
95};
96
97/// For each file, record whether the last published diagnostics contained at
98/// least one error.
99class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
100public:
101 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
102 llvm::ArrayRef<Diag> Diagnostics) override {
103 bool HadError = diagsContainErrors(Diagnostics);
104
105 std::lock_guard<std::mutex> Lock(Mutex);
106 LastDiagsHadError[File] = HadError;
107 }
108
109 /// Exposes all files consumed by onDiagnosticsReady in an unspecified order.
110 /// For each file, a bool value indicates whether the last diagnostics
111 /// contained an error.
112 std::vector<std::pair<Path, bool>> filesWithDiags() const {
113 std::vector<std::pair<Path, bool>> Result;
114 std::lock_guard<std::mutex> Lock(Mutex);
115 for (const auto &It : LastDiagsHadError)
116 Result.emplace_back(std::string(It.first()), It.second);
117 return Result;
118 }
119
120 void clear() {
121 std::lock_guard<std::mutex> Lock(Mutex);
122 LastDiagsHadError.clear();
123 }
124
125private:
126 mutable std::mutex Mutex;
127 llvm::StringMap<bool> LastDiagsHadError;
128};
129
130/// Replaces all patterns of the form 0x123abc with spaces
131std::string replacePtrsInDump(std::string const &Dump) {
132 llvm::Regex RE("0x[0-9a-fA-F]+");
133 llvm::SmallVector<llvm::StringRef, 1> Matches;
134 llvm::StringRef Pending = Dump;
135
136 std::string Result;
137 while (RE.match(Pending, &Matches)) {
138 assert(Matches.size() == 1 && "Exactly one match expected");
139 auto MatchPos = Matches[0].data() - Pending.data();
140
141 Result += Pending.take_front(MatchPos);
142 Pending = Pending.drop_front(MatchPos + Matches[0].size());
143 }
144 Result += Pending;
145
146 return Result;
147}
148
149std::string dumpAST(ClangdServer &Server, PathRef File) {
150 std::string Result;
151 Notification Done;
152 Server.customAction(File, "DumpAST", [&](llvm::Expected<InputsAndAST> AST) {
153 if (AST) {
154 llvm::raw_string_ostream ResultOS(Result);
155 AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS, true);
156 } else {
157 llvm::consumeError(AST.takeError());
158 Result = "<no-ast>";
159 }
160 Done.notify();
161 });
162 Done.wait();
163 return Result;
164}
165
166std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) {
167 return replacePtrsInDump(dumpAST(Server, File));
168}
169
170std::string parseSourceAndDumpAST(
171 PathRef SourceFileRelPath, llvm::StringRef SourceContents,
172 std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {},
173 bool ExpectErrors = false) {
174 MockFS FS;
175 ErrorCheckingCallbacks DiagConsumer;
177 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
178 for (const auto &FileWithContents : ExtraFiles)
179 FS.Files[testPath(FileWithContents.first)] =
180 std::string(FileWithContents.second);
181
182 auto SourceFilename = testPath(SourceFileRelPath);
183 Server.addDocument(SourceFilename, SourceContents);
184 auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
185 EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
186 EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags());
187 return Result;
188}
189
190TEST(ClangdServerTest, Parse) {
191 // FIXME: figure out a stable format for AST dumps, so that we can check the
192 // output of the dump itself is equal to the expected one, not just that it's
193 // different.
194 auto Empty = parseSourceAndDumpAST("foo.cpp", "");
195 auto OneDecl = parseSourceAndDumpAST("foo.cpp", "int a;");
196 auto SomeDecls = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
197 EXPECT_NE(Empty, OneDecl);
198 EXPECT_NE(Empty, SomeDecls);
199 EXPECT_NE(SomeDecls, OneDecl);
200
201 auto Empty2 = parseSourceAndDumpAST("foo.cpp", "");
202 auto OneDecl2 = parseSourceAndDumpAST("foo.cpp", "int a;");
203 auto SomeDecls2 = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;");
204 EXPECT_EQ(Empty, Empty2);
205 EXPECT_EQ(OneDecl, OneDecl2);
206 EXPECT_EQ(SomeDecls, SomeDecls2);
207}
208
209TEST(ClangdServerTest, ParseWithHeader) {
210 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {},
211 /*ExpectErrors=*/true);
212 parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}},
213 /*ExpectErrors=*/false);
214
215 const auto *SourceContents = R"cpp(
216#include "foo.h"
217int b = a;
218)cpp";
219 parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", ""}},
220 /*ExpectErrors=*/true);
221 parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", "int a;"}},
222 /*ExpectErrors=*/false);
223}
224
225TEST(ClangdServerTest, Reparse) {
226 MockFS FS;
227 ErrorCheckingCallbacks DiagConsumer;
229 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
230
231 const auto *SourceContents = R"cpp(
232#include "foo.h"
233int b = a;
234)cpp";
235
236 auto FooCpp = testPath("foo.cpp");
237
238 FS.Files[testPath("foo.h")] = "int a;";
239 FS.Files[FooCpp] = SourceContents;
240
241 Server.addDocument(FooCpp, SourceContents);
242 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
243 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
244 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
245
246 Server.addDocument(FooCpp, "");
247 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
248 auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp);
249 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
250
251 Server.addDocument(FooCpp, SourceContents);
252 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
253 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
254 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
255
256 EXPECT_EQ(DumpParse1, DumpParse2);
257 EXPECT_NE(DumpParse1, DumpParseEmpty);
258}
259
260TEST(ClangdServerTest, ReparseOnHeaderChange) {
261 MockFS FS;
262 ErrorCheckingCallbacks DiagConsumer;
264 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
265
266 const auto *SourceContents = R"cpp(
267#include "foo.h"
268int b = a;
269)cpp";
270
271 auto FooCpp = testPath("foo.cpp");
272 auto FooH = testPath("foo.h");
273
274 FS.Files[FooH] = "int a;";
275 FS.Files[FooCpp] = SourceContents;
276
277 Server.addDocument(FooCpp, SourceContents);
278 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
279 auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
280 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
281
282 FS.Files[FooH] = "";
283 Server.addDocument(FooCpp, SourceContents);
284 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
285 auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp);
286 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
287
288 FS.Files[FooH] = "int a;";
289 Server.addDocument(FooCpp, SourceContents);
290 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
291 auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp);
292 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
293
294 EXPECT_EQ(DumpParse1, DumpParse2);
295 EXPECT_NE(DumpParse1, DumpParseDifferent);
296}
297
298TEST(ClangdServerTest, PropagatesContexts) {
299 static Key<int> Secret;
300 struct ContextReadingFS : public ThreadsafeFS {
301 mutable int Got;
302
303 private:
304 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
305 Got = Context::current().getExisting(Secret);
306 return buildTestFS({});
307 }
308 } FS;
309 struct Callbacks : public ClangdServer::Callbacks {
310 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
311 llvm::ArrayRef<Diag> Diagnostics) override {
312 Got = Context::current().getExisting(Secret);
313 }
314 int Got;
315 } Callbacks;
317
318 // Verify that the context is plumbed to the FS provider and diagnostics.
319 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
320 {
321 WithContextValue Entrypoint(Secret, 42);
322 Server.addDocument(testPath("foo.cpp"), "void main(){}");
323 }
324 ASSERT_TRUE(Server.blockUntilIdleForTest());
325 EXPECT_EQ(FS.Got, 42);
326 EXPECT_EQ(Callbacks.Got, 42);
327}
328
329TEST(ClangdServerTest, RespectsConfig) {
330 // Go-to-definition will resolve as marked if FOO is defined.
331 Annotations Example(R"cpp(
332 #ifdef FOO
333 int [[x]];
334 #else
335 int x;
336 #endif
337 int y = ^x;
338 )cpp");
339 // Provide conditional config that defines FOO for foo.cc.
340 class ConfigProvider : public config::Provider {
341 std::vector<config::CompiledFragment>
342 getFragments(const config::Params &,
343 config::DiagnosticCallback DC) const override {
344 config::Fragment F;
345 F.If.PathMatch.emplace_back(".*foo.cc");
346 F.CompileFlags.Add.emplace_back("-DFOO=1");
347 return {std::move(F).compile(DC)};
348 }
349 } CfgProvider;
350
351 auto Opts = ClangdServer::optsForTest();
352 Opts.ContextProvider =
354 OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
356 MockFS FS;
357 ClangdServer Server(CDB, FS, Opts);
358 // foo.cc sees the expected definition, as FOO is defined.
359 Server.addDocument(testPath("foo.cc"), Example.code());
360 auto Result = runLocateSymbolAt(Server, testPath("foo.cc"), Example.point());
361 ASSERT_TRUE(bool(Result)) << Result.takeError();
362 ASSERT_THAT(*Result, SizeIs(1));
363 EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range());
364 // bar.cc gets a different result, as FOO is not defined.
365 Server.addDocument(testPath("bar.cc"), Example.code());
366 Result = runLocateSymbolAt(Server, testPath("bar.cc"), Example.point());
367 ASSERT_TRUE(bool(Result)) << Result.takeError();
368 ASSERT_THAT(*Result, SizeIs(1));
369 EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range());
370}
371
372TEST(ClangdServerTest, PropagatesVersion) {
374 MockFS FS;
375 struct Callbacks : public ClangdServer::Callbacks {
376 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
377 llvm::ArrayRef<Diag> Diagnostics) override {
378 Got = Version.str();
379 }
380 std::string Got = "";
381 } Callbacks;
382
383 // Verify that the version is plumbed to diagnostics.
384 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks);
385 runAddDocument(Server, testPath("foo.cpp"), "void main(){}", "42");
386 EXPECT_EQ(Callbacks.Got, "42");
387}
388
389// Only enable this test on Unix
390#ifdef LLVM_ON_UNIX
391TEST(ClangdServerTest, SearchLibDir) {
392 // Checks that searches for GCC installation is done through vfs.
393 MockFS FS;
394 ErrorCheckingCallbacks DiagConsumer;
396 CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(),
397 {"-xc++", "--target=x86_64-unknown-linux-gnu",
398 "-m64", "--gcc-toolchain=/randomusr",
399 "-stdlib=libstdc++"});
400 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
401
402 // Just a random gcc version string
403 SmallString<8> Version("4.9.3");
404
405 // A lib dir for gcc installation
406 SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu");
407 llvm::sys::path::append(LibDir, Version);
408
409 // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc
410 // installation there.
411 SmallString<64> MockLibFile;
412 llvm::sys::path::append(MockLibFile, LibDir, "64", "crtbegin.o");
413 FS.Files[MockLibFile] = "";
414
415 SmallString<64> IncludeDir("/randomusr/include/c++");
416 llvm::sys::path::append(IncludeDir, Version);
417
418 SmallString<64> StringPath;
419 llvm::sys::path::append(StringPath, IncludeDir, "string");
420 FS.Files[StringPath] = "class mock_string {};";
421
422 auto FooCpp = testPath("foo.cpp");
423 const auto *SourceContents = R"cpp(
424#include <string>
425mock_string x;
426)cpp";
427 FS.Files[FooCpp] = SourceContents;
428
429 runAddDocument(Server, FooCpp, SourceContents);
430 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
431
432 const auto *SourceContentsWithError = R"cpp(
433#include <string>
434std::string x;
435)cpp";
436 runAddDocument(Server, FooCpp, SourceContentsWithError);
437 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
438}
439#endif // LLVM_ON_UNIX
440
441TEST(ClangdServerTest, ForceReparseCompileCommand) {
442 MockFS FS;
443 ErrorCheckingCallbacks DiagConsumer;
445 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
446
447 auto FooCpp = testPath("foo.cpp");
448 const auto *SourceContents1 = R"cpp(
449template <class T>
450struct foo { T x; };
451)cpp";
452 const auto *SourceContents2 = R"cpp(
453template <class T>
454struct bar { T x; };
455)cpp";
456
457 FS.Files[FooCpp] = "";
458
459 // First parse files in C mode and check they produce errors.
460 CDB.ExtraClangFlags = {"-xc"};
461 runAddDocument(Server, FooCpp, SourceContents1);
462 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
463 runAddDocument(Server, FooCpp, SourceContents2);
464 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
465
466 // Now switch to C++ mode.
467 CDB.ExtraClangFlags = {"-xc++"};
468 runAddDocument(Server, FooCpp, SourceContents2);
469 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
470 // Subsequent addDocument calls should finish without errors too.
471 runAddDocument(Server, FooCpp, SourceContents1);
472 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
473 runAddDocument(Server, FooCpp, SourceContents2);
474 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
475}
476
477TEST(ClangdServerTest, ForceReparseCompileCommandDefines) {
478 MockFS FS;
479 ErrorCheckingCallbacks DiagConsumer;
481 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
482
483 auto FooCpp = testPath("foo.cpp");
484 const auto *SourceContents = R"cpp(
485#ifdef WITH_ERROR
486this
487#endif
488
489int main() { return 0; }
490)cpp";
491 FS.Files[FooCpp] = "";
492
493 // Parse with define, we expect to see the errors.
494 CDB.ExtraClangFlags = {"-DWITH_ERROR"};
495 runAddDocument(Server, FooCpp, SourceContents);
496 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
497
498 // Parse without the define, no errors should be produced.
499 CDB.ExtraClangFlags = {};
500 runAddDocument(Server, FooCpp, SourceContents);
501 ASSERT_TRUE(Server.blockUntilIdleForTest());
502 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
503 // Subsequent addDocument call should finish without errors too.
504 runAddDocument(Server, FooCpp, SourceContents);
505 EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
506}
507
508// Test ClangdServer.reparseOpenedFiles.
509TEST(ClangdServerTest, ReparseOpenedFiles) {
510 Annotations FooSource(R"cpp(
511#ifdef MACRO
512static void $one[[bob]]() {}
513#else
514static void $two[[bob]]() {}
515#endif
516
517int main () { bo^b (); return 0; }
518)cpp");
519
520 Annotations BarSource(R"cpp(
521#ifdef MACRO
522this is an error
523#endif
524)cpp");
525
526 Annotations BazSource(R"cpp(
527int hello;
528)cpp");
529
530 MockFS FS;
532 MultipleErrorCheckingCallbacks DiagConsumer;
533 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
534
535 auto FooCpp = testPath("foo.cpp");
536 auto BarCpp = testPath("bar.cpp");
537 auto BazCpp = testPath("baz.cpp");
538
539 FS.Files[FooCpp] = "";
540 FS.Files[BarCpp] = "";
541 FS.Files[BazCpp] = "";
542
543 CDB.ExtraClangFlags = {"-DMACRO=1"};
544 Server.addDocument(FooCpp, FooSource.code());
545 Server.addDocument(BarCpp, BarSource.code());
546 Server.addDocument(BazCpp, BazSource.code());
547 ASSERT_TRUE(Server.blockUntilIdleForTest());
548
549 EXPECT_THAT(DiagConsumer.filesWithDiags(),
550 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true),
551 Pair(BazCpp, false)));
552
553 auto Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point());
554 EXPECT_TRUE(bool(Locations));
555 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one"))));
556
557 // Undefine MACRO, close baz.cpp.
558 CDB.ExtraClangFlags.clear();
559 DiagConsumer.clear();
560 Server.removeDocument(BazCpp);
561 Server.addDocument(FooCpp, FooSource.code());
562 Server.addDocument(BarCpp, BarSource.code());
563 ASSERT_TRUE(Server.blockUntilIdleForTest());
564
565 EXPECT_THAT(DiagConsumer.filesWithDiags(),
566 UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false)));
567
568 Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point());
569 EXPECT_TRUE(bool(Locations));
570 EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two"))));
571}
572
573MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "") {
574 return arg.first() == Name &&
575 (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
576 UsesMemory &&
577 std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
578 std::tie(PreambleBuilds, ASTBuilds);
579}
580
581TEST(ClangdServerTest, FileStats) {
582 MockFS FS;
583 ErrorCheckingCallbacks DiagConsumer;
585 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
586
587 Path FooCpp = testPath("foo.cpp");
588 const auto *SourceContents = R"cpp(
589struct Something {
590 int method();
591};
592)cpp";
593 Path BarCpp = testPath("bar.cpp");
594
595 FS.Files[FooCpp] = "";
596 FS.Files[BarCpp] = "";
597
598 EXPECT_THAT(Server.fileStats(), IsEmpty());
599
600 Server.addDocument(FooCpp, SourceContents);
601 Server.addDocument(BarCpp, SourceContents);
602 ASSERT_TRUE(Server.blockUntilIdleForTest());
603
604 EXPECT_THAT(Server.fileStats(),
605 UnorderedElementsAre(Stats(FooCpp, true, 1, 1),
606 Stats(BarCpp, true, 1, 1)));
607
608 Server.removeDocument(FooCpp);
609 ASSERT_TRUE(Server.blockUntilIdleForTest());
610 EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp, true, 1, 1)));
611
612 Server.removeDocument(BarCpp);
613 ASSERT_TRUE(Server.blockUntilIdleForTest());
614 EXPECT_THAT(Server.fileStats(), IsEmpty());
615}
616
617TEST(ClangdServerTest, InvalidCompileCommand) {
618 MockFS FS;
619 ErrorCheckingCallbacks DiagConsumer;
621
622 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
623
624 auto FooCpp = testPath("foo.cpp");
625 // clang cannot create CompilerInvocation in this case.
626 CDB.ExtraClangFlags.push_back("-###");
627
628 // Clang can't parse command args in that case, but we shouldn't crash.
629 runAddDocument(Server, FooCpp, "int main() {}");
630
631 EXPECT_EQ(dumpAST(Server, FooCpp), "<no-ast>");
632 EXPECT_ERROR(runLocateSymbolAt(Server, FooCpp, Position()));
634 EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name",
635 clangd::RenameOptions()));
638 // Identifier-based fallback completion.
639 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
640 clangd::CodeCompleteOptions()))
641 .Completions,
642 ElementsAre(Field(&CodeCompletion::Name, "int"),
643 Field(&CodeCompletion::Name, "main")));
644}
645
646TEST(ClangdThreadingTest, StressTest) {
647 // Without 'static' clang gives an error for a usage inside TestDiagConsumer.
648 static const unsigned FilesCount = 5;
649 const unsigned RequestsCount = 500;
650 // Blocking requests wait for the parsing to complete, they slow down the test
651 // dramatically, so they are issued rarely. Each
652 // BlockingRequestInterval-request will be a blocking one.
653 const unsigned BlockingRequestInterval = 40;
654
655 const auto *SourceContentsWithoutErrors = R"cpp(
656int a;
657int b;
658int c;
659int d;
660)cpp";
661
662 const auto *SourceContentsWithErrors = R"cpp(
663int a = x;
664int b;
665int c;
666int d;
667)cpp";
668
669 // Giving invalid line and column number should not crash ClangdServer, but
670 // just to make sure we're sometimes hitting the bounds inside the file we
671 // limit the intervals of line and column number that are generated.
672 unsigned MaxLineForFileRequests = 7;
673 unsigned MaxColumnForFileRequests = 10;
674
675 std::vector<std::string> FilePaths;
676 MockFS FS;
677 for (unsigned I = 0; I < FilesCount; ++I) {
678 std::string Name = std::string("Foo") + std::to_string(I) + ".cpp";
679 FS.Files[Name] = "";
680 FilePaths.push_back(testPath(Name));
681 }
682
683 struct FileStat {
684 unsigned HitsWithoutErrors = 0;
685 unsigned HitsWithErrors = 0;
686 bool HadErrorsInLastDiags = false;
687 };
688
689 class TestDiagConsumer : public ClangdServer::Callbacks {
690 public:
691 TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
692
693 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
694 llvm::ArrayRef<Diag> Diagnostics) override {
695 StringRef FileIndexStr = llvm::sys::path::stem(File);
696 ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
697
698 unsigned long FileIndex = std::stoul(FileIndexStr.str());
699
700 bool HadError = diagsContainErrors(Diagnostics);
701
702 std::lock_guard<std::mutex> Lock(Mutex);
703 if (HadError)
704 Stats[FileIndex].HitsWithErrors++;
705 else
706 Stats[FileIndex].HitsWithoutErrors++;
707 Stats[FileIndex].HadErrorsInLastDiags = HadError;
708 }
709
710 std::vector<FileStat> takeFileStats() {
711 std::lock_guard<std::mutex> Lock(Mutex);
712 return std::move(Stats);
713 }
714
715 private:
716 std::mutex Mutex;
717 std::vector<FileStat> Stats;
718 };
719
720 struct RequestStats {
721 unsigned RequestsWithoutErrors = 0;
722 unsigned RequestsWithErrors = 0;
723 bool LastContentsHadErrors = false;
724 bool FileIsRemoved = true;
725 };
726
727 std::vector<RequestStats> ReqStats;
728 ReqStats.reserve(FilesCount);
729 for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex)
730 ReqStats.emplace_back();
731
732 TestDiagConsumer DiagConsumer;
733 {
735 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
736
737 // Prepare some random distributions for the test.
738 std::random_device RandGen;
739
740 std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1);
741 // Pass a text that contains compiler errors to addDocument in about 20% of
742 // all requests.
743 std::bernoulli_distribution ShouldHaveErrorsDist(0.2);
744 // Line and Column numbers for requests that need them.
745 std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests);
746 std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests);
747
748 // Some helpers.
749 auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) {
750 auto &Stats = ReqStats[FileIndex];
751
752 if (HadErrors)
753 ++Stats.RequestsWithErrors;
754 else
755 ++Stats.RequestsWithoutErrors;
756 Stats.LastContentsHadErrors = HadErrors;
757 Stats.FileIsRemoved = false;
758 };
759
760 auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) {
761 auto &Stats = ReqStats[FileIndex];
762
763 Stats.FileIsRemoved = true;
764 };
765
766 auto AddDocument = [&](unsigned FileIndex, bool SkipCache) {
767 bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
768 Server.addDocument(FilePaths[FileIndex],
769 ShouldHaveErrors ? SourceContentsWithErrors
770 : SourceContentsWithoutErrors);
771 UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
772 };
773
774 // Various requests that we would randomly run.
775 auto AddDocumentRequest = [&]() {
776 unsigned FileIndex = FileIndexDist(RandGen);
777 AddDocument(FileIndex, /*SkipCache=*/false);
778 };
779
780 auto ForceReparseRequest = [&]() {
781 unsigned FileIndex = FileIndexDist(RandGen);
782 AddDocument(FileIndex, /*SkipCache=*/true);
783 };
784
785 auto RemoveDocumentRequest = [&]() {
786 unsigned FileIndex = FileIndexDist(RandGen);
787 // Make sure we don't violate the ClangdServer's contract.
788 if (ReqStats[FileIndex].FileIsRemoved)
789 AddDocument(FileIndex, /*SkipCache=*/false);
790
791 Server.removeDocument(FilePaths[FileIndex]);
792 UpdateStatsOnRemoveDocument(FileIndex);
793 };
794
795 auto CodeCompletionRequest = [&]() {
796 unsigned FileIndex = FileIndexDist(RandGen);
797 // Make sure we don't violate the ClangdServer's contract.
798 if (ReqStats[FileIndex].FileIsRemoved)
799 AddDocument(FileIndex, /*SkipCache=*/false);
800
801 Position Pos;
802 Pos.line = LineDist(RandGen);
803 Pos.character = ColumnDist(RandGen);
804 // FIXME(ibiryukov): Also test async completion requests.
805 // Simply putting CodeCompletion into async requests now would make
806 // tests slow, since there's no way to cancel previous completion
807 // requests as opposed to AddDocument/RemoveDocument, which are implicitly
808 // cancelled by any subsequent AddDocument/RemoveDocument request to the
809 // same file.
810 cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos,
811 clangd::CodeCompleteOptions()));
812 };
813
814 auto LocateSymbolRequest = [&]() {
815 unsigned FileIndex = FileIndexDist(RandGen);
816 // Make sure we don't violate the ClangdServer's contract.
817 if (ReqStats[FileIndex].FileIsRemoved)
818 AddDocument(FileIndex, /*SkipCache=*/false);
819
820 Position Pos;
821 Pos.line = LineDist(RandGen);
822 Pos.character = ColumnDist(RandGen);
823
824 ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos));
825 };
826
827 std::vector<std::function<void()>> AsyncRequests = {
828 AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest};
829 std::vector<std::function<void()>> BlockingRequests = {
830 CodeCompletionRequest, LocateSymbolRequest};
831
832 // Bash requests to ClangdServer in a loop.
833 std::uniform_int_distribution<int> AsyncRequestIndexDist(
834 0, AsyncRequests.size() - 1);
835 std::uniform_int_distribution<int> BlockingRequestIndexDist(
836 0, BlockingRequests.size() - 1);
837 for (unsigned I = 1; I <= RequestsCount; ++I) {
838 if (I % BlockingRequestInterval != 0) {
839 // Issue an async request most of the time. It should be fast.
840 unsigned RequestIndex = AsyncRequestIndexDist(RandGen);
841 AsyncRequests[RequestIndex]();
842 } else {
843 // Issue a blocking request once in a while.
844 auto RequestIndex = BlockingRequestIndexDist(RandGen);
845 BlockingRequests[RequestIndex]();
846 }
847 }
848 ASSERT_TRUE(Server.blockUntilIdleForTest());
849 }
850
851 // Check some invariants about the state of the program.
852 std::vector<FileStat> Stats = DiagConsumer.takeFileStats();
853 for (unsigned I = 0; I < FilesCount; ++I) {
854 if (!ReqStats[I].FileIsRemoved) {
855 ASSERT_EQ(Stats[I].HadErrorsInLastDiags,
856 ReqStats[I].LastContentsHadErrors);
857 }
858
859 ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors);
860 ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors);
861 }
862}
863
864TEST(ClangdThreadingTest, NoConcurrentDiagnostics) {
865 class NoConcurrentAccessDiagConsumer : public ClangdServer::Callbacks {
866 public:
867 std::atomic<int> Count = {0};
868
869 NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
870 : StartSecondReparse(std::move(StartSecondReparse)) {}
871
872 void onDiagnosticsReady(PathRef, llvm::StringRef,
873 llvm::ArrayRef<Diag>) override {
874 ++Count;
875 std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
876 ASSERT_TRUE(Lock.owns_lock())
877 << "Detected concurrent onDiagnosticsReady calls for the same file.";
878
879 // If we started the second parse immediately, it might cancel the first.
880 // So we don't allow it to start until the first has delivered diags...
881 if (FirstRequest) {
882 FirstRequest = false;
883 StartSecondReparse.set_value();
884 // ... but then we wait long enough that the callbacks would overlap.
885 std::this_thread::sleep_for(std::chrono::milliseconds(50));
886 }
887 }
888
889 private:
890 std::mutex Mutex;
891 bool FirstRequest = true;
892 std::promise<void> StartSecondReparse;
893 };
894
895 const auto *SourceContentsWithoutErrors = R"cpp(
896int a;
897int b;
898int c;
899int d;
900)cpp";
901
902 const auto *SourceContentsWithErrors = R"cpp(
903int a = x;
904int b;
905int c;
906int d;
907)cpp";
908
909 auto FooCpp = testPath("foo.cpp");
910 MockFS FS;
911 FS.Files[FooCpp] = "";
912
913 std::promise<void> StartSecondPromise;
914 std::future<void> StartSecond = StartSecondPromise.get_future();
915
916 NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise));
918 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
919 Server.addDocument(FooCpp, SourceContentsWithErrors);
920 StartSecond.wait();
921 Server.addDocument(FooCpp, SourceContentsWithoutErrors);
922 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
923 ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both?
924}
925
926TEST(ClangdServerTest, FormatCode) {
927 MockFS FS;
928 ErrorCheckingCallbacks DiagConsumer;
930 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
931
932 auto Path = testPath("foo.cpp");
933 std::string Code = R"cpp(
934#include "x.h"
935#include "y.h"
936
937void f( ) {}
938)cpp";
939 std::string Expected = R"cpp(
940#include "x.h"
941#include "y.h"
942
943void f() {}
944)cpp";
945 FS.Files[Path] = Code;
946 runAddDocument(Server, Path, Code);
947
948 auto Replaces = runFormatFile(Server, Path, /*Rngs=*/{});
949 EXPECT_TRUE(static_cast<bool>(Replaces));
950 auto Changed = tooling::applyAllReplacements(Code, *Replaces);
951 EXPECT_TRUE(static_cast<bool>(Changed));
952 EXPECT_EQ(Expected, *Changed);
953}
954
955TEST(ClangdServerTest, ChangedHeaderFromISystem) {
956 MockFS FS;
957 ErrorCheckingCallbacks DiagConsumer;
959 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
960
961 auto SourcePath = testPath("source/foo.cpp");
962 auto HeaderPath = testPath("headers/foo.h");
963 FS.Files[HeaderPath] = "struct X { int bar; };";
964 Annotations Code(R"cpp(
965 #include "foo.h"
966
967 int main() {
968 X().ba^
969 })cpp");
970 CDB.ExtraClangFlags.push_back("-xc++");
971 CDB.ExtraClangFlags.push_back("-isystem" + testPath("headers"));
972
973 runAddDocument(Server, SourcePath, Code.code());
974 auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
975 clangd::CodeCompleteOptions()))
976 .Completions;
977 EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar")));
978 // Update the header and rerun addDocument to make sure we get the updated
979 // files.
980 FS.Files[HeaderPath] = "struct X { int bar; int baz; };";
981 runAddDocument(Server, SourcePath, Code.code());
982 Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
983 clangd::CodeCompleteOptions()))
984 .Completions;
985 // We want to make sure we see the updated version.
986 EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"),
987 Field(&CodeCompletion::Name, "baz")));
988}
989
990// FIXME(ioeric): make this work for windows again.
991#ifndef _WIN32
992// Check that running code completion doesn't stat() a bunch of files from the
993// preamble again. (They should be using the preamble's stat-cache)
994TEST(ClangdTests, PreambleVFSStatCache) {
995 class StatRecordingFS : public ThreadsafeFS {
996 llvm::StringMap<unsigned> &CountStats;
997
998 public:
999 // If relative paths are used, they are resolved with testPath().
1000 llvm::StringMap<std::string> Files;
1001
1002 StatRecordingFS(llvm::StringMap<unsigned> &CountStats)
1003 : CountStats(CountStats) {}
1004
1005 private:
1006 IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
1007 class StatRecordingVFS : public llvm::vfs::ProxyFileSystem {
1008 public:
1009 StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
1010 llvm::StringMap<unsigned> &CountStats)
1011 : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {}
1012
1013 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
1014 openFileForRead(const Twine &Path) override {
1015 ++CountStats[llvm::sys::path::filename(Path.str())];
1016 return ProxyFileSystem::openFileForRead(Path);
1017 }
1018 llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {
1019 ++CountStats[llvm::sys::path::filename(Path.str())];
1020 return ProxyFileSystem::status(Path);
1021 }
1022
1023 private:
1024 llvm::StringMap<unsigned> &CountStats;
1025 };
1026
1027 return IntrusiveRefCntPtr<StatRecordingVFS>(
1028 new StatRecordingVFS(buildTestFS(Files), CountStats));
1029 }
1030 };
1031
1032 llvm::StringMap<unsigned> CountStats;
1033 StatRecordingFS FS(CountStats);
1034 ErrorCheckingCallbacks DiagConsumer;
1036 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1037
1038 auto SourcePath = testPath("foo.cpp");
1039 auto HeaderPath = testPath("foo.h");
1040 FS.Files[HeaderPath] = "struct TestSym {};";
1041 Annotations Code(R"cpp(
1042 #include "foo.h"
1043
1044 int main() {
1045 TestSy^
1046 })cpp");
1047
1048 runAddDocument(Server, SourcePath, Code.code());
1049
1050 unsigned Before = CountStats["foo.h"];
1051 EXPECT_GT(Before, 0u);
1052 auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
1053 clangd::CodeCompleteOptions()))
1054 .Completions;
1055 EXPECT_EQ(CountStats["foo.h"], Before);
1056 EXPECT_THAT(Completions,
1057 ElementsAre(Field(&CodeCompletion::Name, "TestSym")));
1058}
1059#endif
1060
1061TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) {
1062 MockFS FS;
1063 ErrorCheckingCallbacks DiagConsumer;
1065 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1066
1067 auto FooCpp = testPath("foo.cpp");
1068 Annotations Code(R"cpp(
1069 namespace ns { int xyz; }
1070 using namespace ns;
1071 int main() {
1072 xy^
1073 })cpp");
1074 FS.Files[FooCpp] = FooCpp;
1075
1076 auto Opts = clangd::CodeCompleteOptions();
1077 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1078
1079 // This will make compile command broken and preamble absent.
1080 CDB.ExtraClangFlags = {"-###"};
1081 Server.addDocument(FooCpp, Code.code());
1082 ASSERT_TRUE(Server.blockUntilIdleForTest());
1083 auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts));
1084 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1085 // Identifier-based fallback completion doesn't know about "symbol" scope.
1086 EXPECT_THAT(Res.Completions,
1087 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1088 Field(&CodeCompletion::Scope, ""))));
1089
1090 // Make the compile command work again.
1091 CDB.ExtraClangFlags = {"-std=c++11"};
1092 Server.addDocument(FooCpp, Code.code());
1093 ASSERT_TRUE(Server.blockUntilIdleForTest());
1094 EXPECT_THAT(
1095 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1096 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1097 Field(&CodeCompletion::Scope, "ns::"))));
1098
1099 // Now force identifier-based completion.
1100 Opts.RunParser = CodeCompleteOptions::NeverParse;
1101 EXPECT_THAT(
1102 cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions,
1103 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1104 Field(&CodeCompletion::Scope, ""))));
1105}
1106
1107TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
1108 MockFS FS;
1109 ErrorCheckingCallbacks DiagConsumer;
1110 // Returns compile command only when notified.
1111 class DelayedCompilationDatabase : public GlobalCompilationDatabase {
1112 public:
1113 DelayedCompilationDatabase(Notification &CanReturnCommand)
1114 : CanReturnCommand(CanReturnCommand) {}
1115
1116 std::optional<tooling::CompileCommand>
1117 getCompileCommand(PathRef File) const override {
1118 // FIXME: make this timeout and fail instead of waiting forever in case
1119 // something goes wrong.
1120 CanReturnCommand.wait();
1121 auto FileName = llvm::sys::path::filename(File);
1122 std::vector<std::string> CommandLine = {"clangd", "-ffreestanding",
1123 std::string(File)};
1124 return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
1125 FileName, std::move(CommandLine), "")};
1126 }
1127
1128 std::vector<std::string> ExtraClangFlags;
1129
1130 private:
1131 Notification &CanReturnCommand;
1132 };
1133
1134 Notification CanReturnCommand;
1135 DelayedCompilationDatabase CDB(CanReturnCommand);
1136 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1137
1138 auto FooCpp = testPath("foo.cpp");
1139 Annotations Code(R"cpp(
1140 namespace ns { int xyz; }
1141 using namespace ns;
1142 int main() {
1143 xy^
1144 })cpp");
1145 FS.Files[FooCpp] = FooCpp;
1146 Server.addDocument(FooCpp, Code.code());
1147
1148 // Sleep for some time to make sure code completion is not run because update
1149 // hasn't been scheduled.
1150 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1151 auto Opts = clangd::CodeCompleteOptions();
1152 Opts.RunParser = CodeCompleteOptions::ParseIfReady;
1153
1154 auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts));
1155 EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery);
1156
1157 CanReturnCommand.notify();
1158 ASSERT_TRUE(Server.blockUntilIdleForTest());
1159 EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(),
1160 clangd::CodeCompleteOptions()))
1161 .Completions,
1162 ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"),
1163 Field(&CodeCompletion::Scope, "ns::"))));
1164}
1165
1166TEST(ClangdServerTest, CustomAction) {
1167 OverlayCDB CDB(/*Base=*/nullptr);
1168 MockFS FS;
1169 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1170
1171 Server.addDocument(testPath("foo.cc"), "void x();");
1172 Decl::Kind XKind = Decl::TranslationUnit;
1173 EXPECT_THAT_ERROR(runCustomAction(Server, testPath("foo.cc"),
1174 [&](InputsAndAST AST) {
1175 XKind = findDecl(AST.AST, "x").getKind();
1176 }),
1177 llvm::Succeeded());
1178 EXPECT_EQ(XKind, Decl::Function);
1179}
1180
1181// Tests fails when built with asan due to stack overflow. So skip running the
1182// test as a workaround.
1183#if !defined(__has_feature) || !__has_feature(address_sanitizer)
1184TEST(ClangdServerTest, TestStackOverflow) {
1185 MockFS FS;
1186 ErrorCheckingCallbacks DiagConsumer;
1188 ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
1189
1190 const char *SourceContents = R"cpp(
1191 constexpr int foo() { return foo(); }
1192 static_assert(foo());
1193 )cpp";
1194
1195 auto FooCpp = testPath("foo.cpp");
1196 FS.Files[FooCpp] = SourceContents;
1197
1198 Server.addDocument(FooCpp, SourceContents);
1199 ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
1200 // check that we got a constexpr depth error, and not crashed by stack
1201 // overflow
1202 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
1203}
1204#endif
1205
1206TEST(ClangdServer, TidyOverrideTest) {
1207 struct DiagsCheckingCallback : public ClangdServer::Callbacks {
1208 public:
1209 void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
1210 llvm::ArrayRef<Diag> Diagnostics) override {
1211 std::lock_guard<std::mutex> Lock(Mutex);
1212 HadDiagsInLastCallback = !Diagnostics.empty();
1213 }
1214
1215 std::mutex Mutex;
1216 bool HadDiagsInLastCallback = false;
1217 } DiagConsumer;
1218
1219 MockFS FS;
1220 // These checks don't work well in clangd, even if configured they shouldn't
1221 // run.
1222 FS.Files[testPath(".clang-tidy")] = R"(
1223 Checks: -*,bugprone-use-after-move,llvm-header-guard
1224 )";
1226 std::vector<TidyProvider> Stack;
1227 Stack.push_back(provideClangTidyFiles(FS));
1228 Stack.push_back(disableUnusableChecks());
1229 TidyProvider Provider = combine(std::move(Stack));
1230 CDB.ExtraClangFlags = {"-xc++"};
1231 auto Opts = ClangdServer::optsForTest();
1232 FeatureModuleSet Modules;
1233 Modules.add(std::make_unique<ClangTidyFeatureModule>(std::move(Provider)));
1234 Opts.FeatureModules = &Modules;
1235 ClangdServer Server(CDB, FS, Opts, &DiagConsumer);
1236 const char *SourceContents = R"cpp(
1237 struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };
1238 namespace std { Foo&& move(Foo&); }
1239 void foo() {
1240 Foo x;
1241 Foo y = std::move(x);
1242 Foo z = x;
1243 })cpp";
1244 Server.addDocument(testPath("foo.h"), SourceContents);
1245 ASSERT_TRUE(Server.blockUntilIdleForTest());
1246 EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback);
1247}
1248
1249TEST(ClangdServer, MemoryUsageTest) {
1250 MockFS FS;
1252 ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
1253
1254 auto FooCpp = testPath("foo.cpp");
1255 Server.addDocument(FooCpp, "");
1256 ASSERT_TRUE(Server.blockUntilIdleForTest());
1257
1258 llvm::BumpPtrAllocator Alloc;
1259 MemoryTree MT(&Alloc);
1260 Server.profile(MT);
1261 ASSERT_TRUE(MT.children().count("tuscheduler"));
1262 EXPECT_TRUE(MT.child("tuscheduler").children().count(FooCpp));
1263}
1264
1265TEST(ClangdServer, RespectsTweakFormatting) {
1266 static constexpr const char *TweakID = "ModuleTweak";
1267 static constexpr const char *NewContents = "{not;\nformatted;}";
1268
1269 // Contributes a tweak that generates a non-formatted insertion and disables
1270 // formatting.
1271 struct TweakContributingModule final : public FeatureModule {
1272 struct ModuleTweak final : public Tweak {
1273 const char *id() const override { return TweakID; }
1274 bool prepare(const Selection &Sel) override { return true; }
1275 Expected<Effect> apply(const Selection &Sel) override {
1276 auto &SM = Sel.AST->getSourceManager();
1277 llvm::StringRef FilePath = SM.getFilename(Sel.Cursor);
1278 tooling::Replacements Reps;
1279 llvm::cantFail(
1280 Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents)));
1281 auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps)));
1282 E.FormatEdits = false;
1283 return E;
1284 }
1285 std::string title() const override { return id(); }
1286 llvm::StringLiteral kind() const override {
1287 return llvm::StringLiteral("");
1288 };
1289 };
1290
1291 void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
1292 Out.emplace_back(new ModuleTweak);
1293 }
1294 };
1295
1296 MockFS FS;
1298 auto Opts = ClangdServer::optsForTest();
1299 FeatureModuleSet Set;
1300 Set.add(std::make_unique<TweakContributingModule>());
1301 Opts.FeatureModules = &Set;
1302 ClangdServer Server(CDB, FS, Opts);
1303
1304 auto FooCpp = testPath("foo.cpp");
1305 Server.addDocument(FooCpp, "");
1306 ASSERT_TRUE(Server.blockUntilIdleForTest());
1307
1308 // Ensure that disabled formatting is respected.
1309 Notification N;
1310 Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected<Tweak::Effect> E) {
1311 ASSERT_TRUE(static_cast<bool>(E));
1312 EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()),
1313 NewContents);
1314 N.notify();
1315 });
1316 N.wait();
1317}
1318
1319TEST(ClangdServer, InactiveRegions) {
1320 struct InactiveRegionsCallback : ClangdServer::Callbacks {
1321 std::vector<std::vector<Range>> FoundInactiveRegions;
1322
1323 void onInactiveRegionsReady(PathRef FIle,
1324 std::vector<Range> InactiveRegions) override {
1325 FoundInactiveRegions.push_back(std::move(InactiveRegions));
1326 }
1327 };
1328
1329 MockFS FS;
1331 CDB.ExtraClangFlags.push_back("-DCMDMACRO");
1332 auto Opts = ClangdServer::optsForTest();
1333 Opts.PublishInactiveRegions = true;
1334 InactiveRegionsCallback Callback;
1335 ClangdServer Server(CDB, FS, Opts, &Callback);
1336 Annotations Source(R"cpp(
1337#define PREAMBLEMACRO 42
1338#if PREAMBLEMACRO > 40
1339 #define ACTIVE
1340#else
1341$inactive1[[ #define INACTIVE]]
1342#endif
1343int endPreamble;
1344#ifndef CMDMACRO
1345$inactive2[[ int inactiveInt;]]
1346#endif
1347#undef CMDMACRO
1348#ifdef CMDMACRO
1349$inactive3[[ int inactiveInt2;]]
1350#elif PREAMBLEMACRO > 0
1351 int activeInt1;
1352 int activeInt2;
1353#else
1354$inactive4[[ int inactiveInt3;]]
1355#endif
1356#ifdef CMDMACRO
1357#endif // empty inactive range, gets dropped
1358 )cpp");
1359 Server.addDocument(testPath("foo.cpp"), Source.code());
1360 ASSERT_TRUE(Server.blockUntilIdleForTest());
1361 EXPECT_THAT(Callback.FoundInactiveRegions,
1362 ElementsAre(ElementsAre(
1363 Source.range("inactive1"), Source.range("inactive2"),
1364 Source.range("inactive3"), Source.range("inactive4"))));
1365}
1366
1367} // namespace
1368} // namespace clangd
1369} // namespace clang
std::string CommandLine
#define EXPECT_ERROR(expectedValue)
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
Interface with hooks for users of ClangdServer to be notified of events.
Manages a collection of source files and derived data (ASTs, indexes), and provides language-aware fe...
static std::function< Context(PathRef)> createConfiguredContextProvider(const config::Provider *Provider, ClangdServer::Callbacks *)
Creates a context provider that loads and installs config.
A FeatureModuleSet is a collection of feature modules installed in clangd.
void add(std::unique_ptr< FeatureModule > M)
A FeatureModule contributes a vertical feature to clangd.
This manages symbols from files and an in-memory index on all symbols.
Definition FileIndex.h:110
Provides compilation arguments used for parsing C and C++ files.
Values in a Context are indexed by typed keys.
Definition Context.h:40
std::vector< std::string > ExtraClangFlags
Definition TestFS.h:68
llvm::StringMap< std::string > Files
Definition TestFS.h:45
A threadsafe flag that is initially clear.
Definition Threading.h:91
Wraps another compilation database, and supports overriding the commands using an in-memory mapping.
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
WithContextValue extends Context::current() with a single value.
Definition Context.h:200
A source of configuration fragments.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
@ Changed
The file got changed.
Definition Protocol.h:833
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
Definition TestTU.cpp:231
TidyProvider combine(std::vector< TidyProvider > Providers)
ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, const ASTContext &Ctx)
Definition DumpAST.cpp:414
llvm::Expected< RenameResult > runRename(ClangdServer &Server, PathRef File, Position Pos, llvm::StringRef NewName, const RenameOptions &RenameOpts)
Definition SyncAPI.cpp:101
MATCHER_P2(hasFlag, Flag, Path, "")
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
Definition Function.h:28
llvm::Expected< CodeCompleteResult > runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, clangd::CodeCompleteOptions Opts)
Definition SyncAPI.cpp:72
llvm::Expected< SignatureHelp > runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos, MarkupKind DocumentationFormat)
Definition SyncAPI.cpp:79
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition TestFS.cpp:94
TEST(BackgroundQueueTest, Priority)
void runAddDocument(ClangdServer &Server, PathRef File, llvm::StringRef Contents, llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild)
Definition SyncAPI.cpp:17
llvm::Expected< std::vector< LocatedSymbol > > runLocateSymbolAt(ClangdServer &Server, PathRef File, Position Pos)
Definition SyncAPI.cpp:88
TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS)
Provider that searches for .clang-tidy configuration files in the directory tree.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > buildTestFS(llvm::StringMap< std::string > const &Files, llvm::StringMap< time_t > const &Timestamps)
Definition TestFS.cpp:33
TidyProvider disableUnusableChecks(llvm::ArrayRef< std::string > ExtraBadChecks)
Provider that will disable checks known to not work with clangd.
std::string Path
A typedef to represent a file path.
Definition Path.h:26
llvm::Expected< tooling::Replacements > runFormatFile(ClangdServer &Server, PathRef File, const std::vector< Range > &Rngs)
Definition SyncAPI.cpp:119
llvm::Error runCustomAction(ClangdServer &Server, PathRef File, llvm::function_ref< void(InputsAndAST)> Action)
Definition SyncAPI.cpp:162
llvm::Expected< std::vector< DocumentHighlight > > runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos)
Definition SyncAPI.cpp:95
const char * testRoot()
Definition TestFS.cpp:85
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static CommandMangler forTests()
A tree that can be used to represent memory usage of nested components while preserving the hierarchy...
Definition MemoryTree.h:30
int line
Line position in a document (zero-based).
Definition Protocol.h:159
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition Protocol.cpp:46