1 //===-- IndexTests.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 "SyncAPI.h" 11 #include "TestIndex.h" 12 #include "TestTU.h" 13 #include "index/FileIndex.h" 14 #include "index/Index.h" 15 #include "index/MemIndex.h" 16 #include "index/Merge.h" 17 #include "index/Symbol.h" 18 #include "clang/Index/IndexSymbol.h" 19 #include "gmock/gmock.h" 20 #include "gtest/gtest.h" 21 #include <utility> 22 23 using ::testing::_; 24 using ::testing::AllOf; 25 using ::testing::ElementsAre; 26 using ::testing::IsEmpty; 27 using ::testing::Pair; 28 using ::testing::Pointee; 29 using ::testing::UnorderedElementsAre; 30 31 namespace clang { 32 namespace clangd { 33 namespace { 34 35 MATCHER_P(named, N, "") { return arg.Name == N; } 36 MATCHER_P(refRange, Range, "") { 37 return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), 38 arg.Location.End.line(), arg.Location.End.column()) == 39 std::make_tuple(Range.start.line, Range.start.character, 40 Range.end.line, Range.end.character); 41 } 42 MATCHER_P(fileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } 43 44 TEST(SymbolLocation, Position) { 45 using Position = SymbolLocation::Position; 46 Position Pos; 47 48 Pos.setLine(1); 49 EXPECT_EQ(1u, Pos.line()); 50 Pos.setColumn(2); 51 EXPECT_EQ(2u, Pos.column()); 52 EXPECT_FALSE(Pos.hasOverflow()); 53 54 Pos.setLine(Position::MaxLine + 1); // overflow 55 EXPECT_TRUE(Pos.hasOverflow()); 56 EXPECT_EQ(Pos.line(), Position::MaxLine); 57 Pos.setLine(1); // reset the overflowed line. 58 59 Pos.setColumn(Position::MaxColumn + 1); // overflow 60 EXPECT_TRUE(Pos.hasOverflow()); 61 EXPECT_EQ(Pos.column(), Position::MaxColumn); 62 } 63 64 TEST(SymbolSlab, FindAndIterate) { 65 SymbolSlab::Builder B; 66 B.insert(symbol("Z")); 67 B.insert(symbol("Y")); 68 B.insert(symbol("X")); 69 EXPECT_EQ(nullptr, B.find(SymbolID("W"))); 70 for (const char *Sym : {"X", "Y", "Z"}) 71 EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(named(Sym))); 72 73 SymbolSlab S = std::move(B).build(); 74 EXPECT_THAT(S, UnorderedElementsAre(named("X"), named("Y"), named("Z"))); 75 EXPECT_EQ(S.end(), S.find(SymbolID("W"))); 76 for (const char *Sym : {"X", "Y", "Z"}) 77 EXPECT_THAT(*S.find(SymbolID(Sym)), named(Sym)); 78 } 79 80 TEST(RelationSlab, Lookup) { 81 SymbolID A{"A"}; 82 SymbolID B{"B"}; 83 SymbolID C{"C"}; 84 SymbolID D{"D"}; 85 86 RelationSlab::Builder Builder; 87 Builder.insert(Relation{A, RelationKind::BaseOf, B}); 88 Builder.insert(Relation{A, RelationKind::BaseOf, C}); 89 Builder.insert(Relation{B, RelationKind::BaseOf, D}); 90 Builder.insert(Relation{C, RelationKind::BaseOf, D}); 91 92 RelationSlab Slab = std::move(Builder).build(); 93 EXPECT_THAT(Slab.lookup(A, RelationKind::BaseOf), 94 UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B}, 95 Relation{A, RelationKind::BaseOf, C})); 96 } 97 98 TEST(RelationSlab, Duplicates) { 99 SymbolID A{"A"}; 100 SymbolID B{"B"}; 101 SymbolID C{"C"}; 102 103 RelationSlab::Builder Builder; 104 Builder.insert(Relation{A, RelationKind::BaseOf, B}); 105 Builder.insert(Relation{A, RelationKind::BaseOf, C}); 106 Builder.insert(Relation{A, RelationKind::BaseOf, B}); 107 108 RelationSlab Slab = std::move(Builder).build(); 109 EXPECT_THAT(Slab, UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B}, 110 Relation{A, RelationKind::BaseOf, C})); 111 } 112 113 TEST(SwapIndexTest, OldIndexRecycled) { 114 auto Token = std::make_shared<int>(); 115 std::weak_ptr<int> WeakToken = Token; 116 117 SwapIndex S(std::make_unique<MemIndex>(SymbolSlab(), RefSlab(), 118 RelationSlab(), std::move(Token), 119 /*BackingDataSize=*/0)); 120 EXPECT_FALSE(WeakToken.expired()); // Current MemIndex keeps it alive. 121 S.reset(std::make_unique<MemIndex>()); // Now the MemIndex is destroyed. 122 EXPECT_TRUE(WeakToken.expired()); // So the token is too. 123 } 124 125 TEST(MemIndexTest, MemIndexDeduplicate) { 126 std::vector<Symbol> Symbols = {symbol("1"), symbol("2"), symbol("3"), 127 symbol("2") /* duplicate */}; 128 FuzzyFindRequest Req; 129 Req.Query = "2"; 130 Req.AnyScope = true; 131 MemIndex I(Symbols, RefSlab(), RelationSlab()); 132 EXPECT_THAT(match(I, Req), ElementsAre("2")); 133 } 134 135 TEST(MemIndexTest, MemIndexLimitedNumMatches) { 136 auto I = 137 MemIndex::build(generateNumSymbols(0, 100), RefSlab(), RelationSlab()); 138 FuzzyFindRequest Req; 139 Req.Query = "5"; 140 Req.AnyScope = true; 141 Req.Limit = 3; 142 bool Incomplete; 143 auto Matches = match(*I, Req, &Incomplete); 144 EXPECT_TRUE(Req.Limit); 145 EXPECT_EQ(Matches.size(), *Req.Limit); 146 EXPECT_TRUE(Incomplete); 147 } 148 149 TEST(MemIndexTest, FuzzyMatch) { 150 auto I = MemIndex::build( 151 generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), 152 RefSlab(), RelationSlab()); 153 FuzzyFindRequest Req; 154 Req.Query = "lol"; 155 Req.AnyScope = true; 156 Req.Limit = 2; 157 EXPECT_THAT(match(*I, Req), 158 UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); 159 } 160 161 TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { 162 auto I = MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(), 163 RelationSlab()); 164 FuzzyFindRequest Req; 165 Req.Query = "y"; 166 Req.AnyScope = true; 167 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); 168 } 169 170 TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { 171 auto I = MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(), 172 RelationSlab()); 173 FuzzyFindRequest Req; 174 Req.Query = "y"; 175 Req.Scopes = {""}; 176 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); 177 } 178 179 TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { 180 auto I = MemIndex::build( 181 generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab(), 182 RelationSlab()); 183 FuzzyFindRequest Req; 184 Req.Query = "y"; 185 Req.Scopes = {"a::"}; 186 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); 187 } 188 189 TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { 190 auto I = MemIndex::build( 191 generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab(), 192 RelationSlab()); 193 FuzzyFindRequest Req; 194 Req.Query = "y"; 195 Req.Scopes = {"a::", "b::"}; 196 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); 197 } 198 199 TEST(MemIndexTest, NoMatchNestedScopes) { 200 auto I = MemIndex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab(), 201 RelationSlab()); 202 FuzzyFindRequest Req; 203 Req.Query = "y"; 204 Req.Scopes = {"a::"}; 205 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); 206 } 207 208 TEST(MemIndexTest, IgnoreCases) { 209 auto I = MemIndex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab(), 210 RelationSlab()); 211 FuzzyFindRequest Req; 212 Req.Query = "AB"; 213 Req.Scopes = {"ns::"}; 214 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); 215 } 216 217 TEST(MemIndexTest, Lookup) { 218 auto I = MemIndex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab(), 219 RelationSlab()); 220 EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); 221 EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), 222 UnorderedElementsAre("ns::abc", "ns::xyz")); 223 EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), 224 UnorderedElementsAre("ns::xyz")); 225 EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); 226 } 227 228 TEST(MemIndexTest, IndexedFiles) { 229 SymbolSlab Symbols; 230 RefSlab Refs; 231 auto Size = Symbols.bytes() + Refs.bytes(); 232 auto Data = std::make_pair(std::move(Symbols), std::move(Refs)); 233 llvm::StringSet<> Files = {"unittest:///foo.cc", "unittest:///bar.cc"}; 234 MemIndex I(std::move(Data.first), std::move(Data.second), RelationSlab(), 235 std::move(Files), IndexContents::All, std::move(Data), Size); 236 auto ContainsFile = I.indexedFiles(); 237 EXPECT_EQ(ContainsFile("unittest:///foo.cc"), IndexContents::All); 238 EXPECT_EQ(ContainsFile("unittest:///bar.cc"), IndexContents::All); 239 EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None); 240 } 241 242 TEST(MemIndexTest, TemplateSpecialization) { 243 SymbolSlab::Builder B; 244 245 Symbol S = symbol("TempSpec"); 246 S.ID = SymbolID("1"); 247 B.insert(S); 248 249 S = symbol("TempSpec"); 250 S.ID = SymbolID("2"); 251 S.TemplateSpecializationArgs = "<int, bool>"; 252 S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( 253 index::SymbolProperty::TemplateSpecialization); 254 B.insert(S); 255 256 S = symbol("TempSpec"); 257 S.ID = SymbolID("3"); 258 S.TemplateSpecializationArgs = "<int, U>"; 259 S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( 260 index::SymbolProperty::TemplatePartialSpecialization); 261 B.insert(S); 262 263 auto I = MemIndex::build(std::move(B).build(), RefSlab(), RelationSlab()); 264 FuzzyFindRequest Req; 265 Req.AnyScope = true; 266 267 Req.Query = "TempSpec"; 268 EXPECT_THAT(match(*I, Req), 269 UnorderedElementsAre("TempSpec", "TempSpec<int, bool>", 270 "TempSpec<int, U>")); 271 272 // FIXME: Add filtering for template argument list. 273 Req.Query = "TempSpec<int"; 274 EXPECT_THAT(match(*I, Req), IsEmpty()); 275 } 276 277 TEST(MergeIndexTest, Lookup) { 278 auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab(), 279 RelationSlab()), 280 J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab(), 281 RelationSlab()); 282 MergedIndex M(I.get(), J.get()); 283 EXPECT_THAT(lookup(M, SymbolID("ns::A")), UnorderedElementsAre("ns::A")); 284 EXPECT_THAT(lookup(M, SymbolID("ns::B")), UnorderedElementsAre("ns::B")); 285 EXPECT_THAT(lookup(M, SymbolID("ns::C")), UnorderedElementsAre("ns::C")); 286 EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::B")}), 287 UnorderedElementsAre("ns::A", "ns::B")); 288 EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::C")}), 289 UnorderedElementsAre("ns::A", "ns::C")); 290 EXPECT_THAT(lookup(M, SymbolID("ns::D")), UnorderedElementsAre()); 291 EXPECT_THAT(lookup(M, {}), UnorderedElementsAre()); 292 } 293 294 TEST(MergeIndexTest, LookupRemovedDefinition) { 295 FileIndex DynamicIndex(true), StaticIndex(true); 296 MergedIndex Merge(&DynamicIndex, &StaticIndex); 297 298 const char *HeaderCode = "class Foo;"; 299 auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols(); 300 auto Foo = findSymbol(HeaderSymbols, "Foo"); 301 302 // Build static index for test.cc with Foo definition 303 TestTU Test; 304 Test.HeaderCode = HeaderCode; 305 Test.Code = "class Foo {};"; 306 Test.Filename = "test.cc"; 307 auto AST = Test.build(); 308 StaticIndex.updateMain(testPath(Test.Filename), AST); 309 310 // Remove Foo definition from test.cc, i.e. build dynamic index for test.cc 311 // without Foo definition. 312 Test.Code = "class Foo;"; 313 AST = Test.build(); 314 DynamicIndex.updateMain(testPath(Test.Filename), AST); 315 316 // Even though the definition is actually deleted in the newer version of the 317 // file, we still chose to merge with information coming from static index. 318 // This seems wrong, but is generic behavior we want for e.g. include headers 319 // which are always missing from the dynamic index 320 LookupRequest LookupReq; 321 LookupReq.IDs = {Foo.ID}; 322 unsigned SymbolCounter = 0; 323 Merge.lookup(LookupReq, [&](const Symbol &Sym) { 324 ++SymbolCounter; 325 EXPECT_TRUE(Sym.Definition); 326 }); 327 EXPECT_EQ(SymbolCounter, 1u); 328 329 // Drop the symbol completely. 330 Test.Code = "class Bar {};"; 331 AST = Test.build(); 332 DynamicIndex.updateMain(testPath(Test.Filename), AST); 333 334 // Now we don't expect to see the symbol at all. 335 SymbolCounter = 0; 336 Merge.lookup(LookupReq, [&](const Symbol &Sym) { ++SymbolCounter; }); 337 EXPECT_EQ(SymbolCounter, 0u); 338 } 339 340 TEST(MergeIndexTest, FuzzyFind) { 341 auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab(), 342 RelationSlab()), 343 J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab(), 344 RelationSlab()); 345 FuzzyFindRequest Req; 346 Req.Scopes = {"ns::"}; 347 EXPECT_THAT(match(MergedIndex(I.get(), J.get()), Req), 348 UnorderedElementsAre("ns::A", "ns::B", "ns::C")); 349 } 350 351 TEST(MergeIndexTest, FuzzyFindRemovedSymbol) { 352 FileIndex DynamicIndex(true), StaticIndex(true); 353 MergedIndex Merge(&DynamicIndex, &StaticIndex); 354 355 const char *HeaderCode = "class Foo;"; 356 auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols(); 357 auto Foo = findSymbol(HeaderSymbols, "Foo"); 358 359 // Build static index for test.cc with Foo symbol 360 TestTU Test; 361 Test.HeaderCode = HeaderCode; 362 Test.Code = "class Foo {};"; 363 Test.Filename = "test.cc"; 364 auto AST = Test.build(); 365 StaticIndex.updateMain(testPath(Test.Filename), AST); 366 367 // Remove Foo symbol, i.e. build dynamic index for test.cc, which is empty. 368 Test.HeaderCode = ""; 369 Test.Code = ""; 370 AST = Test.build(); 371 DynamicIndex.updateMain(testPath(Test.Filename), AST); 372 373 // Merged index should not return removed symbol. 374 FuzzyFindRequest Req; 375 Req.AnyScope = true; 376 Req.Query = "Foo"; 377 unsigned SymbolCounter = 0; 378 bool IsIncomplete = 379 Merge.fuzzyFind(Req, [&](const Symbol &) { ++SymbolCounter; }); 380 EXPECT_FALSE(IsIncomplete); 381 EXPECT_EQ(SymbolCounter, 0u); 382 } 383 384 TEST(MergeTest, Merge) { 385 Symbol L, R; 386 L.ID = R.ID = SymbolID("hello"); 387 L.Name = R.Name = "Foo"; // same in both 388 L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs 389 R.CanonicalDeclaration.FileURI = "file:///right.h"; 390 L.References = 1; 391 R.References = 2; 392 L.Signature = "()"; // present in left only 393 R.CompletionSnippetSuffix = "{$1:0}"; // present in right only 394 R.Documentation = "--doc--"; 395 L.Origin = SymbolOrigin::Preamble; 396 R.Origin = SymbolOrigin::Static; 397 R.Type = "expectedType"; 398 399 Symbol M = mergeSymbol(L, R); 400 EXPECT_EQ(M.Name, "Foo"); 401 EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:///left.h"); 402 EXPECT_EQ(M.References, 3u); 403 EXPECT_EQ(M.Signature, "()"); 404 EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}"); 405 EXPECT_EQ(M.Documentation, "--doc--"); 406 EXPECT_EQ(M.Type, "expectedType"); 407 EXPECT_EQ(M.Origin, SymbolOrigin::Preamble | SymbolOrigin::Static | 408 SymbolOrigin::Merge); 409 } 410 411 TEST(MergeTest, PreferSymbolWithDefn) { 412 Symbol L, R; 413 414 L.ID = R.ID = SymbolID("hello"); 415 L.CanonicalDeclaration.FileURI = "file:/left.h"; 416 R.CanonicalDeclaration.FileURI = "file:/right.h"; 417 L.Name = "left"; 418 R.Name = "right"; 419 420 Symbol M = mergeSymbol(L, R); 421 EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/left.h"); 422 EXPECT_EQ(StringRef(M.Definition.FileURI), ""); 423 EXPECT_EQ(M.Name, "left"); 424 425 R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored. 426 M = mergeSymbol(L, R); 427 EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/right.h"); 428 EXPECT_EQ(StringRef(M.Definition.FileURI), "file:/right.cpp"); 429 EXPECT_EQ(M.Name, "right"); 430 } 431 432 TEST(MergeTest, PreferSymbolLocationInCodegenFile) { 433 Symbol L, R; 434 435 L.ID = R.ID = SymbolID("hello"); 436 L.CanonicalDeclaration.FileURI = "file:/x.proto.h"; 437 R.CanonicalDeclaration.FileURI = "file:/x.proto"; 438 439 Symbol M = mergeSymbol(L, R); 440 EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/x.proto"); 441 442 // Prefer L if both have codegen suffix. 443 L.CanonicalDeclaration.FileURI = "file:/y.proto"; 444 M = mergeSymbol(L, R); 445 EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/y.proto"); 446 } 447 448 TEST(MergeIndexTest, Refs) { 449 FileIndex Dyn(true); 450 FileIndex StaticIndex(true); 451 MergedIndex Merge(&Dyn, &StaticIndex); 452 453 const char *HeaderCode = "class Foo;"; 454 auto HeaderSymbols = TestTU::withHeaderCode("class Foo;").headerSymbols(); 455 auto Foo = findSymbol(HeaderSymbols, "Foo"); 456 457 // Build dynamic index for test.cc. 458 Annotations Test1Code(R"(class $Foo[[Foo]];)"); 459 TestTU Test; 460 Test.HeaderCode = HeaderCode; 461 Test.Code = std::string(Test1Code.code()); 462 Test.Filename = "test.cc"; 463 auto AST = Test.build(); 464 Dyn.updateMain(testPath(Test.Filename), AST); 465 466 // Build static index for test.cc. 467 Test.HeaderCode = HeaderCode; 468 Test.Code = "// static\nclass Foo {};"; 469 Test.Filename = "test.cc"; 470 auto StaticAST = Test.build(); 471 // Add stale refs for test.cc. 472 StaticIndex.updateMain(testPath(Test.Filename), StaticAST); 473 474 // Add refs for test2.cc 475 Annotations Test2Code(R"(class $Foo[[Foo]] {};)"); 476 TestTU Test2; 477 Test2.HeaderCode = HeaderCode; 478 Test2.Code = std::string(Test2Code.code()); 479 Test2.Filename = "test2.cc"; 480 StaticAST = Test2.build(); 481 StaticIndex.updateMain(testPath(Test2.Filename), StaticAST); 482 483 RefsRequest Request; 484 Request.IDs = {Foo.ID}; 485 RefSlab::Builder Results; 486 EXPECT_FALSE( 487 Merge.refs(Request, [&](const Ref &O) { Results.insert(Foo.ID, O); })); 488 EXPECT_THAT( 489 std::move(Results).build(), 490 ElementsAre(Pair( 491 _, UnorderedElementsAre(AllOf(refRange(Test1Code.range("Foo")), 492 fileURI("unittest:///test.cc")), 493 AllOf(refRange(Test2Code.range("Foo")), 494 fileURI("unittest:///test2.cc")))))); 495 496 Request.Limit = 1; 497 RefSlab::Builder Results2; 498 EXPECT_TRUE( 499 Merge.refs(Request, [&](const Ref &O) { Results2.insert(Foo.ID, O); })); 500 501 // Remove all refs for test.cc from dynamic index, 502 // merged index should not return results from static index for test.cc. 503 Test.Code = ""; 504 AST = Test.build(); 505 Dyn.updateMain(testPath(Test.Filename), AST); 506 507 Request.Limit = std::nullopt; 508 RefSlab::Builder Results3; 509 EXPECT_FALSE( 510 Merge.refs(Request, [&](const Ref &O) { Results3.insert(Foo.ID, O); })); 511 EXPECT_THAT(std::move(Results3).build(), 512 ElementsAre(Pair(_, UnorderedElementsAre(AllOf( 513 refRange(Test2Code.range("Foo")), 514 fileURI("unittest:///test2.cc")))))); 515 } 516 517 TEST(MergeIndexTest, IndexedFiles) { 518 SymbolSlab DynSymbols; 519 RefSlab DynRefs; 520 auto DynSize = DynSymbols.bytes() + DynRefs.bytes(); 521 auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs)); 522 llvm::StringSet<> DynFiles = {"unittest:///foo.cc"}; 523 MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second), 524 RelationSlab(), std::move(DynFiles), IndexContents::Symbols, 525 std::move(DynData), DynSize); 526 SymbolSlab StaticSymbols; 527 RefSlab StaticRefs; 528 auto StaticData = 529 std::make_pair(std::move(StaticSymbols), std::move(StaticRefs)); 530 llvm::StringSet<> StaticFiles = {"unittest:///foo.cc", "unittest:///bar.cc"}; 531 MemIndex StaticIndex( 532 std::move(StaticData.first), std::move(StaticData.second), RelationSlab(), 533 std::move(StaticFiles), IndexContents::References, std::move(StaticData), 534 StaticSymbols.bytes() + StaticRefs.bytes()); 535 MergedIndex Merge(&DynIndex, &StaticIndex); 536 537 auto ContainsFile = Merge.indexedFiles(); 538 EXPECT_EQ(ContainsFile("unittest:///foo.cc"), 539 IndexContents::Symbols | IndexContents::References); 540 EXPECT_EQ(ContainsFile("unittest:///bar.cc"), IndexContents::References); 541 EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None); 542 } 543 544 TEST(MergeIndexTest, NonDocumentation) { 545 using index::SymbolKind; 546 Symbol L, R; 547 L.ID = R.ID = SymbolID("x"); 548 L.Definition.FileURI = "file:/x.h"; 549 R.Documentation = "Forward declarations because x.h is too big to include"; 550 for (auto ClassLikeKind : 551 {SymbolKind::Class, SymbolKind::Struct, SymbolKind::Union}) { 552 L.SymInfo.Kind = ClassLikeKind; 553 EXPECT_EQ(mergeSymbol(L, R).Documentation, ""); 554 } 555 556 L.SymInfo.Kind = SymbolKind::Function; 557 R.Documentation = "Documentation from non-class symbols should be included"; 558 EXPECT_EQ(mergeSymbol(L, R).Documentation, R.Documentation); 559 } 560 561 MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { 562 return (arg.IncludeHeader == IncludeHeader) && (arg.References == References); 563 } 564 565 TEST(MergeTest, MergeIncludesOnDifferentDefinitions) { 566 Symbol L, R; 567 L.Name = "left"; 568 R.Name = "right"; 569 L.ID = R.ID = SymbolID("hello"); 570 L.IncludeHeaders.emplace_back("common", 1, Symbol::Include); 571 R.IncludeHeaders.emplace_back("common", 1, Symbol::Include); 572 R.IncludeHeaders.emplace_back("new", 1, Symbol::Include); 573 574 // Both have no definition. 575 Symbol M = mergeSymbol(L, R); 576 EXPECT_THAT(M.IncludeHeaders, 577 UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), 578 IncludeHeaderWithRef("new", 1u))); 579 580 // Only merge references of the same includes but do not merge new #includes. 581 L.Definition.FileURI = "file:/left.h"; 582 M = mergeSymbol(L, R); 583 EXPECT_THAT(M.IncludeHeaders, 584 UnorderedElementsAre(IncludeHeaderWithRef("common", 2u))); 585 586 // Definitions are the same. 587 R.Definition.FileURI = "file:/right.h"; 588 M = mergeSymbol(L, R); 589 EXPECT_THAT(M.IncludeHeaders, 590 UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), 591 IncludeHeaderWithRef("new", 1u))); 592 593 // Definitions are different. 594 R.Definition.FileURI = "file:/right.h"; 595 M = mergeSymbol(L, R); 596 EXPECT_THAT(M.IncludeHeaders, 597 UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), 598 IncludeHeaderWithRef("new", 1u))); 599 } 600 601 TEST(MergeIndexTest, IncludeHeadersMerged) { 602 auto S = symbol("Z"); 603 S.Definition.FileURI = "unittest:///foo.cc"; 604 605 SymbolSlab::Builder DynB; 606 S.IncludeHeaders.clear(); 607 DynB.insert(S); 608 SymbolSlab DynSymbols = std::move(DynB).build(); 609 RefSlab DynRefs; 610 auto DynSize = DynSymbols.bytes() + DynRefs.bytes(); 611 auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs)); 612 llvm::StringSet<> DynFiles = {S.Definition.FileURI}; 613 MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second), 614 RelationSlab(), std::move(DynFiles), IndexContents::Symbols, 615 std::move(DynData), DynSize); 616 617 SymbolSlab::Builder StaticB; 618 S.IncludeHeaders.push_back({"<header>", 0, Symbol::Include}); 619 StaticB.insert(S); 620 auto StaticIndex = 621 MemIndex::build(std::move(StaticB).build(), RefSlab(), RelationSlab()); 622 MergedIndex Merge(&DynIndex, StaticIndex.get()); 623 624 EXPECT_THAT(runFuzzyFind(Merge, S.Name), 625 ElementsAre(testing::Field( 626 &Symbol::IncludeHeaders, 627 ElementsAre(IncludeHeaderWithRef("<header>", 0u))))); 628 629 LookupRequest Req; 630 Req.IDs = {S.ID}; 631 std::string IncludeHeader; 632 Merge.lookup(Req, [&](const Symbol &S) { 633 EXPECT_TRUE(IncludeHeader.empty()); 634 ASSERT_EQ(S.IncludeHeaders.size(), 1u); 635 IncludeHeader = S.IncludeHeaders.front().IncludeHeader.str(); 636 }); 637 EXPECT_EQ(IncludeHeader, "<header>"); 638 } 639 } // namespace 640 } // namespace clangd 641 } // namespace clang 642