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
37using ::testing::_;
38using ::testing::AllOf;
39using ::testing::Contains;
40using ::testing::ElementsAre;
41using ::testing::Gt;
42using ::testing::IsEmpty;
43using ::testing::Pair;
44using ::testing::UnorderedElementsAre;
45
46MATCHER_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}
52MATCHER_P(fileURI, F, "") { return llvm::StringRef(arg.Location.FileURI) == F; }
53MATCHER_P(declURI, U, "") {
54 return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U;
55}
56MATCHER_P(defURI, U, "") {
57 return llvm::StringRef(arg.Definition.FileURI) == U;
58}
59MATCHER_P(qName, N, "") { return (arg.Scope + arg.Name).str() == N; }
60MATCHER_P(numReferences, N, "") { return arg.References == N; }
61MATCHER_P(hasOrign, O, "") { return bool(arg.Origin & O); }
62
63MATCHER_P(includeHeader, P, "") {
64 return (arg.IncludeHeaders.size() == 1) &&
65 (arg.IncludeHeaders.begin()->IncludeHeader == P);
66}
67
68namespace clang {
69namespace clangd {
70namespace {
71::testing::Matcher<const RefSlab &>
72refsAre(std::vector<::testing::Matcher<Ref>> Matchers) {
73 return ElementsAre(matchers: ::testing::Pair(first_matcher: _, second_matcher: UnorderedElementsAreArray(container: Matchers)));
74}
75
76Symbol symbol(llvm::StringRef ID) {
77 Symbol Sym;
78 Sym.ID = SymbolID(ID);
79 Sym.Name = ID;
80 return Sym;
81}
82
83std::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
90std::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
99std::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
106TEST(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
118TEST(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
128TEST(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
149TEST(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.
171void 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
182TEST(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
189TEST(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
200TEST(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
209TEST(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
221TEST(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
230TEST(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
242TEST(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
268TEST(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
279TEST(FileIndexTest, TemplateParamsInLabel) {
280 auto *Source = R"cpp(
281template <class Ty>
282class vector {
283};
284
285template <class Ty, class Arg>
286vector<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
308TEST(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
363TEST(FileIndexTest, Refs) {
364 const char *HeaderCode = "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
400TEST(FileIndexTest, MacroRefs) {
401 Annotations HeaderCode(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 HeaderMacro = 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
434TEST(FileIndexTest, CollectMacros) {
435 FileIndex M;
436 update(M, Basename: "f", Code: "#define CLANGD 1");
437 EXPECT_THAT(runFuzzyFind(M, ""), Contains(qName("CLANGD")));
438}
439
440TEST(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
459TEST(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
486TEST(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
505TEST(FileIndexTest, MergeMainFileSymbols) {
506 const char *CommonHeader = "void foo();";
507 TestTU Header = 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 HeaderAST = 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
526TEST(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
538TEST(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
560TEST(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.
583TEST(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
607TEST(FileShardedIndexTest, Sharding) {
608 auto AHeaderUri = URI::create(AbsolutePath: testPath(File: "a.h")).toString();
609 auto BHeaderUri = 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
716TEST(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
740TEST(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
760TEST(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

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