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