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