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(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 | |
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(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 | |
98 | TEST(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 | |
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>(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 | |
125 | TEST(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 | |
135 | TEST(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 | |
149 | TEST(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 | |
161 | TEST(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 | |
170 | TEST(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 | |
179 | TEST(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 | |
189 | TEST(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 | |
199 | TEST(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 | |
208 | TEST(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 | |
217 | TEST(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 | |
228 | TEST(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 | |
242 | TEST(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 | |
277 | TEST(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 | |
294 | TEST(MergeIndexTest, LookupRemovedDefinition) { |
295 | FileIndex DynamicIndex, StaticIndex; |
296 | MergedIndex Merge(&DynamicIndex, &StaticIndex); |
297 | |
298 | const char * = "class Foo;" ; |
299 | auto = 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 | |
340 | TEST(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 | |
351 | TEST(MergeIndexTest, FuzzyFindRemovedSymbol) { |
352 | FileIndex DynamicIndex, StaticIndex; |
353 | MergedIndex Merge(&DynamicIndex, &StaticIndex); |
354 | |
355 | const char * = "class Foo;" ; |
356 | auto = 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 | |
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; |
450 | FileIndex StaticIndex; |
451 | MergedIndex Merge(&Dyn, &StaticIndex); |
452 | |
453 | const char * = "class Foo;" ; |
454 | auto = 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 | |
517 | TEST(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 | |
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(, , , "" ) { |
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(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 | |
601 | TEST(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 ; |
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 | |