xref: /llvm-project/clang-tools-extra/clangd/unittests/IndexActionTests.cpp (revision cf048e16a7c682a3ed5abb32702c3048fcad7638)
1 //===------ IndexActionTests.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 "Headers.h"
10 #include "TestFS.h"
11 #include "URI.h"
12 #include "index/IndexAction.h"
13 #include "index/Serialization.h"
14 #include "clang/Basic/SourceLocation.h"
15 #include "clang/Basic/SourceManager.h"
16 #include "clang/Tooling/Tooling.h"
17 #include "gmock/gmock.h"
18 #include "gtest/gtest.h"
19 #include <string>
20 
21 namespace clang {
22 namespace clangd {
23 namespace {
24 
25 using ::testing::AllOf;
26 using ::testing::ElementsAre;
27 using ::testing::EndsWith;
28 using ::testing::Not;
29 using ::testing::Pair;
30 using ::testing::UnorderedElementsAre;
31 using ::testing::UnorderedPointwise;
32 
toUri(llvm::StringRef Path)33 std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); }
34 
35 MATCHER(isTU, "") { return arg.Flags & IncludeGraphNode::SourceFlag::IsTU; }
36 
37 MATCHER_P(hasDigest, Digest, "") { return arg.Digest == Digest; }
38 
39 MATCHER_P(hasName, Name, "") { return arg.Name == Name; }
40 
41 MATCHER(hasSameURI, "") {
42   llvm::StringRef URI = ::testing::get<0>(arg);
43   const std::string &Path = ::testing::get<1>(arg);
44   return toUri(Path) == URI;
45 }
46 
47 MATCHER_P(includeHeader, P, "") {
48   return (arg.IncludeHeaders.size() == 1) &&
49          (arg.IncludeHeaders.begin()->IncludeHeader == P);
50 }
51 
52 ::testing::Matcher<const IncludeGraphNode &>
includesAre(const std::vector<std::string> & Includes)53 includesAre(const std::vector<std::string> &Includes) {
54   return ::testing::Field(&IncludeGraphNode::DirectIncludes,
55                           UnorderedPointwise(hasSameURI(), Includes));
56 }
57 
checkNodesAreInitialized(const IndexFileIn & IndexFile,const std::vector<std::string> & Paths)58 void checkNodesAreInitialized(const IndexFileIn &IndexFile,
59                               const std::vector<std::string> &Paths) {
60   ASSERT_TRUE(IndexFile.Sources);
61   EXPECT_THAT(Paths.size(), IndexFile.Sources->size());
62   for (llvm::StringRef Path : Paths) {
63     auto URI = toUri(Path);
64     const auto &Node = IndexFile.Sources->lookup(URI);
65     // Uninitialized nodes will have an empty URI.
66     EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData());
67   }
68 }
69 
toMap(const IncludeGraph & IG)70 std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) {
71   std::map<std::string, const IncludeGraphNode &> Nodes;
72   for (auto &I : IG)
73     Nodes.emplace(std::string(I.getKey()), I.getValue());
74   return Nodes;
75 }
76 
77 class IndexActionTest : public ::testing::Test {
78 public:
IndexActionTest()79   IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {}
80 
81   IndexFileIn
runIndexingAction(llvm::StringRef MainFilePath,const std::vector<std::string> & ExtraArgs={})82   runIndexingAction(llvm::StringRef MainFilePath,
83                     const std::vector<std::string> &ExtraArgs = {}) {
84     IndexFileIn IndexFile;
85     llvm::IntrusiveRefCntPtr<FileManager> Files(
86         new FileManager(FileSystemOptions(), InMemoryFileSystem));
87 
88     auto Action = createStaticIndexingAction(
__anonc593db390202(SymbolSlab S) 89         Opts, [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); },
__anonc593db390302(RefSlab R) 90         [&](RefSlab R) { IndexFile.Refs = std::move(R); },
__anonc593db390402(RelationSlab R) 91         [&](RelationSlab R) { IndexFile.Relations = std::move(R); },
__anonc593db390502(IncludeGraph IG) 92         [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); });
93 
94     std::vector<std::string> Args = {"index_action", "-fsyntax-only",
95                                      "-xc++",        "-std=c++11",
96                                      "-iquote",      testRoot()};
97     Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
98     Args.push_back(std::string(MainFilePath));
99 
100     tooling::ToolInvocation Invocation(
101         Args, std::move(Action), Files.get(),
102         std::make_shared<PCHContainerOperations>());
103 
104     Invocation.run();
105 
106     checkNodesAreInitialized(IndexFile, FilePaths);
107     return IndexFile;
108   }
109 
addFile(llvm::StringRef Path,llvm::StringRef Content)110   void addFile(llvm::StringRef Path, llvm::StringRef Content) {
111     InMemoryFileSystem->addFile(Path, 0,
112                                 llvm::MemoryBuffer::getMemBufferCopy(Content));
113     FilePaths.push_back(std::string(Path));
114   }
115 
116 protected:
117   SymbolCollector::Options Opts;
118   std::vector<std::string> FilePaths;
119   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
120 };
121 
TEST_F(IndexActionTest,CollectIncludeGraph)122 TEST_F(IndexActionTest, CollectIncludeGraph) {
123   std::string MainFilePath = testPath("main.cpp");
124   std::string MainCode = "#include \"level1.h\"";
125   std::string Level1HeaderPath = testPath("level1.h");
126   std::string Level1HeaderCode = "#include \"level2.h\"";
127   std::string Level2HeaderPath = testPath("level2.h");
128   std::string Level2HeaderCode = "";
129 
130   addFile(MainFilePath, MainCode);
131   addFile(Level1HeaderPath, Level1HeaderCode);
132   addFile(Level2HeaderPath, Level2HeaderCode);
133 
134   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
135   auto Nodes = toMap(*IndexFile.Sources);
136 
137   EXPECT_THAT(Nodes,
138               UnorderedElementsAre(
139                   Pair(toUri(MainFilePath),
140                        AllOf(isTU(), includesAre({Level1HeaderPath}),
141                              hasDigest(digest(MainCode)))),
142                   Pair(toUri(Level1HeaderPath),
143                        AllOf(Not(isTU()), includesAre({Level2HeaderPath}),
144                              hasDigest(digest(Level1HeaderCode)))),
145                   Pair(toUri(Level2HeaderPath),
146                        AllOf(Not(isTU()), includesAre({}),
147                              hasDigest(digest(Level2HeaderCode))))));
148 }
149 
TEST_F(IndexActionTest,IncludeGraphSelfInclude)150 TEST_F(IndexActionTest, IncludeGraphSelfInclude) {
151   std::string MainFilePath = testPath("main.cpp");
152   std::string MainCode = "#include \"header.h\"";
153   std::string HeaderPath = testPath("header.h");
154   std::string HeaderCode = R"cpp(
155       #ifndef _GUARD_
156       #define _GUARD_
157       #include "header.h"
158       #endif)cpp";
159 
160   addFile(MainFilePath, MainCode);
161   addFile(HeaderPath, HeaderCode);
162 
163   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
164   auto Nodes = toMap(*IndexFile.Sources);
165 
166   EXPECT_THAT(
167       Nodes,
168       UnorderedElementsAre(
169           Pair(toUri(MainFilePath), AllOf(isTU(), includesAre({HeaderPath}),
170                                           hasDigest(digest(MainCode)))),
171           Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({HeaderPath}),
172                                         hasDigest(digest(HeaderCode))))));
173 }
174 
TEST_F(IndexActionTest,IncludeGraphSkippedFile)175 TEST_F(IndexActionTest, IncludeGraphSkippedFile) {
176   std::string MainFilePath = testPath("main.cpp");
177   std::string MainCode = R"cpp(
178       #include "common.h"
179       #include "header.h"
180       )cpp";
181 
182   std::string CommonHeaderPath = testPath("common.h");
183   std::string CommonHeaderCode = R"cpp(
184       #ifndef _GUARD_
185       #define _GUARD_
186       void f();
187       #endif)cpp";
188 
189   std::string HeaderPath = testPath("header.h");
190   std::string HeaderCode = R"cpp(
191       #include "common.h"
192       void g();)cpp";
193 
194   addFile(MainFilePath, MainCode);
195   addFile(HeaderPath, HeaderCode);
196   addFile(CommonHeaderPath, CommonHeaderCode);
197 
198   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
199   auto Nodes = toMap(*IndexFile.Sources);
200 
201   EXPECT_THAT(
202       Nodes, UnorderedElementsAre(
203                  Pair(toUri(MainFilePath),
204                       AllOf(isTU(), includesAre({HeaderPath, CommonHeaderPath}),
205                             hasDigest(digest(MainCode)))),
206                  Pair(toUri(HeaderPath),
207                       AllOf(Not(isTU()), includesAre({CommonHeaderPath}),
208                             hasDigest(digest(HeaderCode)))),
209                  Pair(toUri(CommonHeaderPath),
210                       AllOf(Not(isTU()), includesAre({}),
211                             hasDigest(digest(CommonHeaderCode))))));
212 }
213 
TEST_F(IndexActionTest,IncludeGraphDynamicInclude)214 TEST_F(IndexActionTest, IncludeGraphDynamicInclude) {
215   std::string MainFilePath = testPath("main.cpp");
216   std::string MainCode = R"cpp(
217       #ifndef FOO
218       #define FOO "main.cpp"
219       #else
220       #define FOO "header.h"
221       #endif
222 
223       #include FOO)cpp";
224   std::string HeaderPath = testPath("header.h");
225   std::string HeaderCode = "";
226 
227   addFile(MainFilePath, MainCode);
228   addFile(HeaderPath, HeaderCode);
229 
230   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
231   auto Nodes = toMap(*IndexFile.Sources);
232 
233   EXPECT_THAT(
234       Nodes,
235       UnorderedElementsAre(
236           Pair(toUri(MainFilePath),
237                AllOf(isTU(), includesAre({MainFilePath, HeaderPath}),
238                      hasDigest(digest(MainCode)))),
239           Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({}),
240                                         hasDigest(digest(HeaderCode))))));
241 }
242 
TEST_F(IndexActionTest,NoWarnings)243 TEST_F(IndexActionTest, NoWarnings) {
244   std::string MainFilePath = testPath("main.cpp");
245   std::string MainCode = R"cpp(
246       void foo(int x) {
247         if (x = 1) // -Wparentheses
248           return;
249         if (x = 1) // -Wparentheses
250           return;
251       }
252       void bar() {}
253   )cpp";
254   addFile(MainFilePath, MainCode);
255   // We set -ferror-limit so the warning-promoted-to-error would be fatal.
256   // This would cause indexing to stop (if warnings weren't disabled).
257   IndexFileIn IndexFile = runIndexingAction(
258       MainFilePath, {"-ferror-limit=1", "-Wparentheses", "-Werror"});
259   ASSERT_TRUE(IndexFile.Sources);
260   ASSERT_NE(0u, IndexFile.Sources->size());
261   EXPECT_THAT(*IndexFile.Symbols, ElementsAre(hasName("foo"), hasName("bar")));
262 }
263 
TEST_F(IndexActionTest,SkipFiles)264 TEST_F(IndexActionTest, SkipFiles) {
265   std::string MainFilePath = testPath("main.cpp");
266   addFile(MainFilePath, R"cpp(
267     // clang-format off
268     #include "good.h"
269     #include "bad.h"
270     // clang-format on
271   )cpp");
272   addFile(testPath("good.h"), R"cpp(
273     struct S { int s; };
274     void f1() { S f; }
275     auto unskippable1() { return S(); }
276   )cpp");
277   addFile(testPath("bad.h"), R"cpp(
278     struct T { S t; };
279     void f2() { S f; }
280     auto unskippable2() { return S(); }
281   )cpp");
282   Opts.FileFilter = [](const SourceManager &SM, FileID F) {
283     return !SM.getFileEntryRefForID(F)->getName().ends_with("bad.h");
284   };
285   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
286   EXPECT_THAT(*IndexFile.Symbols,
287               UnorderedElementsAre(hasName("S"), hasName("s"), hasName("f1"),
288                                    hasName("unskippable1")));
289   for (const auto &Pair : *IndexFile.Refs)
290     for (const auto &Ref : Pair.second)
291       EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h"));
292 }
293 
TEST_F(IndexActionTest,SkipNestedSymbols)294 TEST_F(IndexActionTest, SkipNestedSymbols) {
295   std::string MainFilePath = testPath("main.cpp");
296   addFile(MainFilePath, R"cpp(
297   namespace ns1 {
298   namespace ns2 {
299   namespace ns3 {
300   namespace ns4 {
301   namespace ns5 {
302   namespace ns6 {
303   namespace ns7 {
304   namespace ns8 {
305   namespace ns9 {
306   class Bar {};
307   void foo() {
308     class Baz {};
309   }
310   }
311   }
312   }
313   }
314   }
315   }
316   }
317   }
318   })cpp");
319   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
320   EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("foo")));
321   EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("Bar")));
322   EXPECT_THAT(*IndexFile.Symbols, Not(testing::Contains(hasName("Baz"))));
323 }
324 
TEST_F(IndexActionTest,SymbolFromCC)325 TEST_F(IndexActionTest, SymbolFromCC) {
326   std::string MainFilePath = testPath("main.cpp");
327   addFile(MainFilePath, R"cpp(
328  #include "main.h"
329  void foo() {}
330  )cpp");
331   addFile(testPath("main.h"), R"cpp(
332  #pragma once
333  void foo();
334  )cpp");
335   Opts.FileFilter = [](const SourceManager &SM, FileID F) {
336     return !SM.getFileEntryRefForID(F)->getName().ends_with("main.h");
337   };
338   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
339   EXPECT_THAT(*IndexFile.Symbols,
340               UnorderedElementsAre(AllOf(
341                   hasName("foo"),
342                   includeHeader(URI::create(testPath("main.h")).toString()))));
343 }
344 
TEST_F(IndexActionTest,IncludeHeaderForwardDecls)345 TEST_F(IndexActionTest, IncludeHeaderForwardDecls) {
346   std::string MainFilePath = testPath("main.cpp");
347   addFile(MainFilePath, R"cpp(
348 #include "fwd.h"
349 #include "full.h"
350  )cpp");
351   addFile(testPath("fwd.h"), R"cpp(
352 #ifndef _FWD_H_
353 #define _FWD_H_
354 struct Foo;
355 #endif
356  )cpp");
357   addFile(testPath("full.h"), R"cpp(
358 #ifndef _FULL_H_
359 #define _FULL_H_
360 struct Foo {};
361 
362 // This decl is important, as otherwise we detect control macro for the file,
363 // before handling definition of Foo.
364 void other();
365 #endif
366  )cpp");
367   IndexFileIn IndexFile = runIndexingAction(MainFilePath);
368   EXPECT_THAT(*IndexFile.Symbols,
369               testing::Contains(AllOf(
370                   hasName("Foo"),
371                   includeHeader(URI::create(testPath("full.h")).toString()))))
372       << *IndexFile.Symbols->begin();
373 }
374 } // namespace
375 } // namespace clangd
376 } // namespace clang
377