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