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 "index/IndexAction.h"
12 #include "clang/Tooling/Tooling.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15
16 namespace clang {
17 namespace clangd {
18 namespace {
19
20 using ::testing::AllOf;
21 using ::testing::ElementsAre;
22 using ::testing::EndsWith;
23 using ::testing::Not;
24 using ::testing::Pair;
25 using ::testing::UnorderedElementsAre;
26 using ::testing::UnorderedPointwise;
27
toUri(llvm::StringRef Path)28 std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); }
29
30 MATCHER(IsTU, "") { return arg.Flags & IncludeGraphNode::SourceFlag::IsTU; }
31
32 MATCHER_P(HasDigest, Digest, "") { return arg.Digest == Digest; }
33
34 MATCHER_P(HasName, Name, "") { return arg.Name == Name; }
35
36 MATCHER(HasSameURI, "") {
37 llvm::StringRef URI = ::testing::get<0>(arg);
38 const std::string &Path = ::testing::get<1>(arg);
39 return toUri(Path) == URI;
40 }
41
42 ::testing::Matcher<const IncludeGraphNode &>
IncludesAre(const std::vector<std::string> & Includes)43 IncludesAre(const std::vector<std::string> &Includes) {
44 return ::testing::Field(&IncludeGraphNode::DirectIncludes,
45 UnorderedPointwise(HasSameURI(), Includes));
46 }
47
checkNodesAreInitialized(const IndexFileIn & IndexFile,const std::vector<std::string> & Paths)48 void checkNodesAreInitialized(const IndexFileIn &IndexFile,
49 const std::vector<std::string> &Paths) {
50 ASSERT_TRUE(IndexFile.Sources);
51 EXPECT_THAT(Paths.size(), IndexFile.Sources->size());
52 for (llvm::StringRef Path : Paths) {
53 auto URI = toUri(Path);
54 const auto &Node = IndexFile.Sources->lookup(URI);
55 // Uninitialized nodes will have an empty URI.
56 EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData());
57 }
58 }
59
toMap(const IncludeGraph & IG)60 std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) {
61 std::map<std::string, const IncludeGraphNode &> Nodes;
62 for (auto &I : IG)
63 Nodes.emplace(std::string(I.getKey()), I.getValue());
64 return Nodes;
65 }
66
67 class IndexActionTest : public ::testing::Test {
68 public:
IndexActionTest()69 IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {}
70
71 IndexFileIn
runIndexingAction(llvm::StringRef MainFilePath,const std::vector<std::string> & ExtraArgs={})72 runIndexingAction(llvm::StringRef MainFilePath,
73 const std::vector<std::string> &ExtraArgs = {}) {
74 IndexFileIn IndexFile;
75 llvm::IntrusiveRefCntPtr<FileManager> Files(
76 new FileManager(FileSystemOptions(), InMemoryFileSystem));
77
78 auto Action = createStaticIndexingAction(
__anon7e85be7d0202(SymbolSlab S) 79 Opts, [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); },
__anon7e85be7d0302(RefSlab R) 80 [&](RefSlab R) { IndexFile.Refs = std::move(R); },
__anon7e85be7d0402(RelationSlab R) 81 [&](RelationSlab R) { IndexFile.Relations = std::move(R); },
__anon7e85be7d0502(IncludeGraph IG) 82 [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); });
83
84 std::vector<std::string> Args = {"index_action", "-fsyntax-only",
85 "-xc++", "-std=c++11",
86 "-iquote", testRoot()};
87 Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
88 Args.push_back(std::string(MainFilePath));
89
90 tooling::ToolInvocation Invocation(
91 Args, std::move(Action), Files.get(),
92 std::make_shared<PCHContainerOperations>());
93
94 Invocation.run();
95
96 checkNodesAreInitialized(IndexFile, FilePaths);
97 return IndexFile;
98 }
99
addFile(llvm::StringRef Path,llvm::StringRef Content)100 void addFile(llvm::StringRef Path, llvm::StringRef Content) {
101 InMemoryFileSystem->addFile(Path, 0,
102 llvm::MemoryBuffer::getMemBufferCopy(Content));
103 FilePaths.push_back(std::string(Path));
104 }
105
106 protected:
107 SymbolCollector::Options Opts;
108 std::vector<std::string> FilePaths;
109 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
110 };
111
TEST_F(IndexActionTest,CollectIncludeGraph)112 TEST_F(IndexActionTest, CollectIncludeGraph) {
113 std::string MainFilePath = testPath("main.cpp");
114 std::string MainCode = "#include \"level1.h\"";
115 std::string Level1HeaderPath = testPath("level1.h");
116 std::string Level1HeaderCode = "#include \"level2.h\"";
117 std::string Level2HeaderPath = testPath("level2.h");
118 std::string Level2HeaderCode = "";
119
120 addFile(MainFilePath, MainCode);
121 addFile(Level1HeaderPath, Level1HeaderCode);
122 addFile(Level2HeaderPath, Level2HeaderCode);
123
124 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
125 auto Nodes = toMap(*IndexFile.Sources);
126
127 EXPECT_THAT(Nodes,
128 UnorderedElementsAre(
129 Pair(toUri(MainFilePath),
130 AllOf(IsTU(), IncludesAre({Level1HeaderPath}),
131 HasDigest(digest(MainCode)))),
132 Pair(toUri(Level1HeaderPath),
133 AllOf(Not(IsTU()), IncludesAre({Level2HeaderPath}),
134 HasDigest(digest(Level1HeaderCode)))),
135 Pair(toUri(Level2HeaderPath),
136 AllOf(Not(IsTU()), IncludesAre({}),
137 HasDigest(digest(Level2HeaderCode))))));
138 }
139
TEST_F(IndexActionTest,IncludeGraphSelfInclude)140 TEST_F(IndexActionTest, IncludeGraphSelfInclude) {
141 std::string MainFilePath = testPath("main.cpp");
142 std::string MainCode = "#include \"header.h\"";
143 std::string HeaderPath = testPath("header.h");
144 std::string HeaderCode = R"cpp(
145 #ifndef _GUARD_
146 #define _GUARD_
147 #include "header.h"
148 #endif)cpp";
149
150 addFile(MainFilePath, MainCode);
151 addFile(HeaderPath, HeaderCode);
152
153 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
154 auto Nodes = toMap(*IndexFile.Sources);
155
156 EXPECT_THAT(
157 Nodes,
158 UnorderedElementsAre(
159 Pair(toUri(MainFilePath), AllOf(IsTU(), IncludesAre({HeaderPath}),
160 HasDigest(digest(MainCode)))),
161 Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({HeaderPath}),
162 HasDigest(digest(HeaderCode))))));
163 }
164
TEST_F(IndexActionTest,IncludeGraphSkippedFile)165 TEST_F(IndexActionTest, IncludeGraphSkippedFile) {
166 std::string MainFilePath = testPath("main.cpp");
167 std::string MainCode = R"cpp(
168 #include "common.h"
169 #include "header.h"
170 )cpp";
171
172 std::string CommonHeaderPath = testPath("common.h");
173 std::string CommonHeaderCode = R"cpp(
174 #ifndef _GUARD_
175 #define _GUARD_
176 void f();
177 #endif)cpp";
178
179 std::string HeaderPath = testPath("header.h");
180 std::string HeaderCode = R"cpp(
181 #include "common.h"
182 void g();)cpp";
183
184 addFile(MainFilePath, MainCode);
185 addFile(HeaderPath, HeaderCode);
186 addFile(CommonHeaderPath, CommonHeaderCode);
187
188 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
189 auto Nodes = toMap(*IndexFile.Sources);
190
191 EXPECT_THAT(
192 Nodes, UnorderedElementsAre(
193 Pair(toUri(MainFilePath),
194 AllOf(IsTU(), IncludesAre({HeaderPath, CommonHeaderPath}),
195 HasDigest(digest(MainCode)))),
196 Pair(toUri(HeaderPath),
197 AllOf(Not(IsTU()), IncludesAre({CommonHeaderPath}),
198 HasDigest(digest(HeaderCode)))),
199 Pair(toUri(CommonHeaderPath),
200 AllOf(Not(IsTU()), IncludesAre({}),
201 HasDigest(digest(CommonHeaderCode))))));
202 }
203
TEST_F(IndexActionTest,IncludeGraphDynamicInclude)204 TEST_F(IndexActionTest, IncludeGraphDynamicInclude) {
205 std::string MainFilePath = testPath("main.cpp");
206 std::string MainCode = R"cpp(
207 #ifndef FOO
208 #define FOO "main.cpp"
209 #else
210 #define FOO "header.h"
211 #endif
212
213 #include FOO)cpp";
214 std::string HeaderPath = testPath("header.h");
215 std::string HeaderCode = "";
216
217 addFile(MainFilePath, MainCode);
218 addFile(HeaderPath, HeaderCode);
219
220 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
221 auto Nodes = toMap(*IndexFile.Sources);
222
223 EXPECT_THAT(
224 Nodes,
225 UnorderedElementsAre(
226 Pair(toUri(MainFilePath),
227 AllOf(IsTU(), IncludesAre({MainFilePath, HeaderPath}),
228 HasDigest(digest(MainCode)))),
229 Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({}),
230 HasDigest(digest(HeaderCode))))));
231 }
232
TEST_F(IndexActionTest,NoWarnings)233 TEST_F(IndexActionTest, NoWarnings) {
234 std::string MainFilePath = testPath("main.cpp");
235 std::string MainCode = R"cpp(
236 void foo(int x) {
237 if (x = 1) // -Wparentheses
238 return;
239 if (x = 1) // -Wparentheses
240 return;
241 }
242 void bar() {}
243 )cpp";
244 addFile(MainFilePath, MainCode);
245 // We set -ferror-limit so the warning-promoted-to-error would be fatal.
246 // This would cause indexing to stop (if warnings weren't disabled).
247 IndexFileIn IndexFile = runIndexingAction(
248 MainFilePath, {"-ferror-limit=1", "-Wparentheses", "-Werror"});
249 ASSERT_TRUE(IndexFile.Sources);
250 ASSERT_NE(0u, IndexFile.Sources->size());
251 EXPECT_THAT(*IndexFile.Symbols, ElementsAre(HasName("foo"), HasName("bar")));
252 }
253
TEST_F(IndexActionTest,SkipFiles)254 TEST_F(IndexActionTest, SkipFiles) {
255 std::string MainFilePath = testPath("main.cpp");
256 addFile(MainFilePath, R"cpp(
257 // clang-format off
258 #include "good.h"
259 #include "bad.h"
260 // clang-format on
261 )cpp");
262 addFile(testPath("good.h"), R"cpp(
263 struct S { int s; };
264 void f1() { S f; }
265 auto unskippable1() { return S(); }
266 )cpp");
267 addFile(testPath("bad.h"), R"cpp(
268 struct T { S t; };
269 void f2() { S f; }
270 auto unskippable2() { return S(); }
271 )cpp");
272 Opts.FileFilter = [](const SourceManager &SM, FileID F) {
273 return !SM.getFileEntryForID(F)->getName().endswith("bad.h");
274 };
275 IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
276 EXPECT_THAT(*IndexFile.Symbols,
277 UnorderedElementsAre(HasName("S"), HasName("s"), HasName("f1"),
278 HasName("unskippable1")));
279 for (const auto &Pair : *IndexFile.Refs)
280 for (const auto &Ref : Pair.second)
281 EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h"));
282 }
283
284 } // namespace
285 } // namespace clangd
286 } // namespace clang
287