• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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