xref: /llvm-project/clang-tools-extra/clangd/unittests/IndexTests.cpp (revision 61fe67a4017375fd675f75652e857e837f77fa51)
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