1 //===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "Annotations.h" 10 #include "ClangdServer.h" 11 #include "CodeComplete.h" 12 #include "CompileCommands.h" 13 #include "ConfigFragment.h" 14 #include "GlobalCompilationDatabase.h" 15 #include "Matchers.h" 16 #include "SyncAPI.h" 17 #include "TestFS.h" 18 #include "TestTU.h" 19 #include "TidyProvider.h" 20 #include "refactor/Tweak.h" 21 #include "support/MemoryTree.h" 22 #include "support/Path.h" 23 #include "support/Threading.h" 24 #include "clang/Config/config.h" 25 #include "clang/Sema/CodeCompleteConsumer.h" 26 #include "clang/Tooling/ArgumentsAdjusters.h" 27 #include "clang/Tooling/Core/Replacement.h" 28 #include "llvm/ADT/ArrayRef.h" 29 #include "llvm/ADT/SmallVector.h" 30 #include "llvm/ADT/StringMap.h" 31 #include "llvm/ADT/StringRef.h" 32 #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX 33 #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 50 namespace clang { 51 namespace clangd { 52 53 namespace { 54 55 using ::testing::AllOf; 56 using ::testing::ElementsAre; 57 using ::testing::Field; 58 using ::testing::IsEmpty; 59 using ::testing::Pair; 60 using ::testing::SizeIs; 61 using ::testing::UnorderedElementsAre; 62 63 MATCHER_P2(DeclAt, File, Range, "") { 64 return arg.PreferredDeclaration == 65 Location{URIForFile::canonicalize(File, testRoot()), Range}; 66 } 67 68 bool 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 77 class ErrorCheckingCallbacks : public ClangdServer::Callbacks { 78 public: 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 91 private: 92 std::mutex Mutex; 93 bool HadErrorInLastDiags = false; 94 }; 95 96 /// For each file, record whether the last published diagnostics contained at 97 /// least one error. 98 class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks { 99 public: 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 diagnostics 110 /// 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 124 private: 125 mutable std::mutex Mutex; 126 llvm::StringMap<bool> LastDiagsHadError; 127 }; 128 129 /// Replaces all patterns of the form 0x123abc with spaces 130 std::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 148 std::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 165 std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) { 166 return replacePtrsInDump(dumpAST(Server, File)); 167 } 168 169 std::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 189 TEST(ClangdServerTest, Parse) { 190 // FIXME: figure out a stable format for AST dumps, so that we can check the 191 // output of the dump itself is equal to the expected one, not just that it's 192 // 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 208 TEST(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" 216 int 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 224 TEST(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" 232 int 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 259 TEST(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" 267 int 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 297 TEST(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 328 TEST(ClangdServerTest, RespectsConfig) { 329 // Go-to-definition will resolve as marked if FOO is defined. 330 Annotations Example(R"cpp( 331 #ifdef FOO 332 int [[x]]; 333 #else 334 int x; 335 #endif 336 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 371 TEST(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 Unix 389 #ifdef LLVM_ON_UNIX 390 TEST(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 string 402 SmallString<8> Version("4.9.3"); 403 404 // A lib dir for gcc installation 405 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 gcc 409 // 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> 424 mock_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> 433 std::string x; 434 )cpp"; 435 runAddDocument(Server, FooCpp, SourceContentsWithError); 436 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); 437 } 438 #endif // LLVM_ON_UNIX 439 440 TEST(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( 448 template <class T> 449 struct foo { T x; }; 450 )cpp"; 451 const auto *SourceContents2 = R"cpp( 452 template <class T> 453 struct 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 476 TEST(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_ERROR 485 this 486 #endif 487 488 int 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. 508 TEST(ClangdServerTest, ReparseOpenedFiles) { 509 Annotations FooSource(R"cpp( 510 #ifdef MACRO 511 static void $one[[bob]]() {} 512 #else 513 static void $two[[bob]]() {} 514 #endif 515 516 int main () { bo^b (); return 0; } 517 )cpp"); 518 519 Annotations BarSource(R"cpp( 520 #ifdef MACRO 521 this is an error 522 #endif 523 )cpp"); 524 525 Annotations BazSource(R"cpp( 526 int 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 572 MATCHER_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 580 TEST(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( 588 struct 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 616 TEST(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 645 TEST(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 test 650 // dramatically, so they are issued rarely. Each 651 // BlockingRequestInterval-request will be a blocking one. 652 const unsigned BlockingRequestInterval = 40; 653 654 const auto *SourceContentsWithoutErrors = R"cpp( 655 int a; 656 int b; 657 int c; 658 int d; 659 )cpp"; 660 661 const auto *SourceContentsWithErrors = R"cpp( 662 int a = x; 663 int b; 664 int c; 665 int d; 666 )cpp"; 667 668 // Giving invalid line and column number should not crash ClangdServer, but 669 // just to make sure we're sometimes hitting the bounds inside the file we 670 // 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 else 705 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% of 741 // 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 else 754 ++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 ? SourceContentsWithErrors 769 : 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 make 805 // tests slow, since there's no way to cancel previous completion 806 // requests as opposed to AddDocument/RemoveDocument, which are implicitly 807 // cancelled by any subsequent AddDocument/RemoveDocument request to the 808 // 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 863 TEST(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( 895 int a; 896 int b; 897 int c; 898 int d; 899 )cpp"; 900 901 const auto *SourceContentsWithErrors = R"cpp( 902 int a = x; 903 int b; 904 int c; 905 int 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 925 TEST(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 936 void f( ) {} 937 )cpp"; 938 std::string Expected = R"cpp( 939 #include "x.h" 940 #include "y.h" 941 942 void f() {} 943 )cpp"; 944 FS.Files[Path] = Code; 945 runAddDocument(Server, Path, Code); 946 947 auto Replaces = runFormatFile(Server, Path, /*Rng=*/std::nullopt); 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 954 TEST(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 updated 978 // 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 _WIN32 991 // Check that running code completion doesn't stat() a bunch of files from the 992 // preamble again. (They should be using the preamble's stat-cache) 993 TEST(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 #endif 1059 1060 TEST(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 1106 TEST(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 case 1118 // 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 update 1148 // 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 1165 TEST(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 the 1181 // test as a workaround. 1182 #if !defined(__has_feature) || !__has_feature(address_sanitizer) 1183 TEST(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 stack 1200 // overflow 1201 EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); 1202 } 1203 #endif 1204 1205 TEST(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't 1220 // run. 1221 FS.Files[testPath(".clang-tidy")] = R"( 1222 Checks: -*,bugprone-use-after-move,llvm-header-guard 1223 )"; 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 1246 TEST(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 1262 TEST(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 disables 1267 // 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 1316 TEST(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 42 1335 #if PREAMBLEMACRO > 40 1336 #define ACTIVE 1337 #else 1338 $inactive1[[ #define INACTIVE]] 1339 #endif 1340 int endPreamble; 1341 #ifndef CMDMACRO 1342 $inactive2[[ int inactiveInt;]] 1343 #endif 1344 #undef CMDMACRO 1345 #ifdef CMDMACRO 1346 $inactive3[[ int inactiveInt2;]] 1347 #elif PREAMBLEMACRO > 0 1348 int activeInt1; 1349 int activeInt2; 1350 #else 1351 $inactive4[[ int inactiveInt3;]] 1352 #endif 1353 #ifdef CMDMACRO 1354 #endif // empty inactive range, gets dropped 1355 )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 } // namespace 1365 } // namespace clangd 1366 } // namespace clang 1367