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