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