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
23using ::testing::_;
24using ::testing::AllOf;
25using ::testing::ElementsAre;
26using ::testing::IsEmpty;
27using ::testing::Pair;
28using ::testing::Pointee;
29using ::testing::UnorderedElementsAre;
30
31namespace clang {
32namespace clangd {
33namespace {
34
35MATCHER_P(named, N, "") { return arg.Name == N; }
36MATCHER_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}
42MATCHER_P(fileURI, F, "") { return StringRef(arg.Location.FileURI) == F; }
43
44TEST(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
64TEST(SymbolSlab, FindAndIterate) {
65 SymbolSlab::Builder B;
66 B.insert(S: symbol(QName: "Z"));
67 B.insert(S: symbol(QName: "Y"));
68 B.insert(S: symbol(QName: "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
80TEST(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(R: Relation{.Subject: A, .Predicate: RelationKind::BaseOf, .Object: B});
88 Builder.insert(R: Relation{.Subject: A, .Predicate: RelationKind::BaseOf, .Object: C});
89 Builder.insert(R: Relation{.Subject: B, .Predicate: RelationKind::BaseOf, .Object: D});
90 Builder.insert(R: Relation{.Subject: C, .Predicate: RelationKind::BaseOf, .Object: 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
98TEST(RelationSlab, Duplicates) {
99 SymbolID A{"A"};
100 SymbolID B{"B"};
101 SymbolID C{"C"};
102
103 RelationSlab::Builder Builder;
104 Builder.insert(R: Relation{.Subject: A, .Predicate: RelationKind::BaseOf, .Object: B});
105 Builder.insert(R: Relation{.Subject: A, .Predicate: RelationKind::BaseOf, .Object: C});
106 Builder.insert(R: Relation{.Subject: A, .Predicate: RelationKind::BaseOf, .Object: 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
113TEST(SwapIndexTest, OldIndexRecycled) {
114 auto Token = std::make_shared<int>();
115 std::weak_ptr<int> WeakToken = Token;
116
117 SwapIndex S(std::make_unique<MemIndex>(args: SymbolSlab(), args: RefSlab(),
118 args: RelationSlab(), args: std::move(Token),
119 /*BackingDataSize=*/args: 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
125TEST(MemIndexTest, MemIndexDeduplicate) {
126 std::vector<Symbol> Symbols = {symbol(QName: "1"), symbol(QName: "2"), symbol(QName: "3"),
127 symbol(QName: "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
135TEST(MemIndexTest, MemIndexLimitedNumMatches) {
136 auto I =
137 MemIndex::build(Symbols: generateNumSymbols(Begin: 0, End: 100), Refs: RefSlab(), Relations: RelationSlab());
138 FuzzyFindRequest Req;
139 Req.Query = "5";
140 Req.AnyScope = true;
141 Req.Limit = 3;
142 bool Incomplete;
143 auto Matches = match(I: *I, Req, Incomplete: &Incomplete);
144 EXPECT_TRUE(Req.Limit);
145 EXPECT_EQ(Matches.size(), *Req.Limit);
146 EXPECT_TRUE(Incomplete);
147}
148
149TEST(MemIndexTest, FuzzyMatch) {
150 auto I = MemIndex::build(
151 Symbols: generateSymbols(QualifiedNames: {"LaughingOutLoud", "LionPopulation", "LittleOldLady"}),
152 Refs: RefSlab(), Relations: 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
161TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) {
162 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"a::y1", "b::y2", "y3"}), Refs: RefSlab(),
163 Relations: 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
170TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) {
171 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"a::y1", "b::y2", "y3"}), Refs: RefSlab(),
172 Relations: RelationSlab());
173 FuzzyFindRequest Req;
174 Req.Query = "y";
175 Req.Scopes = {""};
176 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3"));
177}
178
179TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) {
180 auto I = MemIndex::build(
181 Symbols: generateSymbols(QualifiedNames: {"a::y1", "a::y2", "a::x", "b::y2", "y3"}), Refs: RefSlab(),
182 Relations: 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
189TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) {
190 auto I = MemIndex::build(
191 Symbols: generateSymbols(QualifiedNames: {"a::y1", "a::y2", "a::x", "b::y3", "y3"}), Refs: RefSlab(),
192 Relations: 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
199TEST(MemIndexTest, NoMatchNestedScopes) {
200 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"a::y1", "a::b::y2"}), Refs: RefSlab(),
201 Relations: RelationSlab());
202 FuzzyFindRequest Req;
203 Req.Query = "y";
204 Req.Scopes = {"a::"};
205 EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1"));
206}
207
208TEST(MemIndexTest, IgnoreCases) {
209 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::ABC", "ns::abc"}), Refs: RefSlab(),
210 Relations: 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
217TEST(MemIndexTest, Lookup) {
218 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::abc", "ns::xyz"}), Refs: RefSlab(),
219 Relations: 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
228TEST(MemIndexTest, IndexedFiles) {
229 SymbolSlab Symbols;
230 RefSlab Refs;
231 auto Size = Symbols.bytes() + Refs.bytes();
232 auto Data = std::make_pair(x: std::move(Symbols), y: 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
242TEST(MemIndexTest, TemplateSpecialization) {
243 SymbolSlab::Builder B;
244
245 Symbol S = symbol(QName: "TempSpec");
246 S.ID = SymbolID("1");
247 B.insert(S);
248
249 S = symbol(QName: "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(QName: "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(Symbols: std::move(B).build(), Refs: RefSlab(), Relations: 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
277TEST(MergeIndexTest, Lookup) {
278 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::A", "ns::B"}), Refs: RefSlab(),
279 Relations: RelationSlab()),
280 J = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::B", "ns::C"}), Refs: RefSlab(),
281 Relations: 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
294TEST(MergeIndexTest, LookupRemovedDefinition) {
295 FileIndex DynamicIndex, StaticIndex;
296 MergedIndex Merge(&DynamicIndex, &StaticIndex);
297
298 const char *HeaderCode = "class Foo;";
299 auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols();
300 auto Foo = findSymbol(HeaderSymbols, QName: "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(Path: testPath(File: 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(Path: testPath(File: 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(Path: testPath(File: 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
340TEST(MergeIndexTest, FuzzyFind) {
341 auto I = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::A", "ns::B"}), Refs: RefSlab(),
342 Relations: RelationSlab()),
343 J = MemIndex::build(Symbols: generateSymbols(QualifiedNames: {"ns::B", "ns::C"}), Refs: RefSlab(),
344 Relations: 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
351TEST(MergeIndexTest, FuzzyFindRemovedSymbol) {
352 FileIndex DynamicIndex, StaticIndex;
353 MergedIndex Merge(&DynamicIndex, &StaticIndex);
354
355 const char *HeaderCode = "class Foo;";
356 auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols();
357 auto Foo = findSymbol(HeaderSymbols, QName: "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(Path: testPath(File: 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(Path: testPath(File: 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
384TEST(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
411TEST(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
432TEST(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
448TEST(MergeIndexTest, Refs) {
449 FileIndex Dyn;
450 FileIndex StaticIndex;
451 MergedIndex Merge(&Dyn, &StaticIndex);
452
453 const char *HeaderCode = "class Foo;";
454 auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode: "class Foo;").headerSymbols();
455 auto Foo = findSymbol(HeaderSymbols, QName: "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(Path: testPath(File: 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(Path: testPath(File: Test.Filename), AST&: 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(Path: testPath(File: Test2.Filename), AST&: 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(Path: testPath(File: 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
517TEST(MergeIndexTest, IndexedFiles) {
518 SymbolSlab DynSymbols;
519 RefSlab DynRefs;
520 auto DynSize = DynSymbols.bytes() + DynRefs.bytes();
521 auto DynData = std::make_pair(x: std::move(DynSymbols), y: 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(x: std::move(StaticSymbols), y: 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
544TEST(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
561MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") {
562 return (arg.IncludeHeader == IncludeHeader) && (arg.References == References);
563}
564
565TEST(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(Args: "common", Args: 1, Args: Symbol::Include);
571 R.IncludeHeaders.emplace_back(Args: "common", Args: 1, Args: Symbol::Include);
572 R.IncludeHeaders.emplace_back(Args: "new", Args: 1, Args: 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
601TEST(MergeIndexTest, IncludeHeadersMerged) {
602 auto S = symbol(QName: "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(x: std::move(DynSymbols), y: 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(Elt: {"<header>", 0, Symbol::Include});
619 StaticB.insert(S);
620 auto StaticIndex =
621 MemIndex::build(Symbols: std::move(StaticB).build(), Refs: RefSlab(), Relations: 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

source code of clang-tools-extra/clangd/unittests/IndexTests.cpp