1 //===-- FileIndexTests.cpp ---------------------------*- 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 "Compiler.h" 11 #include "Headers.h" 12 #include "ParsedAST.h" 13 #include "SyncAPI.h" 14 #include "TestFS.h" 15 #include "TestTU.h" 16 #include "TestWorkspace.h" 17 #include "URI.h" 18 #include "clang-include-cleaner/Record.h" 19 #include "index/FileIndex.h" 20 #include "index/Index.h" 21 #include "index/Ref.h" 22 #include "index/Relation.h" 23 #include "index/Serialization.h" 24 #include "index/Symbol.h" 25 #include "index/SymbolID.h" 26 #include "support/Threading.h" 27 #include "clang/Frontend/CompilerInvocation.h" 28 #include "clang/Tooling/CompilationDatabase.h" 29 #include "llvm/ADT/ArrayRef.h" 30 #include "llvm/Support/Allocator.h" 31 #include "gmock/gmock.h" 32 #include "gtest/gtest.h" 33 #include <memory> 34 #include <utility> 35 #include <vector> 36 37 using ::testing::_; 38 using ::testing::AllOf; 39 using ::testing::Contains; 40 using ::testing::ElementsAre; 41 using ::testing::Gt; 42 using ::testing::IsEmpty; 43 using ::testing::Pair; 44 using ::testing::UnorderedElementsAre; 45 46 MATCHER_P(refRange, Range, "") { 47 return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), 48 arg.Location.End.line(), arg.Location.End.column()) == 49 std::make_tuple(Range.start.line, Range.start.character, 50 Range.end.line, Range.end.character); 51 } 52 MATCHER_P(fileURI, F, "") { return llvm::StringRef(arg.Location.FileURI) == F; } 53 MATCHER_P(declURI, U, "") { 54 return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U; 55 } 56 MATCHER_P(defURI, U, "") { 57 return llvm::StringRef(arg.Definition.FileURI) == U; 58 } 59 MATCHER_P(qName, N, "") { return (arg.Scope + arg.Name).str() == N; } 60 MATCHER_P(numReferences, N, "") { return arg.References == N; } 61 MATCHER_P(hasOrign, O, "") { return bool(arg.Origin & O); } 62 63 MATCHER_P(includeHeader, P, "") { 64 return (arg.IncludeHeaders.size() == 1) && 65 (arg.IncludeHeaders.begin()->IncludeHeader == P); 66 } 67 68 namespace clang { 69 namespace clangd { 70 namespace { 71 ::testing::Matcher<const RefSlab &> 72 refsAre(std::vector<::testing::Matcher<Ref>> Matchers) { 73 return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers))); 74 } 75 76 Symbol symbol(llvm::StringRef ID) { 77 Symbol Sym; 78 Sym.ID = SymbolID(ID); 79 Sym.Name = ID; 80 return Sym; 81 } 82 83 std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) { 84 SymbolSlab::Builder Slab; 85 for (int I = Begin; I <= End; I++) 86 Slab.insert(symbol(std::to_string(I))); 87 return std::make_unique<SymbolSlab>(std::move(Slab).build()); 88 } 89 90 std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) { 91 RefSlab::Builder Slab; 92 Ref R; 93 R.Location.FileURI = Path; 94 R.Kind = RefKind::Reference; 95 Slab.insert(ID, R); 96 return std::make_unique<RefSlab>(std::move(Slab).build()); 97 } 98 99 std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) { 100 RelationSlab::Builder RelBuilder; 101 for (auto &Rel : Rels) 102 RelBuilder.insert(Rel); 103 return std::make_unique<RelationSlab>(std::move(RelBuilder).build()); 104 } 105 106 TEST(FileSymbolsTest, UpdateAndGet) { 107 FileSymbols FS(IndexContents::All, true); 108 EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty()); 109 110 FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"), nullptr, 111 false); 112 EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), 113 UnorderedElementsAre(qName("1"), qName("2"), qName("3"))); 114 EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")), 115 refsAre({fileURI("f1.cc")})); 116 } 117 118 TEST(FileSymbolsTest, Overlap) { 119 FileSymbols FS(IndexContents::All, true); 120 FS.update("f1", numSlab(1, 3), nullptr, nullptr, false); 121 FS.update("f2", numSlab(3, 5), nullptr, nullptr, false); 122 for (auto Type : {IndexType::Light, IndexType::Heavy}) 123 EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""), 124 UnorderedElementsAre(qName("1"), qName("2"), qName("3"), 125 qName("4"), qName("5"))); 126 } 127 128 TEST(FileSymbolsTest, MergeOverlap) { 129 FileSymbols FS(IndexContents::All, true); 130 auto OneSymboSlab = [](Symbol Sym) { 131 SymbolSlab::Builder S; 132 S.insert(Sym); 133 return std::make_unique<SymbolSlab>(std::move(S).build()); 134 }; 135 auto X1 = symbol("x"); 136 X1.CanonicalDeclaration.FileURI = "file:///x1"; 137 auto X2 = symbol("x"); 138 X2.Definition.FileURI = "file:///x2"; 139 140 FS.update("f1", OneSymboSlab(X1), nullptr, nullptr, false); 141 FS.update("f2", OneSymboSlab(X2), nullptr, nullptr, false); 142 for (auto Type : {IndexType::Light, IndexType::Heavy}) 143 EXPECT_THAT( 144 runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"), 145 UnorderedElementsAre( 146 AllOf(qName("x"), declURI("file:///x1"), defURI("file:///x2")))); 147 } 148 149 TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { 150 FileSymbols FS(IndexContents::All, true); 151 152 SymbolID ID("1"); 153 FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"), nullptr, false); 154 155 auto Symbols = FS.buildIndex(IndexType::Light); 156 EXPECT_THAT(runFuzzyFind(*Symbols, ""), 157 UnorderedElementsAre(qName("1"), qName("2"), qName("3"))); 158 EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc")})); 159 160 FS.update("f1", nullptr, nullptr, nullptr, false); 161 auto Empty = FS.buildIndex(IndexType::Light); 162 EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty()); 163 EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); 164 165 EXPECT_THAT(runFuzzyFind(*Symbols, ""), 166 UnorderedElementsAre(qName("1"), qName("2"), qName("3"))); 167 EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc")})); 168 } 169 170 // Adds Basename.cpp, which includes Basename.h, which contains Code. 171 void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) { 172 TestTU File; 173 File.Filename = (Basename + ".cpp").str(); 174 File.HeaderFilename = (Basename + ".h").str(); 175 File.HeaderCode = std::string(Code); 176 auto AST = File.build(); 177 M.updatePreamble(testPath(File.Filename), /*Version=*/"null", 178 AST.getASTContext(), AST.getPreprocessor(), 179 AST.getPragmaIncludes()); 180 } 181 182 TEST(FileIndexTest, CustomizedURIScheme) { 183 FileIndex M(true); 184 update(M, "f", "class string {};"); 185 186 EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(declURI("unittest:///f.h"))); 187 } 188 189 TEST(FileIndexTest, IndexAST) { 190 FileIndex M(true); 191 update(M, "f1", "namespace ns { void f() {} class X {}; }"); 192 193 FuzzyFindRequest Req; 194 Req.Query = ""; 195 Req.Scopes = {"ns::"}; 196 EXPECT_THAT(runFuzzyFind(M, Req), 197 UnorderedElementsAre(qName("ns::f"), qName("ns::X"))); 198 } 199 200 TEST(FileIndexTest, NoLocal) { 201 FileIndex M(true); 202 update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }"); 203 204 EXPECT_THAT( 205 runFuzzyFind(M, ""), 206 UnorderedElementsAre(qName("ns"), qName("ns::f"), qName("ns::X"))); 207 } 208 209 TEST(FileIndexTest, IndexMultiASTAndDeduplicate) { 210 FileIndex M(true); 211 update(M, "f1", "namespace ns { void f() {} class X {}; }"); 212 update(M, "f2", "namespace ns { void ff() {} class X {}; }"); 213 214 FuzzyFindRequest Req; 215 Req.Scopes = {"ns::"}; 216 EXPECT_THAT( 217 runFuzzyFind(M, Req), 218 UnorderedElementsAre(qName("ns::f"), qName("ns::X"), qName("ns::ff"))); 219 } 220 221 TEST(FileIndexTest, ClassMembers) { 222 FileIndex M(true); 223 update(M, "f1", "class X { static int m1; int m2; static void f(); };"); 224 225 EXPECT_THAT(runFuzzyFind(M, ""), 226 UnorderedElementsAre(qName("X"), qName("X::m1"), qName("X::m2"), 227 qName("X::f"))); 228 } 229 230 TEST(FileIndexTest, IncludeCollected) { 231 FileIndex M(true); 232 update( 233 M, "f", 234 "// IWYU pragma: private, include <the/good/header.h>\nclass string {};"); 235 236 auto Symbols = runFuzzyFind(M, ""); 237 EXPECT_THAT(Symbols, ElementsAre(_)); 238 EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, 239 "<the/good/header.h>"); 240 } 241 242 TEST(FileIndexTest, IWYUPragmaExport) { 243 FileIndex M(true); 244 245 TestTU File; 246 File.Code = R"cpp(#pragma once 247 #include "exporter.h" 248 )cpp"; 249 File.HeaderFilename = "exporter.h"; 250 File.HeaderCode = R"cpp(#pragma once 251 #include "private.h" // IWYU pragma: export 252 )cpp"; 253 File.AdditionalFiles["private.h"] = "class Foo{};"; 254 auto AST = File.build(); 255 M.updatePreamble(testPath(File.Filename), /*Version=*/"null", 256 AST.getASTContext(), AST.getPreprocessor(), 257 AST.getPragmaIncludes()); 258 259 auto Symbols = runFuzzyFind(M, ""); 260 EXPECT_THAT( 261 Symbols, 262 UnorderedElementsAre(AllOf( 263 qName("Foo"), 264 includeHeader(URI::create(testPath(File.HeaderFilename)).toString()), 265 declURI(URI::create(testPath("private.h")).toString())))); 266 } 267 268 TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) { 269 TestTU TU; 270 TU.HeaderCode = "class Foo{};"; 271 TU.HeaderFilename = "algorithm"; 272 273 auto Symbols = runFuzzyFind(*TU.index(), ""); 274 EXPECT_THAT(Symbols, ElementsAre(_)); 275 EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, 276 "<algorithm>"); 277 } 278 279 TEST(FileIndexTest, TemplateParamsInLabel) { 280 auto *Source = R"cpp( 281 template <class Ty> 282 class vector { 283 }; 284 285 template <class Ty, class Arg> 286 vector<Ty> make_vector(Arg A) {} 287 )cpp"; 288 289 FileIndex M(true); 290 update(M, "f", Source); 291 292 auto Symbols = runFuzzyFind(M, ""); 293 EXPECT_THAT(Symbols, 294 UnorderedElementsAre(qName("vector"), qName("make_vector"))); 295 auto It = Symbols.begin(); 296 Symbol Vector = *It++; 297 Symbol MakeVector = *It++; 298 if (MakeVector.Name == "vector") 299 std::swap(MakeVector, Vector); 300 301 EXPECT_EQ(Vector.Signature, "<class Ty>"); 302 EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>"); 303 304 EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)"); 305 EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})"); 306 } 307 308 TEST(FileIndexTest, RebuildWithPreamble) { 309 auto FooCpp = testPath("foo.cpp"); 310 auto FooH = testPath("foo.h"); 311 // Preparse ParseInputs. 312 ParseInputs PI; 313 PI.CompileCommand.Directory = testRoot(); 314 PI.CompileCommand.Filename = FooCpp; 315 PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp}; 316 317 MockFS FS; 318 FS.Files[FooCpp] = ""; 319 FS.Files[FooH] = R"cpp( 320 namespace ns_in_header { 321 int func_in_header(); 322 } 323 )cpp"; 324 PI.TFS = &FS; 325 326 PI.Contents = R"cpp( 327 #include "foo.h" 328 namespace ns_in_source { 329 int func_in_source(); 330 } 331 )cpp"; 332 333 // Rebuild the file. 334 IgnoreDiagnostics IgnoreDiags; 335 auto CI = buildCompilerInvocation(PI, IgnoreDiags); 336 337 FileIndex Index(true); 338 bool IndexUpdated = false; 339 buildPreamble( 340 FooCpp, *CI, PI, 341 /*StoreInMemory=*/true, 342 [&](CapturedASTCtx ASTCtx, 343 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) { 344 auto &Ctx = ASTCtx.getASTContext(); 345 auto &PP = ASTCtx.getPreprocessor(); 346 EXPECT_FALSE(IndexUpdated) << "Expected only a single index update"; 347 IndexUpdated = true; 348 Index.updatePreamble(FooCpp, /*Version=*/"null", Ctx, PP, *PI); 349 }); 350 ASSERT_TRUE(IndexUpdated); 351 352 // Check the index contains symbols from the preamble, but not from the main 353 // file. 354 FuzzyFindRequest Req; 355 Req.Query = ""; 356 Req.Scopes = {"", "ns_in_header::"}; 357 358 EXPECT_THAT(runFuzzyFind(Index, Req), 359 UnorderedElementsAre(qName("ns_in_header"), 360 qName("ns_in_header::func_in_header"))); 361 } 362 363 TEST(FileIndexTest, Refs) { 364 const char *HeaderCode = "class Foo {};"; 365 Annotations MainCode(R"cpp( 366 void f() { 367 $foo[[Foo]] foo; 368 } 369 )cpp"); 370 371 auto Foo = 372 findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo"); 373 374 RefsRequest Request; 375 Request.IDs = {Foo.ID}; 376 377 FileIndex Index(true); 378 // Add test.cc 379 TestTU Test; 380 Test.HeaderCode = HeaderCode; 381 Test.Code = std::string(MainCode.code()); 382 Test.Filename = "test.cc"; 383 auto AST = Test.build(); 384 Index.updateMain(testPath(Test.Filename), AST); 385 // Add test2.cc 386 TestTU Test2; 387 Test2.HeaderCode = HeaderCode; 388 Test2.Code = std::string(MainCode.code()); 389 Test2.Filename = "test2.cc"; 390 AST = Test2.build(); 391 Index.updateMain(testPath(Test2.Filename), AST); 392 393 EXPECT_THAT(getRefs(Index, Foo.ID), 394 refsAre({AllOf(refRange(MainCode.range("foo")), 395 fileURI("unittest:///test.cc")), 396 AllOf(refRange(MainCode.range("foo")), 397 fileURI("unittest:///test2.cc"))})); 398 } 399 400 TEST(FileIndexTest, MacroRefs) { 401 Annotations HeaderCode(R"cpp( 402 #define $def1[[HEADER_MACRO]](X) (X+1) 403 )cpp"); 404 Annotations MainCode(R"cpp( 405 #define $def2[[MAINFILE_MACRO]](X) (X+1) 406 void f() { 407 int a = $ref1[[HEADER_MACRO]](2); 408 int b = $ref2[[MAINFILE_MACRO]](1); 409 } 410 )cpp"); 411 412 FileIndex Index(true); 413 // Add test.cc 414 TestTU Test; 415 Test.HeaderCode = std::string(HeaderCode.code()); 416 Test.Code = std::string(MainCode.code()); 417 Test.Filename = "test.cc"; 418 auto AST = Test.build(); 419 Index.updateMain(testPath(Test.Filename), AST); 420 421 auto HeaderMacro = findSymbol(Test.headerSymbols(), "HEADER_MACRO"); 422 EXPECT_THAT(getRefs(Index, HeaderMacro.ID), 423 refsAre({AllOf(refRange(MainCode.range("ref1")), 424 fileURI("unittest:///test.cc"))})); 425 426 auto MainFileMacro = findSymbol(Test.headerSymbols(), "MAINFILE_MACRO"); 427 EXPECT_THAT(getRefs(Index, MainFileMacro.ID), 428 refsAre({AllOf(refRange(MainCode.range("def2")), 429 fileURI("unittest:///test.cc")), 430 AllOf(refRange(MainCode.range("ref2")), 431 fileURI("unittest:///test.cc"))})); 432 } 433 434 TEST(FileIndexTest, CollectMacros) { 435 FileIndex M(true); 436 update(M, "f", "#define CLANGD 1"); 437 EXPECT_THAT(runFuzzyFind(M, ""), Contains(qName("CLANGD"))); 438 } 439 440 TEST(FileIndexTest, Relations) { 441 TestTU TU; 442 TU.Filename = "f.cpp"; 443 TU.HeaderFilename = "f.h"; 444 TU.HeaderCode = "class A {}; class B : public A {};"; 445 auto AST = TU.build(); 446 FileIndex Index(true); 447 Index.updatePreamble(testPath(TU.Filename), /*Version=*/"null", 448 AST.getASTContext(), AST.getPreprocessor(), 449 AST.getPragmaIncludes()); 450 SymbolID A = findSymbol(TU.headerSymbols(), "A").ID; 451 uint32_t Results = 0; 452 RelationsRequest Req; 453 Req.Subjects.insert(A); 454 Req.Predicate = RelationKind::BaseOf; 455 Index.relations(Req, [&](const SymbolID &, const Symbol &) { ++Results; }); 456 EXPECT_EQ(Results, 1u); 457 } 458 459 TEST(FileIndexTest, RelationsMultiFile) { 460 TestWorkspace Workspace; 461 Workspace.addSource("Base.h", "class Base {};"); 462 Workspace.addMainFile("A.cpp", R"cpp( 463 #include "Base.h" 464 class A : public Base {}; 465 )cpp"); 466 Workspace.addMainFile("B.cpp", R"cpp( 467 #include "Base.h" 468 class B : public Base {}; 469 )cpp"); 470 471 auto Index = Workspace.index(); 472 FuzzyFindRequest FFReq; 473 FFReq.Query = "Base"; 474 FFReq.AnyScope = true; 475 SymbolID Base; 476 Index->fuzzyFind(FFReq, [&](const Symbol &S) { Base = S.ID; }); 477 478 RelationsRequest Req; 479 Req.Subjects.insert(Base); 480 Req.Predicate = RelationKind::BaseOf; 481 uint32_t Results = 0; 482 Index->relations(Req, [&](const SymbolID &, const Symbol &) { ++Results; }); 483 EXPECT_EQ(Results, 2u); 484 } 485 486 TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { 487 TestTU TU; 488 TU.HeaderCode = "class Foo{};"; 489 Annotations Main(R"cpp( 490 void f() { 491 [[Foo]] foo; 492 } 493 )cpp"); 494 TU.Code = std::string(Main.code()); 495 auto AST = TU.build(); 496 FileIndex Index(true); 497 Index.updateMain(testPath(TU.Filename), AST); 498 499 // Expect to see references in main file, references in headers are excluded 500 // because we only index main AST. 501 EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID), 502 refsAre({refRange(Main.range())})); 503 } 504 505 TEST(FileIndexTest, MergeMainFileSymbols) { 506 const char *CommonHeader = "void foo();"; 507 TestTU Header = TestTU::withCode(CommonHeader); 508 TestTU Cpp = TestTU::withCode("void foo() {}"); 509 Cpp.Filename = "foo.cpp"; 510 Cpp.HeaderFilename = "foo.h"; 511 Cpp.HeaderCode = CommonHeader; 512 513 FileIndex Index(true); 514 auto HeaderAST = Header.build(); 515 auto CppAST = Cpp.build(); 516 Index.updateMain(testPath("foo.h"), HeaderAST); 517 Index.updateMain(testPath("foo.cpp"), CppAST); 518 519 auto Symbols = runFuzzyFind(Index, ""); 520 // Check foo is merged, foo in Cpp wins (as we see the definition there). 521 EXPECT_THAT(Symbols, ElementsAre(AllOf(declURI("unittest:///foo.h"), 522 defURI("unittest:///foo.cpp"), 523 hasOrign(SymbolOrigin::Merge)))); 524 } 525 526 TEST(FileSymbolsTest, CountReferencesNoRefSlabs) { 527 FileSymbols FS(IndexContents::All, true); 528 FS.update("f1", numSlab(1, 3), nullptr, nullptr, true); 529 FS.update("f2", numSlab(1, 3), nullptr, nullptr, false); 530 EXPECT_THAT( 531 runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), 532 ""), 533 UnorderedElementsAre(AllOf(qName("1"), numReferences(0u)), 534 AllOf(qName("2"), numReferences(0u)), 535 AllOf(qName("3"), numReferences(0u)))); 536 } 537 538 TEST(FileSymbolsTest, CountReferencesWithRefSlabs) { 539 FileSymbols FS(IndexContents::All, true); 540 FS.update("f1cpp", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cpp"), nullptr, 541 true); 542 FS.update("f1h", numSlab(1, 3), refSlab(SymbolID("1"), "f1.h"), nullptr, 543 false); 544 FS.update("f2cpp", numSlab(1, 3), refSlab(SymbolID("2"), "f2.cpp"), nullptr, 545 true); 546 FS.update("f2h", numSlab(1, 3), refSlab(SymbolID("2"), "f2.h"), nullptr, 547 false); 548 FS.update("f3cpp", numSlab(1, 3), refSlab(SymbolID("3"), "f3.cpp"), nullptr, 549 true); 550 FS.update("f3h", numSlab(1, 3), refSlab(SymbolID("3"), "f3.h"), nullptr, 551 false); 552 EXPECT_THAT( 553 runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), 554 ""), 555 UnorderedElementsAre(AllOf(qName("1"), numReferences(1u)), 556 AllOf(qName("2"), numReferences(1u)), 557 AllOf(qName("3"), numReferences(1u)))); 558 } 559 560 TEST(FileIndexTest, StalePreambleSymbolsDeleted) { 561 FileIndex M(true); 562 TestTU File; 563 File.HeaderFilename = "a.h"; 564 565 File.Filename = "f1.cpp"; 566 File.HeaderCode = "int a;"; 567 auto AST = File.build(); 568 M.updatePreamble(testPath(File.Filename), /*Version=*/"null", 569 AST.getASTContext(), AST.getPreprocessor(), 570 AST.getPragmaIncludes()); 571 EXPECT_THAT(runFuzzyFind(M, ""), UnorderedElementsAre(qName("a"))); 572 573 File.Filename = "f2.cpp"; 574 File.HeaderCode = "int b;"; 575 AST = File.build(); 576 M.updatePreamble(testPath(File.Filename), /*Version=*/"null", 577 AST.getASTContext(), AST.getPreprocessor(), 578 AST.getPragmaIncludes()); 579 EXPECT_THAT(runFuzzyFind(M, ""), UnorderedElementsAre(qName("b"))); 580 } 581 582 // Verifies that concurrent calls to updateMain don't "lose" any updates. 583 TEST(FileIndexTest, Threadsafety) { 584 FileIndex M(true); 585 Notification Go; 586 587 constexpr int Count = 10; 588 { 589 // Set up workers to concurrently call updateMain() with separate files. 590 AsyncTaskRunner Pool; 591 for (unsigned I = 0; I < Count; ++I) { 592 auto TU = TestTU::withCode(llvm::formatv("int xxx{0};", I).str()); 593 TU.Filename = llvm::formatv("x{0}.c", I).str(); 594 Pool.runAsync(TU.Filename, [&, Filename(testPath(TU.Filename)), 595 AST(TU.build())]() mutable { 596 Go.wait(); 597 M.updateMain(Filename, AST); 598 }); 599 } 600 // On your marks, get set... 601 Go.notify(); 602 } 603 604 EXPECT_THAT(runFuzzyFind(M, "xxx"), ::testing::SizeIs(Count)); 605 } 606 607 TEST(FileShardedIndexTest, Sharding) { 608 auto AHeaderUri = URI::create(testPath("a.h")).toString(); 609 auto BHeaderUri = URI::create(testPath("b.h")).toString(); 610 auto BSourceUri = URI::create(testPath("b.cc")).toString(); 611 612 auto Sym1 = symbol("1"); 613 Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str(); 614 615 auto Sym2 = symbol("2"); 616 Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str(); 617 Sym2.Definition.FileURI = BSourceUri.c_str(); 618 619 auto Sym3 = symbol("3"); // not stored 620 621 IndexFileIn IF; 622 { 623 SymbolSlab::Builder B; 624 // Should be stored in only a.h 625 B.insert(Sym1); 626 // Should be stored in both b.h and b.cc 627 B.insert(Sym2); 628 IF.Symbols.emplace(std::move(B).build()); 629 } 630 { 631 // Should be stored in b.cc 632 IF.Refs.emplace(std::move(*refSlab(Sym1.ID, BSourceUri.c_str()))); 633 } 634 { 635 RelationSlab::Builder B; 636 // Should be stored in a.h and b.h 637 B.insert(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}); 638 // Should be stored in a.h and b.h 639 B.insert(Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID}); 640 // Should be stored in a.h (where Sym1 is stored) even though 641 // the relation is dangling as Sym3 is unknown. 642 B.insert(Relation{Sym3.ID, RelationKind::BaseOf, Sym1.ID}); 643 IF.Relations.emplace(std::move(B).build()); 644 } 645 646 IF.Sources.emplace(); 647 IncludeGraph &IG = *IF.Sources; 648 { 649 // b.cc includes b.h 650 auto &Node = IG[BSourceUri]; 651 Node.DirectIncludes = {BHeaderUri}; 652 Node.URI = BSourceUri; 653 } 654 { 655 // b.h includes a.h 656 auto &Node = IG[BHeaderUri]; 657 Node.DirectIncludes = {AHeaderUri}; 658 Node.URI = BHeaderUri; 659 } 660 { 661 // a.h includes nothing. 662 auto &Node = IG[AHeaderUri]; 663 Node.DirectIncludes = {}; 664 Node.URI = AHeaderUri; 665 } 666 667 IF.Cmd = tooling::CompileCommand(testRoot(), "b.cc", {"clang"}, "out"); 668 669 FileShardedIndex ShardedIndex(std::move(IF)); 670 ASSERT_THAT(ShardedIndex.getAllSources(), 671 UnorderedElementsAre(AHeaderUri, BHeaderUri, BSourceUri)); 672 673 { 674 auto Shard = ShardedIndex.getShard(AHeaderUri); 675 ASSERT_TRUE(Shard); 676 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("1"))); 677 EXPECT_THAT(*Shard->Refs, IsEmpty()); 678 EXPECT_THAT( 679 *Shard->Relations, 680 UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, 681 Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID}, 682 Relation{Sym3.ID, RelationKind::BaseOf, Sym1.ID})); 683 ASSERT_THAT(Shard->Sources->keys(), UnorderedElementsAre(AHeaderUri)); 684 EXPECT_THAT(Shard->Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty()); 685 EXPECT_TRUE(Shard->Cmd); 686 } 687 { 688 auto Shard = ShardedIndex.getShard(BHeaderUri); 689 ASSERT_TRUE(Shard); 690 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2"))); 691 EXPECT_THAT(*Shard->Refs, IsEmpty()); 692 EXPECT_THAT( 693 *Shard->Relations, 694 UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, 695 Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID})); 696 ASSERT_THAT(Shard->Sources->keys(), 697 UnorderedElementsAre(BHeaderUri, AHeaderUri)); 698 EXPECT_THAT(Shard->Sources->lookup(BHeaderUri).DirectIncludes, 699 UnorderedElementsAre(AHeaderUri)); 700 EXPECT_TRUE(Shard->Cmd); 701 } 702 { 703 auto Shard = ShardedIndex.getShard(BSourceUri); 704 ASSERT_TRUE(Shard); 705 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2"))); 706 EXPECT_THAT(*Shard->Refs, UnorderedElementsAre(Pair(Sym1.ID, _))); 707 EXPECT_THAT(*Shard->Relations, IsEmpty()); 708 ASSERT_THAT(Shard->Sources->keys(), 709 UnorderedElementsAre(BSourceUri, BHeaderUri)); 710 EXPECT_THAT(Shard->Sources->lookup(BSourceUri).DirectIncludes, 711 UnorderedElementsAre(BHeaderUri)); 712 EXPECT_TRUE(Shard->Cmd); 713 } 714 } 715 716 TEST(FileIndexTest, Profile) { 717 FileIndex FI(true); 718 719 auto FileName = testPath("foo.cpp"); 720 auto AST = TestTU::withHeaderCode("int a;").build(); 721 FI.updateMain(FileName, AST); 722 FI.updatePreamble(FileName, "v1", AST.getASTContext(), AST.getPreprocessor(), 723 AST.getPragmaIncludes()); 724 725 llvm::BumpPtrAllocator Alloc; 726 MemoryTree MT(&Alloc); 727 FI.profile(MT); 728 ASSERT_THAT(MT.children(), 729 UnorderedElementsAre(Pair("preamble", _), Pair("main_file", _))); 730 731 ASSERT_THAT(MT.child("preamble").children(), 732 UnorderedElementsAre(Pair("index", _), Pair("slabs", _))); 733 ASSERT_THAT(MT.child("main_file").children(), 734 UnorderedElementsAre(Pair("index", _), Pair("slabs", _))); 735 736 ASSERT_THAT(MT.child("preamble").child("index").total(), Gt(0U)); 737 ASSERT_THAT(MT.child("main_file").child("index").total(), Gt(0U)); 738 } 739 740 TEST(FileSymbolsTest, Profile) { 741 FileSymbols FS(IndexContents::All, true); 742 FS.update("f1", numSlab(1, 2), nullptr, nullptr, false); 743 FS.update("f2", nullptr, refSlab(SymbolID("1"), "f1"), nullptr, false); 744 FS.update("f3", nullptr, nullptr, 745 relSlab({{SymbolID("1"), RelationKind::BaseOf, SymbolID("2")}}), 746 false); 747 llvm::BumpPtrAllocator Alloc; 748 MemoryTree MT(&Alloc); 749 FS.profile(MT); 750 ASSERT_THAT(MT.children(), UnorderedElementsAre(Pair("f1", _), Pair("f2", _), 751 Pair("f3", _))); 752 EXPECT_THAT(MT.child("f1").children(), ElementsAre(Pair("symbols", _))); 753 EXPECT_THAT(MT.child("f1").total(), Gt(0U)); 754 EXPECT_THAT(MT.child("f2").children(), ElementsAre(Pair("references", _))); 755 EXPECT_THAT(MT.child("f2").total(), Gt(0U)); 756 EXPECT_THAT(MT.child("f3").children(), ElementsAre(Pair("relations", _))); 757 EXPECT_THAT(MT.child("f3").total(), Gt(0U)); 758 } 759 760 TEST(FileIndexTest, MacrosFromMainFile) { 761 FileIndex Idx(true); 762 TestTU TU; 763 TU.Code = "#pragma once\n#define FOO"; 764 TU.Filename = "foo.h"; 765 auto AST = TU.build(); 766 Idx.updateMain(testPath(TU.Filename), AST); 767 768 auto Slab = runFuzzyFind(Idx, ""); 769 auto &FooSymbol = findSymbol(Slab, "FOO"); 770 EXPECT_TRUE(FooSymbol.Flags & Symbol::IndexedForCodeCompletion); 771 } 772 773 } // namespace 774 } // namespace clangd 775 } // namespace clang 776