brintos

brintos / llvm-project-archived public Read only

0
0
Text · 45.2 KiB · 9ea7c3e Raw
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