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