1//===-- HeadersTests.cpp - Include headers unit tests -----------*- 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
11#include "Compiler.h"
12#include "Matchers.h"
13#include "TestFS.h"
14#include "TestTU.h"
15#include "clang/Basic/TokenKinds.h"
16#include "clang/Frontend/CompilerInstance.h"
17#include "clang/Frontend/CompilerInvocation.h"
18#include "clang/Frontend/FrontendActions.h"
19#include "clang/Tooling/Inclusions/HeaderIncludes.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/FormatVariadic.h"
23#include "llvm/Support/Path.h"
24#include "llvm/Testing/Support/Error.h"
25#include "gmock/gmock.h"
26#include "gtest/gtest.h"
27#include <optional>
28
29namespace clang {
30namespace clangd {
31namespace {
32
33using ::testing::AllOf;
34using ::testing::Contains;
35using ::testing::ElementsAre;
36using ::testing::IsEmpty;
37using ::testing::Not;
38using ::testing::UnorderedElementsAre;
39
40class HeadersTest : public ::testing::Test {
41public:
42 HeadersTest() {
43 CDB.ExtraClangFlags = {SearchDirArg.c_str()};
44 FS.Files[MainFile] = "";
45 // Make sure directory sub/ exists.
46 FS.Files[testPath(File: "sub/EMPTY")] = "";
47 }
48
49private:
50 std::unique_ptr<CompilerInstance> setupClang() {
51 auto Cmd = CDB.getCompileCommand(File: MainFile);
52 assert(static_cast<bool>(Cmd));
53
54 ParseInputs PI;
55 PI.CompileCommand = *Cmd;
56 PI.TFS = &FS;
57 auto CI = buildCompilerInvocation(Inputs: PI, D&: IgnoreDiags);
58 EXPECT_TRUE(static_cast<bool>(CI));
59 // The diagnostic options must be set before creating a CompilerInstance.
60 CI->getDiagnosticOpts().IgnoreWarnings = true;
61 auto VFS = PI.TFS->view(CWD: Cmd->Directory);
62 auto Clang = prepareCompilerInstance(
63 std::move(CI), /*Preamble=*/nullptr,
64 MainFile: llvm::MemoryBuffer::getMemBuffer(InputData: FS.Files[MainFile], BufferName: MainFile),
65 std::move(VFS), IgnoreDiags);
66
67 EXPECT_FALSE(Clang->getFrontendOpts().Inputs.empty());
68 return Clang;
69 }
70
71protected:
72 IncludeStructure::HeaderID getID(StringRef Filename,
73 IncludeStructure &Includes) {
74 auto &SM = Clang->getSourceManager();
75 auto Entry = SM.getFileManager().getFileRef(Filename);
76 EXPECT_THAT_EXPECTED(Entry, llvm::Succeeded());
77 return Includes.getOrCreateID(Entry: *Entry);
78 }
79
80 IncludeStructure collectIncludes() {
81 Clang = setupClang();
82 PreprocessOnlyAction Action;
83 EXPECT_TRUE(
84 Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
85 IncludeStructure Includes;
86 Includes.collect(CI: *Clang);
87 EXPECT_FALSE(Action.Execute());
88 Action.EndSourceFile();
89 return Includes;
90 }
91
92 // Calculates the include path, or returns "" on error or header should not be
93 // inserted.
94 std::string calculate(PathRef Original, PathRef Preferred = "",
95 const std::vector<Inclusion> &Inclusions = {}) {
96 Clang = setupClang();
97 PreprocessOnlyAction Action;
98 EXPECT_TRUE(
99 Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
100
101 if (Preferred.empty())
102 Preferred = Original;
103 auto ToHeaderFile = [](llvm::StringRef Header) {
104 return HeaderFile{.File: std::string(Header),
105 /*Verbatim=*/!llvm::sys::path::is_absolute(path: Header)};
106 };
107
108 IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
109 CDB.getCompileCommand(File: MainFile)->Directory,
110 &Clang->getPreprocessor().getHeaderSearchInfo());
111 for (const auto &Inc : Inclusions)
112 Inserter.addExisting(Inc);
113 auto Inserted = ToHeaderFile(Preferred);
114 if (!Inserter.shouldInsertInclude(DeclaringHeader: Original, InsertedHeader: Inserted))
115 return "";
116 auto Path = Inserter.calculateIncludePath(InsertedHeader: Inserted, IncludingFile: MainFile);
117 Action.EndSourceFile();
118 return Path.value_or(u: "");
119 }
120
121 std::optional<TextEdit> insert(llvm::StringRef VerbatimHeader,
122 tooling::IncludeDirective Directive) {
123 Clang = setupClang();
124 PreprocessOnlyAction Action;
125 EXPECT_TRUE(
126 Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
127
128 IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
129 CDB.getCompileCommand(File: MainFile)->Directory,
130 &Clang->getPreprocessor().getHeaderSearchInfo());
131 auto Edit = Inserter.insert(VerbatimHeader, Directive);
132 Action.EndSourceFile();
133 return Edit;
134 }
135
136 MockFS FS;
137 MockCompilationDatabase CDB;
138 std::string MainFile = testPath(File: "main.cpp");
139 std::string Subdir = testPath(File: "sub");
140 std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str();
141 IgnoringDiagConsumer IgnoreDiags;
142 std::unique_ptr<CompilerInstance> Clang;
143};
144
145MATCHER_P(written, Name, "") { return arg.Written == Name; }
146MATCHER_P(resolved, Name, "") { return arg.Resolved == Name; }
147MATCHER_P(includeLine, N, "") { return arg.HashLine == N; }
148MATCHER_P(directive, D, "") { return arg.Directive == D; }
149
150MATCHER_P2(Distance, File, D, "") {
151 if (arg.getFirst() != File)
152 *result_listener << "file =" << static_cast<unsigned>(arg.getFirst());
153 if (arg.getSecond() != D)
154 *result_listener << "distance =" << arg.getSecond();
155 return arg.getFirst() == File && arg.getSecond() == D;
156}
157
158TEST_F(HeadersTest, CollectRewrittenAndResolved) {
159 FS.Files[MainFile] = R"cpp(
160#include "sub/bar.h" // not shortest
161)cpp";
162 std::string BarHeader = testPath(File: "sub/bar.h");
163 FS.Files[BarHeader] = "";
164
165 auto Includes = collectIncludes();
166 EXPECT_THAT(Includes.MainFileIncludes,
167 UnorderedElementsAre(
168 AllOf(written("\"sub/bar.h\""), resolved(BarHeader))));
169 EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),
170 UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),
171 Distance(getID(BarHeader, Includes), 1u)));
172}
173
174TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
175 std::string BazHeader = testPath(File: "sub/baz.h");
176 FS.Files[BazHeader] = "";
177 std::string BarHeader = testPath(File: "sub/bar.h");
178 FS.Files[BarHeader] = R"cpp(
179#include "baz.h"
180)cpp";
181 FS.Files[MainFile] = R"cpp(
182#include "bar.h"
183)cpp";
184 auto Includes = collectIncludes();
185 EXPECT_THAT(
186 Includes.MainFileIncludes,
187 UnorderedElementsAre(AllOf(written("\"bar.h\""), resolved(BarHeader))));
188 EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),
189 UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),
190 Distance(getID(BarHeader, Includes), 1u),
191 Distance(getID(BazHeader, Includes), 2u)));
192 // includeDepth() also works for non-main files.
193 EXPECT_THAT(Includes.includeDepth(getID(BarHeader, Includes)),
194 UnorderedElementsAre(Distance(getID(BarHeader, Includes), 0u),
195 Distance(getID(BazHeader, Includes), 1u)));
196}
197
198TEST_F(HeadersTest, CacheBySpellingIsBuiltForMainInclusions) {
199 std::string FooHeader = testPath(File: "foo.h");
200 FS.Files[FooHeader] = R"cpp(
201 void foo();
202)cpp";
203 std::string BarHeader = testPath(File: "bar.h");
204 FS.Files[BarHeader] = R"cpp(
205 void bar();
206)cpp";
207 std::string BazHeader = testPath(File: "baz.h");
208 FS.Files[BazHeader] = R"cpp(
209 void baz();
210)cpp";
211 FS.Files[MainFile] = R"cpp(
212#include "foo.h"
213#include "bar.h"
214#include "baz.h"
215)cpp";
216 auto Includes = collectIncludes();
217 EXPECT_THAT(Includes.MainFileIncludes,
218 UnorderedElementsAre(written("\"foo.h\""), written("\"bar.h\""),
219 written("\"baz.h\"")));
220 EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"foo.h\""),
221 UnorderedElementsAre(&Includes.MainFileIncludes[0]));
222 EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"bar.h\""),
223 UnorderedElementsAre(&Includes.MainFileIncludes[1]));
224 EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"baz.h\""),
225 UnorderedElementsAre(&Includes.MainFileIncludes[2]));
226}
227
228TEST_F(HeadersTest, PreambleIncludesPresentOnce) {
229 // We use TestTU here, to ensure we use the preamble replay logic.
230 // We're testing that the logic doesn't crash, and doesn't result in duplicate
231 // includes. (We'd test more directly, but it's pretty well encapsulated!)
232 auto TU = TestTU::withCode(Code: R"cpp(
233 #include "a.h"
234
235 #include "a.h"
236 void foo();
237 #include "a.h"
238 )cpp");
239 TU.HeaderFilename = "a.h"; // suppress "not found".
240 EXPECT_THAT(TU.build().getIncludeStructure().MainFileIncludes,
241 ElementsAre(includeLine(1), includeLine(3), includeLine(5)));
242}
243
244TEST_F(HeadersTest, UnResolvedInclusion) {
245 FS.Files[MainFile] = R"cpp(
246#include "foo.h"
247)cpp";
248
249 EXPECT_THAT(collectIncludes().MainFileIncludes,
250 UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));
251 EXPECT_THAT(collectIncludes().IncludeChildren, IsEmpty());
252}
253
254TEST_F(HeadersTest, IncludedFilesGraph) {
255 FS.Files[MainFile] = R"cpp(
256#include "bar.h"
257#include "foo.h"
258)cpp";
259 std::string BarHeader = testPath(File: "bar.h");
260 FS.Files[BarHeader] = "";
261 std::string FooHeader = testPath(File: "foo.h");
262 FS.Files[FooHeader] = R"cpp(
263#include "bar.h"
264#include "baz.h"
265)cpp";
266 std::string BazHeader = testPath(File: "baz.h");
267 FS.Files[BazHeader] = "";
268
269 auto Includes = collectIncludes();
270 llvm::DenseMap<IncludeStructure::HeaderID,
271 SmallVector<IncludeStructure::HeaderID>>
272 Expected = {{getID(Filename: MainFile, Includes),
273 {getID(Filename: BarHeader, Includes), getID(Filename: FooHeader, Includes)}},
274 {getID(Filename: FooHeader, Includes),
275 {getID(Filename: BarHeader, Includes), getID(Filename: BazHeader, Includes)}}};
276 EXPECT_EQ(Includes.IncludeChildren, Expected);
277}
278
279TEST_F(HeadersTest, IncludeDirective) {
280 FS.Files[MainFile] = R"cpp(
281#include "foo.h"
282#import "foo.h"
283#include_next "foo.h"
284)cpp";
285
286 // ms-compatibility changes meaning of #import, make sure it is turned off.
287 CDB.ExtraClangFlags.push_back(x: "-fno-ms-compatibility");
288 EXPECT_THAT(collectIncludes().MainFileIncludes,
289 UnorderedElementsAre(directive(tok::pp_include),
290 directive(tok::pp_import),
291 directive(tok::pp_include_next)));
292}
293
294TEST_F(HeadersTest, SearchPath) {
295 FS.Files["foo/bar.h"] = "x";
296 FS.Files["foo/bar/baz.h"] = "y";
297 CDB.ExtraClangFlags.push_back(x: "-Ifoo/bar");
298 CDB.ExtraClangFlags.push_back(x: "-Ifoo/bar/..");
299 EXPECT_THAT(collectIncludes().SearchPathsCanonical,
300 ElementsAre(Subdir, testPath("foo/bar"), testPath("foo")));
301}
302
303TEST_F(HeadersTest, InsertInclude) {
304 std::string Path = testPath(File: "sub/bar.h");
305 FS.Files[Path] = "";
306 EXPECT_EQ(calculate(Path), "\"bar.h\"");
307}
308
309TEST_F(HeadersTest, DoNotInsertIfInSameFile) {
310 MainFile = testPath(File: "main.h");
311 EXPECT_EQ(calculate(MainFile), "");
312}
313
314TEST_F(HeadersTest, DoNotInsertOffIncludePath) {
315 MainFile = testPath(File: "sub/main.cpp");
316 EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");
317}
318
319TEST_F(HeadersTest, ShortenIncludesInSearchPath) {
320 std::string BarHeader = testPath(File: "sub/bar.h");
321 EXPECT_EQ(calculate(BarHeader), "\"bar.h\"");
322
323 SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str();
324 CDB.ExtraClangFlags = {SearchDirArg.c_str()};
325 BarHeader = testPath(File: "sub/bar.h");
326 EXPECT_EQ(calculate(BarHeader), "\"sub/bar.h\"");
327}
328
329TEST_F(HeadersTest, ShortenedIncludeNotInSearchPath) {
330 std::string BarHeader =
331 llvm::sys::path::convert_to_slash(path: testPath(File: "sub-2/bar.h"));
332 EXPECT_EQ(calculate(BarHeader, ""), "\"sub-2/bar.h\"");
333}
334
335TEST_F(HeadersTest, PreferredHeader) {
336 std::string BarHeader = testPath(File: "sub/bar.h");
337 EXPECT_EQ(calculate(BarHeader, "<bar>"), "<bar>");
338
339 std::string BazHeader = testPath(File: "sub/baz.h");
340 EXPECT_EQ(calculate(BarHeader, BazHeader), "\"baz.h\"");
341}
342
343TEST_F(HeadersTest, DontInsertDuplicatePreferred) {
344 Inclusion Inc;
345 Inc.Written = "\"bar.h\"";
346 Inc.Resolved = "";
347 EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc}), "");
348 EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc}), "");
349}
350
351TEST_F(HeadersTest, DontInsertDuplicateResolved) {
352 Inclusion Inc;
353 Inc.Written = "fake-bar.h";
354 Inc.Resolved = testPath(File: "sub/bar.h");
355 EXPECT_EQ(calculate(Inc.Resolved, "", {Inc}), "");
356 // Do not insert preferred.
357 EXPECT_EQ(calculate(Inc.Resolved, "\"BAR.h\"", {Inc}), "");
358}
359
360TEST_F(HeadersTest, PreferInserted) {
361 auto Edit = insert(VerbatimHeader: "<y>", Directive: tooling::IncludeDirective::Include);
362 ASSERT_TRUE(Edit);
363 EXPECT_EQ(Edit->newText, "#include <y>\n");
364
365 Edit = insert(VerbatimHeader: "\"header.h\"", Directive: tooling::IncludeDirective::Import);
366 ASSERT_TRUE(Edit);
367 EXPECT_EQ(Edit->newText, "#import \"header.h\"\n");
368}
369
370TEST(Headers, NoHeaderSearchInfo) {
371 std::string MainFile = testPath(File: "main.cpp");
372 IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
373 /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
374
375 auto HeaderPath = testPath(File: "sub/bar.h");
376 auto Inserting = HeaderFile{.File: HeaderPath, /*Verbatim=*/false};
377 auto Verbatim = HeaderFile{.File: "<x>", /*Verbatim=*/true};
378
379 EXPECT_EQ(Inserter.calculateIncludePath(Inserting, MainFile),
380 std::string("\"sub/bar.h\""));
381 EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Inserting), false);
382
383 EXPECT_EQ(Inserter.calculateIncludePath(Verbatim, MainFile),
384 std::string("<x>"));
385 EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Verbatim), true);
386
387 EXPECT_EQ(Inserter.calculateIncludePath(Inserting, "sub2/main2.cpp"),
388 std::nullopt);
389}
390
391TEST_F(HeadersTest, PresumedLocations) {
392 std::string HeaderFile = "__preamble_patch__.h";
393
394 // Line map inclusion back to main file.
395 std::string HeaderContents =
396 llvm::formatv(Fmt: "#line 0 \"{0}\"", Vals: llvm::sys::path::filename(path: MainFile));
397 HeaderContents += R"cpp(
398#line 3
399#include <a.h>)cpp";
400 FS.Files[HeaderFile] = HeaderContents;
401
402 // Including through non-builtin file has no effects.
403 FS.Files[MainFile] = "#include \"__preamble_patch__.h\"\n\n";
404 EXPECT_THAT(collectIncludes().MainFileIncludes,
405 Not(Contains(written("<a.h>"))));
406
407 // Now include through built-in file.
408 CDB.ExtraClangFlags = {"-include", testPath(File: HeaderFile)};
409 EXPECT_THAT(collectIncludes().MainFileIncludes,
410 Contains(AllOf(includeLine(2), written("<a.h>"))));
411}
412
413} // namespace
414} // namespace clangd
415} // namespace clang
416

source code of clang-tools-extra/clangd/unittests/HeadersTests.cpp