1 //===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
14 #include "clang/Basic/TokenKinds.h"
15 #include "clang/Frontend/CompilerInstance.h"
16 #include "clang/Frontend/CompilerInvocation.h"
17 #include "clang/Frontend/FrontendActions.h"
18 #include "llvm/ADT/StringRef.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Support/FormatVariadic.h"
21 #include "llvm/Support/Path.h"
22 #include "llvm/Testing/Support/Error.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
30 using ::testing::AllOf
;
31 using ::testing::Contains
;
32 using ::testing::ElementsAre
;
33 using ::testing::IsEmpty
;
35 using ::testing::UnorderedElementsAre
;
37 class HeadersTest
: public ::testing::Test
{
40 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
41 FS
.Files
[MainFile
] = "";
42 // Make sure directory sub/ exists.
43 FS
.Files
[testPath("sub/EMPTY")] = "";
47 std::unique_ptr
<CompilerInstance
> setupClang() {
48 auto Cmd
= CDB
.getCompileCommand(MainFile
);
49 assert(static_cast<bool>(Cmd
));
52 PI
.CompileCommand
= *Cmd
;
54 auto CI
= buildCompilerInvocation(PI
, IgnoreDiags
);
55 EXPECT_TRUE(static_cast<bool>(CI
));
56 // The diagnostic options must be set before creating a CompilerInstance.
57 CI
->getDiagnosticOpts().IgnoreWarnings
= true;
58 auto VFS
= PI
.TFS
->view(Cmd
->Directory
);
59 auto Clang
= prepareCompilerInstance(
60 std::move(CI
), /*Preamble=*/nullptr,
61 llvm::MemoryBuffer::getMemBuffer(FS
.Files
[MainFile
], MainFile
),
62 std::move(VFS
), IgnoreDiags
);
64 EXPECT_FALSE(Clang
->getFrontendOpts().Inputs
.empty());
69 IncludeStructure::HeaderID
getID(StringRef Filename
,
70 IncludeStructure
&Includes
) {
71 auto &SM
= Clang
->getSourceManager();
72 auto Entry
= SM
.getFileManager().getFileRef(Filename
);
73 EXPECT_THAT_EXPECTED(Entry
, llvm::Succeeded());
74 return Includes
.getOrCreateID(*Entry
);
77 IncludeStructure
collectIncludes() {
79 PreprocessOnlyAction Action
;
81 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
82 IncludeStructure Includes
;
83 Includes
.collect(*Clang
);
84 EXPECT_FALSE(Action
.Execute());
85 Action
.EndSourceFile();
89 // Calculates the include path, or returns "" on error or header should not be
91 std::string
calculate(PathRef Original
, PathRef Preferred
= "",
92 const std::vector
<Inclusion
> &Inclusions
= {}) {
94 PreprocessOnlyAction Action
;
96 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
98 if (Preferred
.empty())
100 auto ToHeaderFile
= [](llvm::StringRef Header
) {
101 return HeaderFile
{std::string(Header
),
102 /*Verbatim=*/!llvm::sys::path::is_absolute(Header
)};
105 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
106 CDB
.getCompileCommand(MainFile
)->Directory
,
107 &Clang
->getPreprocessor().getHeaderSearchInfo());
108 for (const auto &Inc
: Inclusions
)
109 Inserter
.addExisting(Inc
);
110 auto Inserted
= ToHeaderFile(Preferred
);
111 if (!Inserter
.shouldInsertInclude(Original
, Inserted
))
113 auto Path
= Inserter
.calculateIncludePath(Inserted
, MainFile
);
114 Action
.EndSourceFile();
115 return Path
.value_or("");
118 llvm::Optional
<TextEdit
> insert(llvm::StringRef VerbatimHeader
) {
119 Clang
= setupClang();
120 PreprocessOnlyAction Action
;
122 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
124 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
125 CDB
.getCompileCommand(MainFile
)->Directory
,
126 &Clang
->getPreprocessor().getHeaderSearchInfo());
127 auto Edit
= Inserter
.insert(VerbatimHeader
);
128 Action
.EndSourceFile();
133 MockCompilationDatabase CDB
;
134 std::string MainFile
= testPath("main.cpp");
135 std::string Subdir
= testPath("sub");
136 std::string SearchDirArg
= (llvm::Twine("-I") + Subdir
).str();
137 IgnoringDiagConsumer IgnoreDiags
;
138 std::unique_ptr
<CompilerInstance
> Clang
;
141 MATCHER_P(written
, Name
, "") { return arg
.Written
== Name
; }
142 MATCHER_P(resolved
, Name
, "") { return arg
.Resolved
== Name
; }
143 MATCHER_P(includeLine
, N
, "") { return arg
.HashLine
== N
; }
144 MATCHER_P(directive
, D
, "") { return arg
.Directive
== D
; }
145 MATCHER_P(hasPragmaKeep
, H
, "") { return arg
.BehindPragmaKeep
== H
; }
147 MATCHER_P2(Distance
, File
, D
, "") {
148 if (arg
.getFirst() != File
)
149 *result_listener
<< "file =" << static_cast<unsigned>(arg
.getFirst());
150 if (arg
.getSecond() != D
)
151 *result_listener
<< "distance =" << arg
.getSecond();
152 return arg
.getFirst() == File
&& arg
.getSecond() == D
;
155 TEST_F(HeadersTest
, CollectRewrittenAndResolved
) {
156 FS
.Files
[MainFile
] = R
"cpp(
157 #include "sub
/bar
.h
" // not shortest
159 std::string BarHeader
= testPath("sub/bar.h");
160 FS
.Files
[BarHeader
] = "";
162 auto Includes
= collectIncludes();
163 EXPECT_THAT(Includes
.MainFileIncludes
,
164 UnorderedElementsAre(
165 AllOf(written("\"sub/bar.h\""), resolved(BarHeader
))));
166 EXPECT_THAT(Includes
.includeDepth(getID(MainFile
, Includes
)),
167 UnorderedElementsAre(Distance(getID(MainFile
, Includes
), 0u),
168 Distance(getID(BarHeader
, Includes
), 1u)));
171 TEST_F(HeadersTest
, OnlyCollectInclusionsInMain
) {
172 std::string BazHeader
= testPath("sub/baz.h");
173 FS
.Files
[BazHeader
] = "";
174 std::string BarHeader
= testPath("sub/bar.h");
175 FS
.Files
[BarHeader
] = R
"cpp(
178 FS
.Files
[MainFile
] = R
"cpp(
181 auto Includes
= collectIncludes();
183 Includes
.MainFileIncludes
,
184 UnorderedElementsAre(AllOf(written("\"bar.h\""), resolved(BarHeader
))));
185 EXPECT_THAT(Includes
.includeDepth(getID(MainFile
, Includes
)),
186 UnorderedElementsAre(Distance(getID(MainFile
, Includes
), 0u),
187 Distance(getID(BarHeader
, Includes
), 1u),
188 Distance(getID(BazHeader
, Includes
), 2u)));
189 // includeDepth() also works for non-main files.
190 EXPECT_THAT(Includes
.includeDepth(getID(BarHeader
, Includes
)),
191 UnorderedElementsAre(Distance(getID(BarHeader
, Includes
), 0u),
192 Distance(getID(BazHeader
, Includes
), 1u)));
195 TEST_F(HeadersTest
, PreambleIncludesPresentOnce
) {
196 // We use TestTU here, to ensure we use the preamble replay logic.
197 // We're testing that the logic doesn't crash, and doesn't result in duplicate
198 // includes. (We'd test more directly, but it's pretty well encapsulated!)
199 auto TU
= TestTU::withCode(R
"cpp(
206 TU
.HeaderFilename
= "a.h"; // suppress "not found".
207 EXPECT_THAT(TU
.build().getIncludeStructure().MainFileIncludes
,
208 ElementsAre(includeLine(1), includeLine(3), includeLine(5)));
211 TEST_F(HeadersTest
, UnResolvedInclusion
) {
212 FS
.Files
[MainFile
] = R
"cpp(
216 EXPECT_THAT(collectIncludes().MainFileIncludes
,
217 UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));
218 EXPECT_THAT(collectIncludes().IncludeChildren
, IsEmpty());
221 TEST_F(HeadersTest
, IncludedFilesGraph
) {
222 FS
.Files
[MainFile
] = R
"cpp(
226 std::string BarHeader
= testPath("bar.h");
227 FS
.Files
[BarHeader
] = "";
228 std::string FooHeader
= testPath("foo.h");
229 FS
.Files
[FooHeader
] = R
"cpp(
233 std::string BazHeader
= testPath("baz.h");
234 FS
.Files
[BazHeader
] = "";
236 auto Includes
= collectIncludes();
237 llvm::DenseMap
<IncludeStructure::HeaderID
,
238 SmallVector
<IncludeStructure::HeaderID
>>
239 Expected
= {{getID(MainFile
, Includes
),
240 {getID(BarHeader
, Includes
), getID(FooHeader
, Includes
)}},
241 {getID(FooHeader
, Includes
),
242 {getID(BarHeader
, Includes
), getID(BazHeader
, Includes
)}}};
243 EXPECT_EQ(Includes
.IncludeChildren
, Expected
);
246 TEST_F(HeadersTest
, IncludeDirective
) {
247 FS
.Files
[MainFile
] = R
"cpp(
250 #include_next "foo
.h
"
253 // ms-compatibility changes meaning of #import, make sure it is turned off.
254 CDB
.ExtraClangFlags
.push_back("-fno-ms-compatibility");
255 EXPECT_THAT(collectIncludes().MainFileIncludes
,
256 UnorderedElementsAre(directive(tok::pp_include
),
257 directive(tok::pp_import
),
258 directive(tok::pp_include_next
)));
261 TEST_F(HeadersTest
, IWYUPragmaKeep
) {
262 FS
.Files
[MainFile
] = R
"cpp(
263 #include "bar
.h
" // IWYU pragma: keep
268 collectIncludes().MainFileIncludes
,
269 UnorderedElementsAre(AllOf(written("\"foo.h\""), hasPragmaKeep(false)),
270 AllOf(written("\"bar.h\""), hasPragmaKeep(true))));
273 TEST_F(HeadersTest
, InsertInclude
) {
274 std::string Path
= testPath("sub/bar.h");
276 EXPECT_EQ(calculate(Path
), "\"bar.h\"");
279 TEST_F(HeadersTest
, DoNotInsertIfInSameFile
) {
280 MainFile
= testPath("main.h");
281 EXPECT_EQ(calculate(MainFile
), "");
284 TEST_F(HeadersTest
, DoNotInsertOffIncludePath
) {
285 MainFile
= testPath("sub/main.cpp");
286 EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");
289 TEST_F(HeadersTest
, ShortenIncludesInSearchPath
) {
290 std::string BarHeader
= testPath("sub/bar.h");
291 EXPECT_EQ(calculate(BarHeader
), "\"bar.h\"");
293 SearchDirArg
= (llvm::Twine("-I") + Subdir
+ "/..").str();
294 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
295 BarHeader
= testPath("sub/bar.h");
296 EXPECT_EQ(calculate(BarHeader
), "\"sub/bar.h\"");
299 TEST_F(HeadersTest
, ShortenedIncludeNotInSearchPath
) {
300 std::string BarHeader
=
301 llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h"));
302 EXPECT_EQ(calculate(BarHeader
, ""), "\"sub-2/bar.h\"");
305 TEST_F(HeadersTest
, PreferredHeader
) {
306 std::string BarHeader
= testPath("sub/bar.h");
307 EXPECT_EQ(calculate(BarHeader
, "<bar>"), "<bar>");
309 std::string BazHeader
= testPath("sub/baz.h");
310 EXPECT_EQ(calculate(BarHeader
, BazHeader
), "\"baz.h\"");
313 TEST_F(HeadersTest
, DontInsertDuplicatePreferred
) {
315 Inc
.Written
= "\"bar.h\"";
317 EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc
}), "");
318 EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc
}), "");
321 TEST_F(HeadersTest
, DontInsertDuplicateResolved
) {
323 Inc
.Written
= "fake-bar.h";
324 Inc
.Resolved
= testPath("sub/bar.h");
325 EXPECT_EQ(calculate(Inc
.Resolved
, "", {Inc
}), "");
326 // Do not insert preferred.
327 EXPECT_EQ(calculate(Inc
.Resolved
, "\"BAR.h\"", {Inc
}), "");
330 TEST_F(HeadersTest
, PreferInserted
) {
331 auto Edit
= insert("<y>");
333 EXPECT_TRUE(StringRef(Edit
->newText
).contains("<y>"));
336 TEST(Headers
, NoHeaderSearchInfo
) {
337 std::string MainFile
= testPath("main.cpp");
338 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
339 /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
341 auto HeaderPath
= testPath("sub/bar.h");
342 auto Inserting
= HeaderFile
{HeaderPath
, /*Verbatim=*/false};
343 auto Verbatim
= HeaderFile
{"<x>", /*Verbatim=*/true};
345 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, MainFile
),
346 std::string("\"sub/bar.h\""));
347 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Inserting
), false);
349 EXPECT_EQ(Inserter
.calculateIncludePath(Verbatim
, MainFile
),
351 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Verbatim
), true);
353 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, "sub2/main2.cpp"),
357 TEST_F(HeadersTest
, PresumedLocations
) {
358 std::string HeaderFile
= "__preamble_patch__.h";
360 // Line map inclusion back to main file.
361 std::string HeaderContents
=
362 llvm::formatv("#line 0 \"{0}\"", llvm::sys::path::filename(MainFile
));
363 HeaderContents
+= R
"cpp(
366 FS
.Files
[HeaderFile
] = HeaderContents
;
368 // Including through non-builtin file has no effects.
369 FS
.Files
[MainFile
] = "#include \"__preamble_patch__.h\"\n\n";
370 EXPECT_THAT(collectIncludes().MainFileIncludes
,
371 Not(Contains(written("<a.h>"))));
373 // Now include through built-in file.
374 CDB
.ExtraClangFlags
= {"-include", testPath(HeaderFile
)};
375 EXPECT_THAT(collectIncludes().MainFileIncludes
,
376 Contains(AllOf(includeLine(2), written("<a.h>"))));
379 TEST_F(HeadersTest
, SelfContainedHeaders
) {
380 // Including through non-builtin file has no effects.
381 FS
.Files
[MainFile
] = R
"cpp(
382 #include "includeguarded
.h
"
383 #include "nonguarded
.h
"
384 #include "pp_depend
.h
"
385 #include "pragmaguarded
.h
"
386 #include "recursive
.h
"
388 FS
.Files
["pragmaguarded.h"] = R
"cpp(
391 FS
.Files
["includeguarded.h"] = R
"cpp(
392 #ifndef INCLUDE_GUARDED_H
393 #define INCLUDE_GUARDED_H
395 #endif // INCLUDE_GUARDED_H
397 FS
.Files
["nonguarded.h"] = R
"cpp(
399 FS
.Files
["pp_depend.h"] = R
"cpp(
400 #ifndef REQUIRED_PP_DIRECTIVE
401 # error You have to have PP directive set to include this one!
404 FS
.Files
["recursive.h"] = R
"cpp(
408 #include "recursive
.h
"
410 #endif // RECURSIVE_H
413 auto Includes
= collectIncludes();
414 EXPECT_TRUE(Includes
.isSelfContained(getID("pragmaguarded.h", Includes
)));
415 EXPECT_TRUE(Includes
.isSelfContained(getID("includeguarded.h", Includes
)));
416 EXPECT_TRUE(Includes
.isSelfContained(getID("recursive.h", Includes
)));
417 EXPECT_FALSE(Includes
.isSelfContained(getID("nonguarded.h", Includes
)));
418 EXPECT_FALSE(Includes
.isSelfContained(getID("pp_depend.h", Includes
)));
421 TEST_F(HeadersTest
, HasIWYUPragmas
) {
422 FS
.Files
[MainFile
] = R
"cpp(
424 #include "begin_exports
.h
"
427 FS
.Files
["export.h"] = R
"cpp(
429 #include "none
.h
" // IWYU pragma: export
431 FS
.Files
["begin_exports.h"] = R
"cpp(
433 // IWYU pragma: begin_exports
435 // IWYU pragma: end_exports
437 FS
.Files
["none.h"] = R
"cpp(
442 auto Includes
= collectIncludes();
443 EXPECT_TRUE(Includes
.hasIWYUExport(getID("export.h", Includes
)));
444 EXPECT_TRUE(Includes
.hasIWYUExport(getID("begin_exports.h", Includes
)));
445 EXPECT_FALSE(Includes
.hasIWYUExport(getID("none.h", Includes
)));
449 } // namespace clangd