clang-tools  9.0.0
FileIndexTests.cpp
Go to the documentation of this file.
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 "ClangdUnit.h"
12 #include "SyncAPI.h"
13 #include "TestFS.h"
14 #include "TestTU.h"
16 #include "index/FileIndex.h"
17 #include "index/Index.h"
18 #include "clang/Frontend/CompilerInvocation.h"
19 #include "clang/Frontend/Utils.h"
20 #include "clang/Index/IndexSymbol.h"
21 #include "clang/Lex/Preprocessor.h"
22 #include "clang/Tooling/CompilationDatabase.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 
26 using ::testing::_;
27 using ::testing::AllOf;
28 using ::testing::Contains;
29 using ::testing::ElementsAre;
30 using ::testing::IsEmpty;
31 using ::testing::Pair;
32 using ::testing::UnorderedElementsAre;
33 
34 MATCHER_P(RefRange, Range, "") {
35  return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(),
36  arg.Location.End.line(), arg.Location.End.column()) ==
37  std::make_tuple(Range.start.line, Range.start.character,
38  Range.end.line, Range.end.character);
39 }
40 MATCHER_P(FileURI, F, "") { return llvm::StringRef(arg.Location.FileURI) == F; }
41 MATCHER_P(DeclURI, U, "") {
42  return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U;
43 }
44 MATCHER_P(DefURI, U, "") {
45  return llvm::StringRef(arg.Definition.FileURI) == U;
46 }
47 MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; }
48 MATCHER_P(NumReferences, N, "") { return arg.References == N; }
49 MATCHER_P(hasOrign, O, "") { return bool(arg.Origin & O); }
50 
51 namespace clang {
52 namespace clangd {
53 namespace {
54 ::testing::Matcher<const RefSlab &>
55 RefsAre(std::vector<::testing::Matcher<Ref>> Matchers) {
56  return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers)));
57 }
58 
59 Symbol symbol(llvm::StringRef ID) {
60  Symbol Sym;
61  Sym.ID = SymbolID(ID);
62  Sym.Name = ID;
63  return Sym;
64 }
65 
66 std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
68  for (int i = Begin; i <= End; i++)
69  Slab.insert(symbol(std::to_string(i)));
70  return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
71 }
72 
73 std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) {
74  RefSlab::Builder Slab;
75  Ref R;
76  R.Location.FileURI = Path;
77  R.Kind = RefKind::Reference;
78  Slab.insert(ID, R);
79  return llvm::make_unique<RefSlab>(std::move(Slab).build());
80 }
81 
82 TEST(FileSymbolsTest, UpdateAndGet) {
83  FileSymbols FS;
84  EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty());
85 
86  FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"), nullptr,
87  false);
88  EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""),
89  UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
90  EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")),
91  RefsAre({FileURI("f1.cc")}));
92 }
93 
94 TEST(FileSymbolsTest, Overlap) {
95  FileSymbols FS;
96  FS.update("f1", numSlab(1, 3), nullptr, nullptr, false);
97  FS.update("f2", numSlab(3, 5), nullptr, nullptr, false);
98  for (auto Type : {IndexType::Light, IndexType::Heavy})
99  EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""),
100  UnorderedElementsAre(QName("1"), QName("2"), QName("3"),
101  QName("4"), QName("5")));
102 }
103 
104 TEST(FileSymbolsTest, MergeOverlap) {
105  FileSymbols FS;
106  auto OneSymboSlab = [](Symbol Sym) {
108  S.insert(Sym);
109  return llvm::make_unique<SymbolSlab>(std::move(S).build());
110  };
111  auto X1 = symbol("x");
112  X1.CanonicalDeclaration.FileURI = "file:///x1";
113  auto X2 = symbol("x");
114  X2.Definition.FileURI = "file:///x2";
115 
116  FS.update("f1", OneSymboSlab(X1), nullptr, nullptr, false);
117  FS.update("f2", OneSymboSlab(X2), nullptr, nullptr, false);
118  for (auto Type : {IndexType::Light, IndexType::Heavy})
119  EXPECT_THAT(
120  runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"),
121  UnorderedElementsAre(
122  AllOf(QName("x"), DeclURI("file:///x1"), DefURI("file:///x2"))));
123 }
124 
125 TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
126  FileSymbols FS;
127 
128  SymbolID ID("1");
129  FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"), nullptr, false);
130 
131  auto Symbols = FS.buildIndex(IndexType::Light);
132  EXPECT_THAT(runFuzzyFind(*Symbols, ""),
133  UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
134  EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
135 
136  FS.update("f1", nullptr, nullptr, nullptr, false);
137  auto Empty = FS.buildIndex(IndexType::Light);
138  EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty());
139  EXPECT_THAT(getRefs(*Empty, ID), ElementsAre());
140 
141  EXPECT_THAT(runFuzzyFind(*Symbols, ""),
142  UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
143  EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
144 }
145 
146 // Adds Basename.cpp, which includes Basename.h, which contains Code.
147 void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
148  TestTU File;
149  File.Filename = (Basename + ".cpp").str();
150  File.HeaderFilename = (Basename + ".h").str();
151  File.HeaderCode = Code;
152  auto AST = File.build();
153  M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(),
154  AST.getCanonicalIncludes());
155 }
156 
157 TEST(FileIndexTest, CustomizedURIScheme) {
158  FileIndex M;
159  update(M, "f", "class string {};");
160 
161  EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(DeclURI("unittest:///f.h")));
162 }
163 
164 TEST(FileIndexTest, IndexAST) {
165  FileIndex M;
166  update(M, "f1", "namespace ns { void f() {} class X {}; }");
167 
168  FuzzyFindRequest Req;
169  Req.Query = "";
170  Req.Scopes = {"ns::"};
171  EXPECT_THAT(runFuzzyFind(M, Req),
172  UnorderedElementsAre(QName("ns::f"), QName("ns::X")));
173 }
174 
175 TEST(FileIndexTest, NoLocal) {
176  FileIndex M;
177  update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }");
178 
179  EXPECT_THAT(
180  runFuzzyFind(M, ""),
181  UnorderedElementsAre(QName("ns"), QName("ns::f"), QName("ns::X")));
182 }
183 
184 TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
185  FileIndex M;
186  update(M, "f1", "namespace ns { void f() {} class X {}; }");
187  update(M, "f2", "namespace ns { void ff() {} class X {}; }");
188 
189  FuzzyFindRequest Req;
190  Req.Scopes = {"ns::"};
191  EXPECT_THAT(
192  runFuzzyFind(M, Req),
193  UnorderedElementsAre(QName("ns::f"), QName("ns::X"), QName("ns::ff")));
194 }
195 
196 TEST(FileIndexTest, ClassMembers) {
197  FileIndex M;
198  update(M, "f1", "class X { static int m1; int m2; static void f(); };");
199 
200  EXPECT_THAT(runFuzzyFind(M, ""),
201  UnorderedElementsAre(QName("X"), QName("X::m1"), QName("X::m2"),
202  QName("X::f")));
203 }
204 
205 TEST(FileIndexTest, IncludeCollected) {
206  FileIndex M;
207  update(
208  M, "f",
209  "// IWYU pragma: private, include <the/good/header.h>\nclass string {};");
210 
211  auto Symbols = runFuzzyFind(M, "");
212  EXPECT_THAT(Symbols, ElementsAre(_));
213  EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader,
214  "<the/good/header.h>");
215 }
216 
217 TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) {
218  TestTU TU;
219  TU.HeaderCode = "class Foo{};";
220  TU.HeaderFilename = "algorithm";
221 
222  auto Symbols = runFuzzyFind(*TU.index(), "");
223  EXPECT_THAT(Symbols, ElementsAre(_));
224  EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader,
225  "<algorithm>");
226 }
227 
228 TEST(FileIndexTest, TemplateParamsInLabel) {
229  auto Source = R"cpp(
230 template <class Ty>
231 class vector {
232 };
233 
234 template <class Ty, class Arg>
235 vector<Ty> make_vector(Arg A) {}
236 )cpp";
237 
238  FileIndex M;
239  update(M, "f", Source);
240 
241  auto Symbols = runFuzzyFind(M, "");
242  EXPECT_THAT(Symbols,
243  UnorderedElementsAre(QName("vector"), QName("make_vector")));
244  auto It = Symbols.begin();
245  Symbol Vector = *It++;
246  Symbol MakeVector = *It++;
247  if (MakeVector.Name == "vector")
248  std::swap(MakeVector, Vector);
249 
250  EXPECT_EQ(Vector.Signature, "<class Ty>");
251  EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>");
252 
253  EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)");
254  EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
255 }
256 
257 TEST(FileIndexTest, RebuildWithPreamble) {
258  auto FooCpp = testPath("foo.cpp");
259  auto FooH = testPath("foo.h");
260  // Preparse ParseInputs.
261  ParseInputs PI;
262  PI.CompileCommand.Directory = testRoot();
263  PI.CompileCommand.Filename = FooCpp;
264  PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp};
265 
266  llvm::StringMap<std::string> Files;
267  Files[FooCpp] = "";
268  Files[FooH] = R"cpp(
269  namespace ns_in_header {
270  int func_in_header();
271  }
272  )cpp";
273  PI.FS = buildTestFS(std::move(Files));
274 
275  PI.Contents = R"cpp(
276  #include "foo.h"
277  namespace ns_in_source {
278  int func_in_source();
279  }
280  )cpp";
281 
282  // Rebuild the file.
283  auto CI = buildCompilerInvocation(PI);
284 
285  FileIndex Index;
286  bool IndexUpdated = false;
288  FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
289  /*StoreInMemory=*/true,
290  [&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
291  const CanonicalIncludes &CanonIncludes) {
292  EXPECT_FALSE(IndexUpdated) << "Expected only a single index update";
293  IndexUpdated = true;
294  Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes);
295  });
296  ASSERT_TRUE(IndexUpdated);
297 
298  // Check the index contains symbols from the preamble, but not from the main
299  // file.
300  FuzzyFindRequest Req;
301  Req.Query = "";
302  Req.Scopes = {"", "ns_in_header::"};
303 
304  EXPECT_THAT(runFuzzyFind(Index, Req),
305  UnorderedElementsAre(QName("ns_in_header"),
306  QName("ns_in_header::func_in_header")));
307 }
308 
309 TEST(FileIndexTest, Refs) {
310  const char *HeaderCode = "class Foo {};";
311  Annotations MainCode(R"cpp(
312  void f() {
313  $foo[[Foo]] foo;
314  }
315  )cpp");
316 
317  auto Foo =
318  findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo");
319 
320  RefsRequest Request;
321  Request.IDs = {Foo.ID};
322 
323  FileIndex Index;
324  // Add test.cc
325  TestTU Test;
326  Test.HeaderCode = HeaderCode;
327  Test.Code = MainCode.code();
328  Test.Filename = "test.cc";
329  auto AST = Test.build();
330  Index.updateMain(Test.Filename, AST);
331  // Add test2.cc
332  TestTU Test2;
333  Test2.HeaderCode = HeaderCode;
334  Test2.Code = MainCode.code();
335  Test2.Filename = "test2.cc";
336  AST = Test2.build();
337  Index.updateMain(Test2.Filename, AST);
338 
339  EXPECT_THAT(getRefs(Index, Foo.ID),
340  RefsAre({AllOf(RefRange(MainCode.range("foo")),
341  FileURI("unittest:///test.cc")),
342  AllOf(RefRange(MainCode.range("foo")),
343  FileURI("unittest:///test2.cc"))}));
344 }
345 
346 TEST(FileIndexTest, CollectMacros) {
347  FileIndex M;
348  update(M, "f", "#define CLANGD 1");
349  EXPECT_THAT(runFuzzyFind(M, ""), Contains(QName("CLANGD")));
350 }
351 
352 TEST(FileIndexTest, Relations) {
353  TestTU TU;
354  TU.Filename = "f.cpp";
355  TU.HeaderFilename = "f.h";
356  TU.HeaderCode = "class A {}; class B : public A {};";
357  auto AST = TU.build();
358  FileIndex Index;
359  Index.updatePreamble(TU.Filename, AST.getASTContext(),
361  SymbolID A = findSymbol(TU.headerSymbols(), "A").ID;
362  uint32_t Results = 0;
363  RelationsRequest Req;
364  Req.Subjects.insert(A);
365  Req.Predicate = index::SymbolRole::RelationBaseOf;
366  Index.relations(Req, [&](const SymbolID &, const Symbol &) { ++Results; });
367  EXPECT_EQ(Results, 1u);
368 }
369 
370 TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
371  TestTU TU;
372  TU.HeaderCode = "class Foo{};";
373  Annotations Main(R"cpp(
374  #include "foo.h"
375  void f() {
376  [[Foo]] foo;
377  }
378  )cpp");
379  TU.Code = Main.code();
380  auto AST = TU.build();
381  FileIndex Index;
382  Index.updateMain(testPath(TU.Filename), AST);
383 
384  // Expect to see references in main file, references in headers are excluded
385  // because we only index main AST.
386  EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID),
387  RefsAre({RefRange(Main.range())}));
388 }
389 
390 TEST(FileIndexTest, MergeMainFileSymbols) {
391  const char* CommonHeader = "void foo();";
392  TestTU Header = TestTU::withCode(CommonHeader);
393  TestTU Cpp = TestTU::withCode("void foo() {}");
394  Cpp.Filename = "foo.cpp";
395  Cpp.HeaderFilename = "foo.h";
396  Cpp.HeaderCode = CommonHeader;
397 
398  FileIndex Index;
399  auto HeaderAST = Header.build();
400  auto CppAST = Cpp.build();
401  Index.updateMain(testPath("foo.h"), HeaderAST);
402  Index.updateMain(testPath("foo.cpp"), CppAST);
403 
404  auto Symbols = runFuzzyFind(Index, "");
405  // Check foo is merged, foo in Cpp wins (as we see the definition there).
406  EXPECT_THAT(Symbols, ElementsAre(AllOf(DeclURI("unittest:///foo.h"),
407  DefURI("unittest:///foo.cpp"),
408  hasOrign(SymbolOrigin::Merge))));
409 }
410 
411 TEST(FileSymbolsTest, CountReferencesNoRefSlabs) {
412  FileSymbols FS;
413  FS.update("f1", numSlab(1, 3), nullptr, nullptr, true);
414  FS.update("f2", numSlab(1, 3), nullptr, nullptr, false);
415  EXPECT_THAT(
417  ""),
418  UnorderedElementsAre(AllOf(QName("1"), NumReferences(0u)),
419  AllOf(QName("2"), NumReferences(0u)),
420  AllOf(QName("3"), NumReferences(0u))));
421 }
422 
423 TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
424  FileSymbols FS;
425  FS.update("f1cpp", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cpp"), nullptr,
426  true);
427  FS.update("f1h", numSlab(1, 3), refSlab(SymbolID("1"), "f1.h"), nullptr,
428  false);
429  FS.update("f2cpp", numSlab(1, 3), refSlab(SymbolID("2"), "f2.cpp"), nullptr,
430  true);
431  FS.update("f2h", numSlab(1, 3), refSlab(SymbolID("2"), "f2.h"), nullptr,
432  false);
433  FS.update("f3cpp", numSlab(1, 3), refSlab(SymbolID("3"), "f3.cpp"), nullptr,
434  true);
435  FS.update("f3h", numSlab(1, 3), refSlab(SymbolID("3"), "f3.h"), nullptr,
436  false);
437  EXPECT_THAT(
439  ""),
440  UnorderedElementsAre(AllOf(QName("1"), NumReferences(1u)),
441  AllOf(QName("2"), NumReferences(1u)),
442  AllOf(QName("3"), NumReferences(1u))));
443 }
444 } // namespace
445 } // namespace clangd
446 } // namespace clang
Symbol symbol(llvm::StringRef QName)
Definition: TestIndex.cpp:16
::testing::Matcher< const RefSlab & > RefsAre(std::vector<::testing::Matcher< Ref >> Matchers)
static llvm::Optional< ParsedAST > build(std::unique_ptr< clang::CompilerInvocation > CI, std::shared_ptr< const PreambleData > Preamble, std::unique_ptr< llvm::MemoryBuffer > Buffer, IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS, const SymbolIndex *Index, const ParseOptions &Opts)
Attempts to run Clang and store parsed AST.
Definition: ClangdUnit.cpp:287
std::vector< CodeCompletionResult > Results
MockFSProvider FS
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
Definition: ClangdUnit.cpp:477
SymbolID ID
The ID of the symbol.
Definition: Symbol.h:38
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation &CI, std::shared_ptr< const PreambleData > OldPreamble, const tooling::CompileCommand &OldCompileCommand, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback)
Rebuild the preamble for the new inputs unless the old one can be reused.
Definition: ClangdUnit.cpp:566
const CanonicalIncludes & getCanonicalIncludes() const
Definition: ClangdUnit.cpp:535
Context Ctx
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > buildTestFS(llvm::StringMap< std::string > const &Files, llvm::StringMap< time_t > const &Timestamps)
Definition: TestFS.cpp:22
std::string QName
static TestTU withHeaderCode(llvm::StringRef HeaderCode)
Definition: TestTU.h:39
TEST(BackgroundQueueTest, Priority)
std::string testPath(PathRef File)
Definition: TestFS.cpp:82
std::shared_ptr< Preprocessor > getPreprocessorPtr()
Definition: ClangdUnit.cpp:485
std::string Path
A typedef to represent a file path.
Definition: Path.h:20
SymbolSlab runFuzzyFind(const SymbolIndex &Index, llvm::StringRef Query)
Definition: SyncAPI.cpp:129
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs)
Builds compiler invocation that could be used to build AST or preamble.
Definition: Compiler.cpp:44
const char * testRoot()
Definition: TestFS.cpp:74
RelationSlab Relations
MATCHER_P(RefRange, Range, "")
SymbolSlab Symbols
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:33
CodeCompletionBuilder Builder
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
const Symbol & findSymbol(const SymbolSlab &Slab, llvm::StringRef QName)
Definition: TestTU.cpp:93
const_iterator begin() const
Definition: Symbol.h:185
CharSourceRange Range
SourceRange for the file name.
RefSlab Refs
std::array< uint8_t, 20 > SymbolID
llvm::StringMap< std::string > Files
NodeType Type
RefSlab getRefs(const SymbolIndex &Index, SymbolID ID)
Definition: SyncAPI.cpp:142
const SymbolIndex * Index
Definition: Dexp.cpp:84