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