clang-tools 24.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 "index/Symbol.h"
15#include "clang/Basic/SourceLocation.h"
16#include "clang/Basic/SourceManager.h"
17#include "clang/Tooling/Tooling.h"
18#include "gmock/gmock.h"
19#include "gtest/gtest.h"
20#include <string>
21
22namespace clang {
23namespace clangd {
24namespace {
25
26using ::testing::AllOf;
27using ::testing::ElementsAre;
28using ::testing::EndsWith;
29using ::testing::Not;
30using ::testing::Pair;
31using ::testing::UnorderedElementsAre;
32using ::testing::UnorderedPointwise;
33
34std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); }
35
36MATCHER(isTU, "") { return arg.Flags & IncludeGraphNode::SourceFlag::IsTU; }
37
38MATCHER_P(hasDigest, Digest, "") { return arg.Digest == Digest; }
39
40MATCHER_P(hasName, Name, "") { return arg.Name == Name; }
41
42MATCHER_P(hasSignature, Signature, "") { return arg.Signature == Signature; }
43
44MATCHER(hasSameURI, "") {
45 llvm::StringRef URI = ::testing::get<0>(arg);
46 const std::string &Path = ::testing::get<1>(arg);
47 return toUri(Path) == URI;
48}
49
50MATCHER_P(includeHeader, P, "") {
51 return (arg.IncludeHeaders.size() == 1) &&
52 (arg.IncludeHeaders.begin()->IncludeHeader == P);
53}
54
55::testing::Matcher<const IncludeGraphNode &>
56includesAre(const std::vector<std::string> &Includes) {
57 return ::testing::Field(&IncludeGraphNode::DirectIncludes,
58 UnorderedPointwise(hasSameURI(), Includes));
59}
60
61void checkNodesAreInitialized(const IndexFileIn &IndexFile,
62 const std::vector<std::string> &Paths) {
63 ASSERT_TRUE(IndexFile.Sources);
64 EXPECT_THAT(Paths.size(), IndexFile.Sources->size());
65 for (llvm::StringRef Path : Paths) {
66 auto URI = toUri(Path);
67 const auto &Node = IndexFile.Sources->lookup(URI);
68 // Uninitialized nodes will have an empty URI.
69 EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData());
70 }
71}
72
73std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) {
74 std::map<std::string, const IncludeGraphNode &> Nodes;
75 for (auto &I : IG)
76 Nodes.emplace(std::string(I.getKey()), I.getValue());
77 return Nodes;
78}
79
80class IndexActionTest : public ::testing::Test {
81public:
82 IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {}
83
84 IndexFileIn
85 runIndexingAction(llvm::StringRef MainFilePath,
86 const std::vector<std::string> &ExtraArgs = {}) {
87 IndexFileIn IndexFile;
88 llvm::IntrusiveRefCntPtr<FileManager> Files(
89 new FileManager(FileSystemOptions(), InMemoryFileSystem));
90
91 auto Action = createStaticIndexingAction(
92 Opts, [&](IndexFileIn Result) { IndexFile = std::move(Result); });
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
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
116protected:
117 SymbolCollector::Options Opts;
118 std::vector<std::string> FilePaths;
119 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
120};
121
122TEST_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
150TEST_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
175TEST_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
214TEST_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
243TEST_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
264TEST_F(IndexActionTest, DeclParamName) {
265 // This is 3/3 regression tests to make sure signatures
266 // 1) have consistent variable names between header and source file
267 // 2) find variable names in other declarations
268 // See CompletionTest.DeclParamName and SignatureHelpTest.DeclParamName for
269 // the other tests.
270 std::string MainFilePath = testPath("main.cpp");
271 std::string MainCode = R"cpp( #include "zenith.hpp" )cpp";
272 std::string HeaderPath = testPath("zenith.hpp");
273 std::string HeaderCode = R"cpp(
274 void moon(int, int);
275 void moon(int month, int day);
276 void moon(int, int day);
277 void moon(int, int);
278 )cpp";
279
280 addFile(MainFilePath, MainCode);
281 addFile(HeaderPath, HeaderCode);
282
283 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
284
285 EXPECT_THAT(*IndexFile.Symbols, ElementsAre(hasSignature("(int, int day)")));
286}
287
288TEST_F(IndexActionTest, SkipFiles) {
289 std::string MainFilePath = testPath("main.cpp");
290 addFile(MainFilePath, R"cpp(
291 // clang-format off
292 #include "good.h"
293 #include "bad.h"
294 // clang-format on
295 )cpp");
296 addFile(testPath("good.h"), R"cpp(
297 struct S { int s; };
298 void f1() { S f; }
299 auto unskippable1() { return S(); }
300 )cpp");
301 addFile(testPath("bad.h"), R"cpp(
302 struct T { S t; };
303 void f2() { S f; }
304 auto unskippable2() { return S(); }
305 )cpp");
306 Opts.FileFilter = [](const SourceManager &SM, FileID F) {
307 return !SM.getFileEntryRefForID(F)->getName().ends_with("bad.h");
308 };
309 IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
310 EXPECT_THAT(*IndexFile.Symbols,
311 UnorderedElementsAre(hasName("S"), hasName("s"), hasName("f1"),
312 hasName("unskippable1")));
313 for (const auto &Pair : *IndexFile.Refs)
314 for (const auto &Ref : Pair.second)
315 EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h"));
316}
317
318TEST_F(IndexActionTest, SkipNestedSymbols) {
319 std::string MainFilePath = testPath("main.cpp");
320 addFile(MainFilePath, R"cpp(
321 namespace ns1 {
322 namespace ns2 {
323 namespace ns3 {
324 namespace ns4 {
325 namespace ns5 {
326 namespace ns6 {
327 namespace ns7 {
328 namespace ns8 {
329 namespace ns9 {
330 class Bar {};
331 void foo() {
332 class Baz {};
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340 }
341 }
342 })cpp");
343 IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
344 EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("foo")));
345 EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("Bar")));
346 EXPECT_THAT(*IndexFile.Symbols, Not(testing::Contains(hasName("Baz"))));
347}
348
349TEST_F(IndexActionTest, SymbolFromCC) {
350 std::string MainFilePath = testPath("main.cpp");
351 addFile(MainFilePath, R"cpp(
352 #include "main.h"
353 void foo() {}
354 )cpp");
355 addFile(testPath("main.h"), R"cpp(
356 #pragma once
357 void foo();
358 )cpp");
359 Opts.FileFilter = [](const SourceManager &SM, FileID F) {
360 return !SM.getFileEntryRefForID(F)->getName().ends_with("main.h");
361 };
362 IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
363 EXPECT_THAT(*IndexFile.Symbols,
364 UnorderedElementsAre(AllOf(
365 hasName("foo"),
366 includeHeader(URI::create(testPath("main.h")).toString()))));
367}
368
369TEST_F(IndexActionTest, IncludeHeaderForwardDecls) {
370 std::string MainFilePath = testPath("main.cpp");
371 addFile(MainFilePath, R"cpp(
372#include "fwd.h"
373#include "full.h"
374 )cpp");
375 addFile(testPath("fwd.h"), R"cpp(
376#ifndef _FWD_H_
377#define _FWD_H_
378struct Foo;
379#endif
380 )cpp");
381 addFile(testPath("full.h"), R"cpp(
382#ifndef _FULL_H_
383#define _FULL_H_
384struct Foo {};
385
386// This decl is important, as otherwise we detect control macro for the file,
387// before handling definition of Foo.
388void other();
389#endif
390 )cpp");
391 IndexFileIn IndexFile = runIndexingAction(MainFilePath);
392 EXPECT_THAT(*IndexFile.Symbols,
393 testing::Contains(AllOf(
394 hasName("Foo"),
395 includeHeader(URI::create(testPath("full.h")).toString()))))
396 << *IndexFile.Symbols->begin();
397}
398} // namespace
399} // namespace clangd
400} // 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:94
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