1 | //===-- FileIndexTests.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 "Compiler.h" |
11 | #include "Headers.h" |
12 | #include "ParsedAST.h" |
13 | #include "SyncAPI.h" |
14 | #include "TestFS.h" |
15 | #include "TestTU.h" |
16 | #include "TestWorkspace.h" |
17 | #include "URI.h" |
18 | #include "clang-include-cleaner/Record.h" |
19 | #include "index/FileIndex.h" |
20 | #include "index/Index.h" |
21 | #include "index/Ref.h" |
22 | #include "index/Relation.h" |
23 | #include "index/Serialization.h" |
24 | #include "index/Symbol.h" |
25 | #include "index/SymbolID.h" |
26 | #include "support/Threading.h" |
27 | #include "clang/Frontend/CompilerInvocation.h" |
28 | #include "clang/Tooling/CompilationDatabase.h" |
29 | #include "llvm/ADT/ArrayRef.h" |
30 | #include "llvm/Support/Allocator.h" |
31 | #include "gmock/gmock.h" |
32 | #include "gtest/gtest.h" |
33 | #include <memory> |
34 | #include <utility> |
35 | #include <vector> |
36 | |
37 | using ::testing::_; |
38 | using ::testing::AllOf; |
39 | using ::testing::Contains; |
40 | using ::testing::ElementsAre; |
41 | using ::testing::Gt; |
42 | using ::testing::IsEmpty; |
43 | using ::testing::Pair; |
44 | using ::testing::UnorderedElementsAre; |
45 | |
46 | MATCHER_P(refRange, Range, "" ) { |
47 | return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), |
48 | arg.Location.End.line(), arg.Location.End.column()) == |
49 | std::make_tuple(Range.start.line, Range.start.character, |
50 | Range.end.line, Range.end.character); |
51 | } |
52 | MATCHER_P(fileURI, F, "" ) { return llvm::StringRef(arg.Location.FileURI) == F; } |
53 | MATCHER_P(declURI, U, "" ) { |
54 | return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U; |
55 | } |
56 | MATCHER_P(defURI, U, "" ) { |
57 | return llvm::StringRef(arg.Definition.FileURI) == U; |
58 | } |
59 | MATCHER_P(qName, N, "" ) { return (arg.Scope + arg.Name).str() == N; } |
60 | MATCHER_P(numReferences, N, "" ) { return arg.References == N; } |
61 | MATCHER_P(hasOrign, O, "" ) { return bool(arg.Origin & O); } |
62 | |
63 | MATCHER_P(, , "" ) { |
64 | return (arg.IncludeHeaders.size() == 1) && |
65 | (arg.IncludeHeaders.begin()->IncludeHeader == P); |
66 | } |
67 | |
68 | namespace clang { |
69 | namespace clangd { |
70 | namespace { |
71 | ::testing::Matcher<const RefSlab &> |
72 | refsAre(std::vector<::testing::Matcher<Ref>> Matchers) { |
73 | return ElementsAre(matchers: ::testing::Pair(first_matcher: _, second_matcher: UnorderedElementsAreArray(container: Matchers))); |
74 | } |
75 | |
76 | Symbol symbol(llvm::StringRef ID) { |
77 | Symbol Sym; |
78 | Sym.ID = SymbolID(ID); |
79 | Sym.Name = ID; |
80 | return Sym; |
81 | } |
82 | |
83 | std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) { |
84 | SymbolSlab::Builder Slab; |
85 | for (int I = Begin; I <= End; I++) |
86 | Slab.insert(S: symbol(ID: std::to_string(val: I))); |
87 | return std::make_unique<SymbolSlab>(args: std::move(Slab).build()); |
88 | } |
89 | |
90 | std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) { |
91 | RefSlab::Builder Slab; |
92 | Ref R; |
93 | R.Location.FileURI = Path; |
94 | R.Kind = RefKind::Reference; |
95 | Slab.insert(ID, S: R); |
96 | return std::make_unique<RefSlab>(args: std::move(Slab).build()); |
97 | } |
98 | |
99 | std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) { |
100 | RelationSlab::Builder RelBuilder; |
101 | for (auto &Rel : Rels) |
102 | RelBuilder.insert(R: Rel); |
103 | return std::make_unique<RelationSlab>(args: std::move(RelBuilder).build()); |
104 | } |
105 | |
106 | TEST(FileSymbolsTest, UpdateAndGet) { |
107 | FileSymbols FS(IndexContents::All); |
108 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), "" ), IsEmpty()); |
109 | |
110 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.cc" ), Relations: nullptr, |
111 | CountReferences: false); |
112 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), "" ), |
113 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
114 | EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1" )), |
115 | refsAre({fileURI("f1.cc" )})); |
116 | } |
117 | |
118 | TEST(FileSymbolsTest, Overlap) { |
119 | FileSymbols FS(IndexContents::All); |
120 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: false); |
121 | FS.update(Key: "f2" , Symbols: numSlab(Begin: 3, End: 5), Refs: nullptr, Relations: nullptr, CountReferences: false); |
122 | for (auto Type : {IndexType::Light, IndexType::Heavy}) |
123 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), "" ), |
124 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ), |
125 | qName("4" ), qName("5" ))); |
126 | } |
127 | |
128 | TEST(FileSymbolsTest, MergeOverlap) { |
129 | FileSymbols FS(IndexContents::All); |
130 | auto OneSymboSlab = [](Symbol Sym) { |
131 | SymbolSlab::Builder S; |
132 | S.insert(S: Sym); |
133 | return std::make_unique<SymbolSlab>(args: std::move(S).build()); |
134 | }; |
135 | auto X1 = symbol(ID: "x" ); |
136 | X1.CanonicalDeclaration.FileURI = "file:///x1" ; |
137 | auto X2 = symbol(ID: "x" ); |
138 | X2.Definition.FileURI = "file:///x2" ; |
139 | |
140 | FS.update(Key: "f1" , Symbols: OneSymboSlab(X1), Refs: nullptr, Relations: nullptr, CountReferences: false); |
141 | FS.update(Key: "f2" , Symbols: OneSymboSlab(X2), Refs: nullptr, Relations: nullptr, CountReferences: false); |
142 | for (auto Type : {IndexType::Light, IndexType::Heavy}) |
143 | EXPECT_THAT( |
144 | runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x" ), |
145 | UnorderedElementsAre( |
146 | AllOf(qName("x" ), declURI("file:///x1" ), defURI("file:///x2" )))); |
147 | } |
148 | |
149 | TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { |
150 | FileSymbols FS(IndexContents::All); |
151 | |
152 | SymbolID ID("1" ); |
153 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID, Path: "f1.cc" ), Relations: nullptr, CountReferences: false); |
154 | |
155 | auto Symbols = FS.buildIndex(IndexType::Light); |
156 | EXPECT_THAT(runFuzzyFind(*Symbols, "" ), |
157 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
158 | EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc" )})); |
159 | |
160 | FS.update(Key: "f1" , Symbols: nullptr, Refs: nullptr, Relations: nullptr, CountReferences: false); |
161 | auto Empty = FS.buildIndex(IndexType::Light); |
162 | EXPECT_THAT(runFuzzyFind(*Empty, "" ), IsEmpty()); |
163 | EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); |
164 | |
165 | EXPECT_THAT(runFuzzyFind(*Symbols, "" ), |
166 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
167 | EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc" )})); |
168 | } |
169 | |
170 | // Adds Basename.cpp, which includes Basename.h, which contains Code. |
171 | void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) { |
172 | TestTU File; |
173 | File.Filename = (Basename + ".cpp" ).str(); |
174 | File.HeaderFilename = (Basename + ".h" ).str(); |
175 | File.HeaderCode = std::string(Code); |
176 | auto AST = File.build(); |
177 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
178 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
179 | PI: AST.getPragmaIncludes()); |
180 | } |
181 | |
182 | TEST(FileIndexTest, CustomizedURIScheme) { |
183 | FileIndex M; |
184 | update(M, Basename: "f" , Code: "class string {};" ); |
185 | |
186 | EXPECT_THAT(runFuzzyFind(M, "" ), ElementsAre(declURI("unittest:///f.h" ))); |
187 | } |
188 | |
189 | TEST(FileIndexTest, IndexAST) { |
190 | FileIndex M; |
191 | update(M, Basename: "f1" , Code: "namespace ns { void f() {} class X {}; }" ); |
192 | |
193 | FuzzyFindRequest Req; |
194 | Req.Query = "" ; |
195 | Req.Scopes = {"ns::" }; |
196 | EXPECT_THAT(runFuzzyFind(M, Req), |
197 | UnorderedElementsAre(qName("ns::f" ), qName("ns::X" ))); |
198 | } |
199 | |
200 | TEST(FileIndexTest, NoLocal) { |
201 | FileIndex M; |
202 | update(M, Basename: "f1" , Code: "namespace ns { void f() { int local = 0; } class X {}; }" ); |
203 | |
204 | EXPECT_THAT( |
205 | runFuzzyFind(M, "" ), |
206 | UnorderedElementsAre(qName("ns" ), qName("ns::f" ), qName("ns::X" ))); |
207 | } |
208 | |
209 | TEST(FileIndexTest, IndexMultiASTAndDeduplicate) { |
210 | FileIndex M; |
211 | update(M, Basename: "f1" , Code: "namespace ns { void f() {} class X {}; }" ); |
212 | update(M, Basename: "f2" , Code: "namespace ns { void ff() {} class X {}; }" ); |
213 | |
214 | FuzzyFindRequest Req; |
215 | Req.Scopes = {"ns::" }; |
216 | EXPECT_THAT( |
217 | runFuzzyFind(M, Req), |
218 | UnorderedElementsAre(qName("ns::f" ), qName("ns::X" ), qName("ns::ff" ))); |
219 | } |
220 | |
221 | TEST(FileIndexTest, ClassMembers) { |
222 | FileIndex M; |
223 | update(M, Basename: "f1" , Code: "class X { static int m1; int m2; static void f(); };" ); |
224 | |
225 | EXPECT_THAT(runFuzzyFind(M, "" ), |
226 | UnorderedElementsAre(qName("X" ), qName("X::m1" ), qName("X::m2" ), |
227 | qName("X::f" ))); |
228 | } |
229 | |
230 | TEST(FileIndexTest, IncludeCollected) { |
231 | FileIndex M; |
232 | update( |
233 | M, Basename: "f" , |
234 | Code: "// IWYU pragma: private, include <the/good/header.h>\nclass string {};" ); |
235 | |
236 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
237 | EXPECT_THAT(Symbols, ElementsAre(_)); |
238 | EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, |
239 | "<the/good/header.h>" ); |
240 | } |
241 | |
242 | TEST(FileIndexTest, IWYUPragmaExport) { |
243 | FileIndex M; |
244 | |
245 | TestTU File; |
246 | File.Code = R"cpp(#pragma once |
247 | #include "exporter.h" |
248 | )cpp" ; |
249 | File.HeaderFilename = "exporter.h" ; |
250 | File.HeaderCode = R"cpp(#pragma once |
251 | #include "private.h" // IWYU pragma: export |
252 | )cpp" ; |
253 | File.AdditionalFiles["private.h" ] = "class Foo{};" ; |
254 | auto AST = File.build(); |
255 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
256 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
257 | PI: AST.getPragmaIncludes()); |
258 | |
259 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
260 | EXPECT_THAT( |
261 | Symbols, |
262 | UnorderedElementsAre(AllOf( |
263 | qName("Foo" ), |
264 | includeHeader(URI::create(testPath(File.HeaderFilename)).toString()), |
265 | declURI(URI::create(testPath("private.h" )).toString())))); |
266 | } |
267 | |
268 | TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) { |
269 | TestTU TU; |
270 | TU.HeaderCode = "class Foo{};" ; |
271 | TU.HeaderFilename = "algorithm" ; |
272 | |
273 | auto Symbols = runFuzzyFind(Index: *TU.index(), Query: "" ); |
274 | EXPECT_THAT(Symbols, ElementsAre(_)); |
275 | EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, |
276 | "<algorithm>" ); |
277 | } |
278 | |
279 | TEST(FileIndexTest, TemplateParamsInLabel) { |
280 | auto *Source = R"cpp( |
281 | template <class Ty> |
282 | class vector { |
283 | }; |
284 | |
285 | template <class Ty, class Arg> |
286 | vector<Ty> make_vector(Arg A) {} |
287 | )cpp" ; |
288 | |
289 | FileIndex M; |
290 | update(M, Basename: "f" , Code: Source); |
291 | |
292 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
293 | EXPECT_THAT(Symbols, |
294 | UnorderedElementsAre(qName("vector" ), qName("make_vector" ))); |
295 | auto It = Symbols.begin(); |
296 | Symbol Vector = *It++; |
297 | Symbol MakeVector = *It++; |
298 | if (MakeVector.Name == "vector" ) |
299 | std::swap(a&: MakeVector, b&: Vector); |
300 | |
301 | EXPECT_EQ(Vector.Signature, "<class Ty>" ); |
302 | EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>" ); |
303 | |
304 | EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)" ); |
305 | EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})" ); |
306 | } |
307 | |
308 | TEST(FileIndexTest, RebuildWithPreamble) { |
309 | auto FooCpp = testPath(File: "foo.cpp" ); |
310 | auto FooH = testPath(File: "foo.h" ); |
311 | // Preparse ParseInputs. |
312 | ParseInputs PI; |
313 | PI.CompileCommand.Directory = testRoot(); |
314 | PI.CompileCommand.Filename = FooCpp; |
315 | PI.CompileCommand.CommandLine = {"clang" , "-xc++" , FooCpp}; |
316 | |
317 | MockFS FS; |
318 | FS.Files[FooCpp] = "" ; |
319 | FS.Files[FooH] = R"cpp( |
320 | namespace ns_in_header { |
321 | int func_in_header(); |
322 | } |
323 | )cpp" ; |
324 | PI.TFS = &FS; |
325 | |
326 | PI.Contents = R"cpp( |
327 | #include "foo.h" |
328 | namespace ns_in_source { |
329 | int func_in_source(); |
330 | } |
331 | )cpp" ; |
332 | |
333 | // Rebuild the file. |
334 | IgnoreDiagnostics IgnoreDiags; |
335 | auto CI = buildCompilerInvocation(Inputs: PI, D&: IgnoreDiags); |
336 | |
337 | FileIndex Index; |
338 | bool IndexUpdated = false; |
339 | buildPreamble( |
340 | FileName: FooCpp, CI: *CI, Inputs: PI, |
341 | /*StoreInMemory=*/true, |
342 | PreambleCallback: [&](CapturedASTCtx ASTCtx, |
343 | std::shared_ptr<const include_cleaner::PragmaIncludes> PI) { |
344 | auto &Ctx = ASTCtx.getASTContext(); |
345 | auto &PP = ASTCtx.getPreprocessor(); |
346 | EXPECT_FALSE(IndexUpdated) << "Expected only a single index update" ; |
347 | IndexUpdated = true; |
348 | Index.updatePreamble(Path: FooCpp, /*Version=*/"null" , AST&: Ctx, PP, PI: *PI); |
349 | }); |
350 | ASSERT_TRUE(IndexUpdated); |
351 | |
352 | // Check the index contains symbols from the preamble, but not from the main |
353 | // file. |
354 | FuzzyFindRequest Req; |
355 | Req.Query = "" ; |
356 | Req.Scopes = {"" , "ns_in_header::" }; |
357 | |
358 | EXPECT_THAT(runFuzzyFind(Index, Req), |
359 | UnorderedElementsAre(qName("ns_in_header" ), |
360 | qName("ns_in_header::func_in_header" ))); |
361 | } |
362 | |
363 | TEST(FileIndexTest, Refs) { |
364 | const char * = "class Foo {};" ; |
365 | Annotations MainCode(R"cpp( |
366 | void f() { |
367 | $foo[[Foo]] foo; |
368 | } |
369 | )cpp" ); |
370 | |
371 | auto Foo = |
372 | findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), QName: "Foo" ); |
373 | |
374 | RefsRequest Request; |
375 | Request.IDs = {Foo.ID}; |
376 | |
377 | FileIndex Index; |
378 | // Add test.cc |
379 | TestTU Test; |
380 | Test.HeaderCode = HeaderCode; |
381 | Test.Code = std::string(MainCode.code()); |
382 | Test.Filename = "test.cc" ; |
383 | auto AST = Test.build(); |
384 | Index.updateMain(Path: testPath(File: Test.Filename), AST); |
385 | // Add test2.cc |
386 | TestTU Test2; |
387 | Test2.HeaderCode = HeaderCode; |
388 | Test2.Code = std::string(MainCode.code()); |
389 | Test2.Filename = "test2.cc" ; |
390 | AST = Test2.build(); |
391 | Index.updateMain(Path: testPath(File: Test2.Filename), AST); |
392 | |
393 | EXPECT_THAT(getRefs(Index, Foo.ID), |
394 | refsAre({AllOf(refRange(MainCode.range("foo" )), |
395 | fileURI("unittest:///test.cc" )), |
396 | AllOf(refRange(MainCode.range("foo" )), |
397 | fileURI("unittest:///test2.cc" ))})); |
398 | } |
399 | |
400 | TEST(FileIndexTest, MacroRefs) { |
401 | Annotations (R"cpp( |
402 | #define $def1[[HEADER_MACRO]](X) (X+1) |
403 | )cpp" ); |
404 | Annotations MainCode(R"cpp( |
405 | #define $def2[[MAINFILE_MACRO]](X) (X+1) |
406 | void f() { |
407 | int a = $ref1[[HEADER_MACRO]](2); |
408 | int b = $ref2[[MAINFILE_MACRO]](1); |
409 | } |
410 | )cpp" ); |
411 | |
412 | FileIndex Index; |
413 | // Add test.cc |
414 | TestTU Test; |
415 | Test.HeaderCode = std::string(HeaderCode.code()); |
416 | Test.Code = std::string(MainCode.code()); |
417 | Test.Filename = "test.cc" ; |
418 | auto AST = Test.build(); |
419 | Index.updateMain(Path: testPath(File: Test.Filename), AST); |
420 | |
421 | auto = findSymbol(Test.headerSymbols(), QName: "HEADER_MACRO" ); |
422 | EXPECT_THAT(getRefs(Index, HeaderMacro.ID), |
423 | refsAre({AllOf(refRange(MainCode.range("ref1" )), |
424 | fileURI("unittest:///test.cc" ))})); |
425 | |
426 | auto MainFileMacro = findSymbol(Test.headerSymbols(), QName: "MAINFILE_MACRO" ); |
427 | EXPECT_THAT(getRefs(Index, MainFileMacro.ID), |
428 | refsAre({AllOf(refRange(MainCode.range("def2" )), |
429 | fileURI("unittest:///test.cc" )), |
430 | AllOf(refRange(MainCode.range("ref2" )), |
431 | fileURI("unittest:///test.cc" ))})); |
432 | } |
433 | |
434 | TEST(FileIndexTest, CollectMacros) { |
435 | FileIndex M; |
436 | update(M, Basename: "f" , Code: "#define CLANGD 1" ); |
437 | EXPECT_THAT(runFuzzyFind(M, "" ), Contains(qName("CLANGD" ))); |
438 | } |
439 | |
440 | TEST(FileIndexTest, Relations) { |
441 | TestTU TU; |
442 | TU.Filename = "f.cpp" ; |
443 | TU.HeaderFilename = "f.h" ; |
444 | TU.HeaderCode = "class A {}; class B : public A {};" ; |
445 | auto AST = TU.build(); |
446 | FileIndex Index; |
447 | Index.updatePreamble(Path: testPath(File: TU.Filename), /*Version=*/"null" , |
448 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
449 | PI: AST.getPragmaIncludes()); |
450 | SymbolID A = findSymbol(TU.headerSymbols(), QName: "A" ).ID; |
451 | uint32_t Results = 0; |
452 | RelationsRequest Req; |
453 | Req.Subjects.insert(V: A); |
454 | Req.Predicate = RelationKind::BaseOf; |
455 | Index.relations(Req, [&](const SymbolID &, const Symbol &) { ++Results; }); |
456 | EXPECT_EQ(Results, 1u); |
457 | } |
458 | |
459 | TEST(FileIndexTest, RelationsMultiFile) { |
460 | TestWorkspace Workspace; |
461 | Workspace.addSource(Filename: "Base.h" , Code: "class Base {};" ); |
462 | Workspace.addMainFile(Filename: "A.cpp" , Code: R"cpp( |
463 | #include "Base.h" |
464 | class A : public Base {}; |
465 | )cpp" ); |
466 | Workspace.addMainFile(Filename: "B.cpp" , Code: R"cpp( |
467 | #include "Base.h" |
468 | class B : public Base {}; |
469 | )cpp" ); |
470 | |
471 | auto Index = Workspace.index(); |
472 | FuzzyFindRequest FFReq; |
473 | FFReq.Query = "Base" ; |
474 | FFReq.AnyScope = true; |
475 | SymbolID Base; |
476 | Index->fuzzyFind(Req: FFReq, Callback: [&](const Symbol &S) { Base = S.ID; }); |
477 | |
478 | RelationsRequest Req; |
479 | Req.Subjects.insert(V: Base); |
480 | Req.Predicate = RelationKind::BaseOf; |
481 | uint32_t Results = 0; |
482 | Index->relations(Req, Callback: [&](const SymbolID &, const Symbol &) { ++Results; }); |
483 | EXPECT_EQ(Results, 2u); |
484 | } |
485 | |
486 | TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { |
487 | TestTU TU; |
488 | TU.HeaderCode = "class Foo{};" ; |
489 | Annotations Main(R"cpp( |
490 | void f() { |
491 | [[Foo]] foo; |
492 | } |
493 | )cpp" ); |
494 | TU.Code = std::string(Main.code()); |
495 | auto AST = TU.build(); |
496 | FileIndex Index; |
497 | Index.updateMain(Path: testPath(File: TU.Filename), AST); |
498 | |
499 | // Expect to see references in main file, references in headers are excluded |
500 | // because we only index main AST. |
501 | EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo" ).ID), |
502 | refsAre({refRange(Main.range())})); |
503 | } |
504 | |
505 | TEST(FileIndexTest, MergeMainFileSymbols) { |
506 | const char * = "void foo();" ; |
507 | TestTU = TestTU::withCode(Code: CommonHeader); |
508 | TestTU Cpp = TestTU::withCode(Code: "void foo() {}" ); |
509 | Cpp.Filename = "foo.cpp" ; |
510 | Cpp.HeaderFilename = "foo.h" ; |
511 | Cpp.HeaderCode = CommonHeader; |
512 | |
513 | FileIndex Index; |
514 | auto = Header.build(); |
515 | auto CppAST = Cpp.build(); |
516 | Index.updateMain(Path: testPath(File: "foo.h" ), AST&: HeaderAST); |
517 | Index.updateMain(Path: testPath(File: "foo.cpp" ), AST&: CppAST); |
518 | |
519 | auto Symbols = runFuzzyFind(Index, Query: "" ); |
520 | // Check foo is merged, foo in Cpp wins (as we see the definition there). |
521 | EXPECT_THAT(Symbols, ElementsAre(AllOf(declURI("unittest:///foo.h" ), |
522 | defURI("unittest:///foo.cpp" ), |
523 | hasOrign(SymbolOrigin::Merge)))); |
524 | } |
525 | |
526 | TEST(FileSymbolsTest, CountReferencesNoRefSlabs) { |
527 | FileSymbols FS(IndexContents::All); |
528 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: true); |
529 | FS.update(Key: "f2" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: false); |
530 | EXPECT_THAT( |
531 | runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), |
532 | "" ), |
533 | UnorderedElementsAre(AllOf(qName("1" ), numReferences(0u)), |
534 | AllOf(qName("2" ), numReferences(0u)), |
535 | AllOf(qName("3" ), numReferences(0u)))); |
536 | } |
537 | |
538 | TEST(FileSymbolsTest, CountReferencesWithRefSlabs) { |
539 | FileSymbols FS(IndexContents::All); |
540 | FS.update(Key: "f1cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.cpp" ), Relations: nullptr, |
541 | CountReferences: true); |
542 | FS.update(Key: "f1h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.h" ), Relations: nullptr, |
543 | CountReferences: false); |
544 | FS.update(Key: "f2cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("2" ), Path: "f2.cpp" ), Relations: nullptr, |
545 | CountReferences: true); |
546 | FS.update(Key: "f2h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("2" ), Path: "f2.h" ), Relations: nullptr, |
547 | CountReferences: false); |
548 | FS.update(Key: "f3cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("3" ), Path: "f3.cpp" ), Relations: nullptr, |
549 | CountReferences: true); |
550 | FS.update(Key: "f3h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("3" ), Path: "f3.h" ), Relations: nullptr, |
551 | CountReferences: false); |
552 | EXPECT_THAT( |
553 | runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), |
554 | "" ), |
555 | UnorderedElementsAre(AllOf(qName("1" ), numReferences(1u)), |
556 | AllOf(qName("2" ), numReferences(1u)), |
557 | AllOf(qName("3" ), numReferences(1u)))); |
558 | } |
559 | |
560 | TEST(FileIndexTest, StalePreambleSymbolsDeleted) { |
561 | FileIndex M; |
562 | TestTU File; |
563 | File.HeaderFilename = "a.h" ; |
564 | |
565 | File.Filename = "f1.cpp" ; |
566 | File.HeaderCode = "int a;" ; |
567 | auto AST = File.build(); |
568 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
569 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
570 | PI: AST.getPragmaIncludes()); |
571 | EXPECT_THAT(runFuzzyFind(M, "" ), UnorderedElementsAre(qName("a" ))); |
572 | |
573 | File.Filename = "f2.cpp" ; |
574 | File.HeaderCode = "int b;" ; |
575 | AST = File.build(); |
576 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
577 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
578 | PI: AST.getPragmaIncludes()); |
579 | EXPECT_THAT(runFuzzyFind(M, "" ), UnorderedElementsAre(qName("b" ))); |
580 | } |
581 | |
582 | // Verifies that concurrent calls to updateMain don't "lose" any updates. |
583 | TEST(FileIndexTest, Threadsafety) { |
584 | FileIndex M; |
585 | Notification Go; |
586 | |
587 | constexpr int Count = 10; |
588 | { |
589 | // Set up workers to concurrently call updateMain() with separate files. |
590 | AsyncTaskRunner Pool; |
591 | for (unsigned I = 0; I < Count; ++I) { |
592 | auto TU = TestTU::withCode(Code: llvm::formatv(Fmt: "int xxx{0};" , Vals&: I).str()); |
593 | TU.Filename = llvm::formatv(Fmt: "x{0}.c" , Vals&: I).str(); |
594 | Pool.runAsync(Name: TU.Filename, Action: [&, Filename(testPath(File: TU.Filename)), |
595 | AST(TU.build())]() mutable { |
596 | Go.wait(); |
597 | M.updateMain(Path: Filename, AST); |
598 | }); |
599 | } |
600 | // On your marks, get set... |
601 | Go.notify(); |
602 | } |
603 | |
604 | EXPECT_THAT(runFuzzyFind(M, "xxx" ), ::testing::SizeIs(Count)); |
605 | } |
606 | |
607 | TEST(FileShardedIndexTest, Sharding) { |
608 | auto = URI::create(AbsolutePath: testPath(File: "a.h" )).toString(); |
609 | auto = URI::create(AbsolutePath: testPath(File: "b.h" )).toString(); |
610 | auto BSourceUri = URI::create(AbsolutePath: testPath(File: "b.cc" )).toString(); |
611 | |
612 | auto Sym1 = symbol(ID: "1" ); |
613 | Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str(); |
614 | |
615 | auto Sym2 = symbol(ID: "2" ); |
616 | Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str(); |
617 | Sym2.Definition.FileURI = BSourceUri.c_str(); |
618 | |
619 | auto Sym3 = symbol(ID: "3" ); // not stored |
620 | |
621 | IndexFileIn IF; |
622 | { |
623 | SymbolSlab::Builder B; |
624 | // Should be stored in only a.h |
625 | B.insert(S: Sym1); |
626 | // Should be stored in both b.h and b.cc |
627 | B.insert(S: Sym2); |
628 | IF.Symbols.emplace(args: std::move(B).build()); |
629 | } |
630 | { |
631 | // Should be stored in b.cc |
632 | IF.Refs.emplace(args: std::move(*refSlab(ID: Sym1.ID, Path: BSourceUri.c_str()))); |
633 | } |
634 | { |
635 | RelationSlab::Builder B; |
636 | // Should be stored in a.h and b.h |
637 | B.insert(R: Relation{.Subject: Sym1.ID, .Predicate: RelationKind::BaseOf, .Object: Sym2.ID}); |
638 | // Should be stored in a.h and b.h |
639 | B.insert(R: Relation{.Subject: Sym2.ID, .Predicate: RelationKind::BaseOf, .Object: Sym1.ID}); |
640 | // Should be stored in a.h (where Sym1 is stored) even though |
641 | // the relation is dangling as Sym3 is unknown. |
642 | B.insert(R: Relation{.Subject: Sym3.ID, .Predicate: RelationKind::BaseOf, .Object: Sym1.ID}); |
643 | IF.Relations.emplace(args: std::move(B).build()); |
644 | } |
645 | |
646 | IF.Sources.emplace(); |
647 | IncludeGraph &IG = *IF.Sources; |
648 | { |
649 | // b.cc includes b.h |
650 | auto &Node = IG[BSourceUri]; |
651 | Node.DirectIncludes = {BHeaderUri}; |
652 | Node.URI = BSourceUri; |
653 | } |
654 | { |
655 | // b.h includes a.h |
656 | auto &Node = IG[BHeaderUri]; |
657 | Node.DirectIncludes = {AHeaderUri}; |
658 | Node.URI = BHeaderUri; |
659 | } |
660 | { |
661 | // a.h includes nothing. |
662 | auto &Node = IG[AHeaderUri]; |
663 | Node.DirectIncludes = {}; |
664 | Node.URI = AHeaderUri; |
665 | } |
666 | |
667 | IF.Cmd = tooling::CompileCommand(testRoot(), "b.cc" , {"clang" }, "out" ); |
668 | |
669 | FileShardedIndex ShardedIndex(std::move(IF)); |
670 | ASSERT_THAT(ShardedIndex.getAllSources(), |
671 | UnorderedElementsAre(AHeaderUri, BHeaderUri, BSourceUri)); |
672 | |
673 | { |
674 | auto Shard = ShardedIndex.getShard(Uri: AHeaderUri); |
675 | ASSERT_TRUE(Shard); |
676 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("1" ))); |
677 | EXPECT_THAT(*Shard->Refs, IsEmpty()); |
678 | EXPECT_THAT( |
679 | *Shard->Relations, |
680 | UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, |
681 | Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID}, |
682 | Relation{Sym3.ID, RelationKind::BaseOf, Sym1.ID})); |
683 | ASSERT_THAT(Shard->Sources->keys(), UnorderedElementsAre(AHeaderUri)); |
684 | EXPECT_THAT(Shard->Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty()); |
685 | EXPECT_TRUE(Shard->Cmd); |
686 | } |
687 | { |
688 | auto Shard = ShardedIndex.getShard(Uri: BHeaderUri); |
689 | ASSERT_TRUE(Shard); |
690 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2" ))); |
691 | EXPECT_THAT(*Shard->Refs, IsEmpty()); |
692 | EXPECT_THAT( |
693 | *Shard->Relations, |
694 | UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, |
695 | Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID})); |
696 | ASSERT_THAT(Shard->Sources->keys(), |
697 | UnorderedElementsAre(BHeaderUri, AHeaderUri)); |
698 | EXPECT_THAT(Shard->Sources->lookup(BHeaderUri).DirectIncludes, |
699 | UnorderedElementsAre(AHeaderUri)); |
700 | EXPECT_TRUE(Shard->Cmd); |
701 | } |
702 | { |
703 | auto Shard = ShardedIndex.getShard(Uri: BSourceUri); |
704 | ASSERT_TRUE(Shard); |
705 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2" ))); |
706 | EXPECT_THAT(*Shard->Refs, UnorderedElementsAre(Pair(Sym1.ID, _))); |
707 | EXPECT_THAT(*Shard->Relations, IsEmpty()); |
708 | ASSERT_THAT(Shard->Sources->keys(), |
709 | UnorderedElementsAre(BSourceUri, BHeaderUri)); |
710 | EXPECT_THAT(Shard->Sources->lookup(BSourceUri).DirectIncludes, |
711 | UnorderedElementsAre(BHeaderUri)); |
712 | EXPECT_TRUE(Shard->Cmd); |
713 | } |
714 | } |
715 | |
716 | TEST(FileIndexTest, Profile) { |
717 | FileIndex FI; |
718 | |
719 | auto FileName = testPath(File: "foo.cpp" ); |
720 | auto AST = TestTU::withHeaderCode(HeaderCode: "int a;" ).build(); |
721 | FI.updateMain(Path: FileName, AST); |
722 | FI.updatePreamble(Path: FileName, Version: "v1" , AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
723 | PI: AST.getPragmaIncludes()); |
724 | |
725 | llvm::BumpPtrAllocator Alloc; |
726 | MemoryTree MT(&Alloc); |
727 | FI.profile(MT); |
728 | ASSERT_THAT(MT.children(), |
729 | UnorderedElementsAre(Pair("preamble" , _), Pair("main_file" , _))); |
730 | |
731 | ASSERT_THAT(MT.child("preamble" ).children(), |
732 | UnorderedElementsAre(Pair("index" , _), Pair("slabs" , _))); |
733 | ASSERT_THAT(MT.child("main_file" ).children(), |
734 | UnorderedElementsAre(Pair("index" , _), Pair("slabs" , _))); |
735 | |
736 | ASSERT_THAT(MT.child("preamble" ).child("index" ).total(), Gt(0U)); |
737 | ASSERT_THAT(MT.child("main_file" ).child("index" ).total(), Gt(0U)); |
738 | } |
739 | |
740 | TEST(FileSymbolsTest, Profile) { |
741 | FileSymbols FS(IndexContents::All); |
742 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 2), Refs: nullptr, Relations: nullptr, CountReferences: false); |
743 | FS.update(Key: "f2" , Symbols: nullptr, Refs: refSlab(ID: SymbolID("1" ), Path: "f1" ), Relations: nullptr, CountReferences: false); |
744 | FS.update(Key: "f3" , Symbols: nullptr, Refs: nullptr, |
745 | Relations: relSlab(Rels: {{.Subject: SymbolID("1" ), .Predicate: RelationKind::BaseOf, .Object: SymbolID("2" )}}), |
746 | CountReferences: false); |
747 | llvm::BumpPtrAllocator Alloc; |
748 | MemoryTree MT(&Alloc); |
749 | FS.profile(MT); |
750 | ASSERT_THAT(MT.children(), UnorderedElementsAre(Pair("f1" , _), Pair("f2" , _), |
751 | Pair("f3" , _))); |
752 | EXPECT_THAT(MT.child("f1" ).children(), ElementsAre(Pair("symbols" , _))); |
753 | EXPECT_THAT(MT.child("f1" ).total(), Gt(0U)); |
754 | EXPECT_THAT(MT.child("f2" ).children(), ElementsAre(Pair("references" , _))); |
755 | EXPECT_THAT(MT.child("f2" ).total(), Gt(0U)); |
756 | EXPECT_THAT(MT.child("f3" ).children(), ElementsAre(Pair("relations" , _))); |
757 | EXPECT_THAT(MT.child("f3" ).total(), Gt(0U)); |
758 | } |
759 | |
760 | TEST(FileIndexTest, MacrosFromMainFile) { |
761 | FileIndex Idx; |
762 | TestTU TU; |
763 | TU.Code = "#pragma once\n#define FOO" ; |
764 | TU.Filename = "foo.h" ; |
765 | auto AST = TU.build(); |
766 | Idx.updateMain(Path: testPath(File: TU.Filename), AST); |
767 | |
768 | auto Slab = runFuzzyFind(Index: Idx, Query: "" ); |
769 | auto &FooSymbol = findSymbol(Slab, QName: "FOO" ); |
770 | EXPECT_TRUE(FooSymbol.Flags & Symbol::IndexedForCodeCompletion); |
771 | } |
772 | |
773 | } // namespace |
774 | } // namespace clangd |
775 | } // namespace clang |
776 | |