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 //===----------------------------------------------------------------------===//
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"
33 using ::testing::AllOf
;
34 using ::testing::Contains
;
35 using ::testing::ElementsAre
;
37 using ::testing::IsEmpty
;
39 using ::testing::UnorderedElementsAre
;
41 class HeadersTest
: public ::testing::Test
{
44 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
45 FS
.Files
[MainFile
] = "";
46 // Make sure directory sub/ exists.
47 FS
.Files
[testPath("sub/EMPTY")] = "";
51 std::unique_ptr
<CompilerInstance
> setupClang() {
52 auto Cmd
= CDB
.getCompileCommand(MainFile
);
53 assert(static_cast<bool>(Cmd
));
56 PI
.CompileCommand
= *Cmd
;
58 auto CI
= buildCompilerInvocation(PI
, IgnoreDiags
);
59 EXPECT_TRUE(static_cast<bool>(CI
));
60 // The diagnostic options must be set before creating a CompilerInstance.
61 CI
->getDiagnosticOpts().IgnoreWarnings
= true;
62 auto VFS
= PI
.TFS
->view(Cmd
->Directory
);
63 auto Clang
= prepareCompilerInstance(
64 std::move(CI
), /*Preamble=*/nullptr,
65 llvm::MemoryBuffer::getMemBuffer(FS
.Files
[MainFile
], MainFile
),
66 std::move(VFS
), IgnoreDiags
);
68 EXPECT_FALSE(Clang
->getFrontendOpts().Inputs
.empty());
73 IncludeStructure::HeaderID
getID(StringRef Filename
,
74 IncludeStructure
&Includes
) {
75 auto &SM
= Clang
->getSourceManager();
76 auto Entry
= SM
.getFileManager().getFileRef(Filename
);
77 EXPECT_THAT_EXPECTED(Entry
, llvm::Succeeded());
78 return Includes
.getOrCreateID(*Entry
);
81 IncludeStructure
collectIncludes() {
83 PreprocessOnlyAction Action
;
85 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
86 IncludeStructure Includes
;
87 Includes
.collect(*Clang
);
88 EXPECT_FALSE(Action
.Execute());
89 Action
.EndSourceFile();
93 // Calculates the include path, or returns "" on error or header should not be
95 std::string
calculate(PathRef Original
, PathRef Preferred
= "",
96 const std::vector
<Inclusion
> &Inclusions
= {}) {
98 PreprocessOnlyAction Action
;
100 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
102 if (Preferred
.empty())
103 Preferred
= Original
;
104 auto ToHeaderFile
= [](llvm::StringRef Header
) {
105 return HeaderFile
{std::string(Header
),
106 /*Verbatim=*/!llvm::sys::path::is_absolute(Header
)};
109 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
110 CDB
.getCompileCommand(MainFile
)->Directory
,
111 &Clang
->getPreprocessor().getHeaderSearchInfo());
112 for (const auto &Inc
: Inclusions
)
113 Inserter
.addExisting(Inc
);
114 auto Inserted
= ToHeaderFile(Preferred
);
115 if (!Inserter
.shouldInsertInclude(Original
, Inserted
))
117 auto Path
= Inserter
.calculateIncludePath(Inserted
, MainFile
);
118 Action
.EndSourceFile();
119 return Path
.value_or("");
122 std::optional
<TextEdit
> insert(llvm::StringRef VerbatimHeader
,
123 tooling::IncludeDirective Directive
) {
124 Clang
= setupClang();
125 PreprocessOnlyAction Action
;
127 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
129 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
130 CDB
.getCompileCommand(MainFile
)->Directory
,
131 &Clang
->getPreprocessor().getHeaderSearchInfo());
132 auto Edit
= Inserter
.insert(VerbatimHeader
, Directive
);
133 Action
.EndSourceFile();
138 MockCompilationDatabase CDB
;
139 std::string MainFile
= testPath("main.cpp");
140 std::string Subdir
= testPath("sub");
141 std::string SearchDirArg
= (llvm::Twine("-I") + Subdir
).str();
142 IgnoringDiagConsumer IgnoreDiags
;
143 std::unique_ptr
<CompilerInstance
> Clang
;
146 MATCHER_P(written
, Name
, "") { return arg
.Written
== Name
; }
147 MATCHER_P(resolved
, Name
, "") { return arg
.Resolved
== Name
; }
148 MATCHER_P(includeLine
, N
, "") { return arg
.HashLine
== N
; }
149 MATCHER_P(directive
, D
, "") { return arg
.Directive
== D
; }
151 MATCHER_P2(Distance
, File
, D
, "") {
152 if (arg
.getFirst() != File
)
153 *result_listener
<< "file =" << static_cast<unsigned>(arg
.getFirst());
154 if (arg
.getSecond() != D
)
155 *result_listener
<< "distance =" << arg
.getSecond();
156 return arg
.getFirst() == File
&& arg
.getSecond() == D
;
159 TEST_F(HeadersTest
, CollectRewrittenAndResolved
) {
160 FS
.Files
[MainFile
] = R
"cpp(
161 #include "sub
/bar
.h
" // not shortest
163 std::string BarHeader
= testPath("sub/bar.h");
164 FS
.Files
[BarHeader
] = "";
166 auto Includes
= collectIncludes();
167 EXPECT_THAT(Includes
.MainFileIncludes
,
168 UnorderedElementsAre(
169 AllOf(written("\"sub/bar.h\""), resolved(BarHeader
))));
170 EXPECT_THAT(Includes
.includeDepth(getID(MainFile
, Includes
)),
171 UnorderedElementsAre(Distance(getID(MainFile
, Includes
), 0u),
172 Distance(getID(BarHeader
, Includes
), 1u)));
175 TEST_F(HeadersTest
, OnlyCollectInclusionsInMain
) {
176 std::string BazHeader
= testPath("sub/baz.h");
177 FS
.Files
[BazHeader
] = "";
178 std::string BarHeader
= testPath("sub/bar.h");
179 FS
.Files
[BarHeader
] = R
"cpp(
182 FS
.Files
[MainFile
] = R
"cpp(
185 auto Includes
= collectIncludes();
187 Includes
.MainFileIncludes
,
188 UnorderedElementsAre(AllOf(written("\"bar.h\""), resolved(BarHeader
))));
189 EXPECT_THAT(Includes
.includeDepth(getID(MainFile
, Includes
)),
190 UnorderedElementsAre(Distance(getID(MainFile
, Includes
), 0u),
191 Distance(getID(BarHeader
, Includes
), 1u),
192 Distance(getID(BazHeader
, Includes
), 2u)));
193 // includeDepth() also works for non-main files.
194 EXPECT_THAT(Includes
.includeDepth(getID(BarHeader
, Includes
)),
195 UnorderedElementsAre(Distance(getID(BarHeader
, Includes
), 0u),
196 Distance(getID(BazHeader
, Includes
), 1u)));
199 TEST_F(HeadersTest
, CacheBySpellingIsBuiltForMainInclusions
) {
200 std::string FooHeader
= testPath("foo.h");
201 FS
.Files
[FooHeader
] = R
"cpp(
204 std::string BarHeader
= testPath("bar.h");
205 FS
.Files
[BarHeader
] = R
"cpp(
208 std::string BazHeader
= testPath("baz.h");
209 FS
.Files
[BazHeader
] = R
"cpp(
212 FS
.Files
[MainFile
] = R
"cpp(
217 auto Includes
= collectIncludes();
218 EXPECT_THAT(Includes
.MainFileIncludes
,
219 UnorderedElementsAre(written("\"foo.h\""), written("\"bar.h\""),
220 written("\"baz.h\"")));
221 EXPECT_THAT(Includes
.mainFileIncludesWithSpelling("\"foo.h\""),
222 UnorderedElementsAre(&Includes
.MainFileIncludes
[0]));
223 EXPECT_THAT(Includes
.mainFileIncludesWithSpelling("\"bar.h\""),
224 UnorderedElementsAre(&Includes
.MainFileIncludes
[1]));
225 EXPECT_THAT(Includes
.mainFileIncludesWithSpelling("\"baz.h\""),
226 UnorderedElementsAre(&Includes
.MainFileIncludes
[2]));
229 TEST_F(HeadersTest
, PreambleIncludesPresentOnce
) {
230 // We use TestTU here, to ensure we use the preamble replay logic.
231 // We're testing that the logic doesn't crash, and doesn't result in duplicate
232 // includes. (We'd test more directly, but it's pretty well encapsulated!)
233 auto TU
= TestTU::withCode(R
"cpp(
240 TU
.HeaderFilename
= "a.h"; // suppress "not found".
241 EXPECT_THAT(TU
.build().getIncludeStructure().MainFileIncludes
,
242 ElementsAre(includeLine(1), includeLine(3), includeLine(5)));
245 TEST_F(HeadersTest
, UnResolvedInclusion
) {
246 FS
.Files
[MainFile
] = R
"cpp(
250 EXPECT_THAT(collectIncludes().MainFileIncludes
,
251 UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));
252 EXPECT_THAT(collectIncludes().IncludeChildren
, IsEmpty());
255 TEST_F(HeadersTest
, IncludedFilesGraph
) {
256 FS
.Files
[MainFile
] = R
"cpp(
260 std::string BarHeader
= testPath("bar.h");
261 FS
.Files
[BarHeader
] = "";
262 std::string FooHeader
= testPath("foo.h");
263 FS
.Files
[FooHeader
] = R
"cpp(
267 std::string BazHeader
= testPath("baz.h");
268 FS
.Files
[BazHeader
] = "";
270 auto Includes
= collectIncludes();
271 llvm::DenseMap
<IncludeStructure::HeaderID
,
272 SmallVector
<IncludeStructure::HeaderID
>>
273 Expected
= {{getID(MainFile
, Includes
),
274 {getID(BarHeader
, Includes
), getID(FooHeader
, Includes
)}},
275 {getID(FooHeader
, Includes
),
276 {getID(BarHeader
, Includes
), getID(BazHeader
, Includes
)}}};
277 EXPECT_EQ(Includes
.IncludeChildren
, Expected
);
280 TEST_F(HeadersTest
, IncludeDirective
) {
281 FS
.Files
[MainFile
] = R
"cpp(
284 #include_next "foo
.h
"
287 // ms-compatibility changes meaning of #import, make sure it is turned off.
288 CDB
.ExtraClangFlags
.push_back("-fno-ms-compatibility");
289 EXPECT_THAT(collectIncludes().MainFileIncludes
,
290 UnorderedElementsAre(directive(tok::pp_include
),
291 directive(tok::pp_import
),
292 directive(tok::pp_include_next
)));
295 TEST_F(HeadersTest
, InsertInclude
) {
296 std::string Path
= testPath("sub/bar.h");
298 EXPECT_EQ(calculate(Path
), "\"bar.h\"");
301 TEST_F(HeadersTest
, DoNotInsertIfInSameFile
) {
302 MainFile
= testPath("main.h");
303 EXPECT_EQ(calculate(MainFile
), "");
306 TEST_F(HeadersTest
, DoNotInsertOffIncludePath
) {
307 MainFile
= testPath("sub/main.cpp");
308 EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");
311 TEST_F(HeadersTest
, ShortenIncludesInSearchPath
) {
312 std::string BarHeader
= testPath("sub/bar.h");
313 EXPECT_EQ(calculate(BarHeader
), "\"bar.h\"");
315 SearchDirArg
= (llvm::Twine("-I") + Subdir
+ "/..").str();
316 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
317 BarHeader
= testPath("sub/bar.h");
318 EXPECT_EQ(calculate(BarHeader
), "\"sub/bar.h\"");
321 TEST_F(HeadersTest
, ShortenedIncludeNotInSearchPath
) {
322 std::string BarHeader
=
323 llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h"));
324 EXPECT_EQ(calculate(BarHeader
, ""), "\"sub-2/bar.h\"");
327 TEST_F(HeadersTest
, PreferredHeader
) {
328 std::string BarHeader
= testPath("sub/bar.h");
329 EXPECT_EQ(calculate(BarHeader
, "<bar>"), "<bar>");
331 std::string BazHeader
= testPath("sub/baz.h");
332 EXPECT_EQ(calculate(BarHeader
, BazHeader
), "\"baz.h\"");
335 TEST_F(HeadersTest
, DontInsertDuplicatePreferred
) {
337 Inc
.Written
= "\"bar.h\"";
339 EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc
}), "");
340 EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc
}), "");
343 TEST_F(HeadersTest
, DontInsertDuplicateResolved
) {
345 Inc
.Written
= "fake-bar.h";
346 Inc
.Resolved
= testPath("sub/bar.h");
347 EXPECT_EQ(calculate(Inc
.Resolved
, "", {Inc
}), "");
348 // Do not insert preferred.
349 EXPECT_EQ(calculate(Inc
.Resolved
, "\"BAR.h\"", {Inc
}), "");
352 TEST_F(HeadersTest
, PreferInserted
) {
353 auto Edit
= insert("<y>", tooling::IncludeDirective::Include
);
355 EXPECT_EQ(Edit
->newText
, "#include <y>\n");
357 Edit
= insert("\"header.h\"", tooling::IncludeDirective::Import
);
359 EXPECT_EQ(Edit
->newText
, "#import \"header.h\"\n");
362 TEST(Headers
, NoHeaderSearchInfo
) {
363 std::string MainFile
= testPath("main.cpp");
364 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
365 /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
367 auto HeaderPath
= testPath("sub/bar.h");
368 auto Inserting
= HeaderFile
{HeaderPath
, /*Verbatim=*/false};
369 auto Verbatim
= HeaderFile
{"<x>", /*Verbatim=*/true};
371 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, MainFile
),
372 std::string("\"sub/bar.h\""));
373 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Inserting
), false);
375 EXPECT_EQ(Inserter
.calculateIncludePath(Verbatim
, MainFile
),
377 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Verbatim
), true);
379 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, "sub2/main2.cpp"),
383 TEST_F(HeadersTest
, PresumedLocations
) {
384 std::string HeaderFile
= "__preamble_patch__.h";
386 // Line map inclusion back to main file.
387 std::string HeaderContents
=
388 llvm::formatv("#line 0 \"{0}\"", llvm::sys::path::filename(MainFile
));
389 HeaderContents
+= R
"cpp(
392 FS
.Files
[HeaderFile
] = HeaderContents
;
394 // Including through non-builtin file has no effects.
395 FS
.Files
[MainFile
] = "#include \"__preamble_patch__.h\"\n\n";
396 EXPECT_THAT(collectIncludes().MainFileIncludes
,
397 Not(Contains(written("<a.h>"))));
399 // Now include through built-in file.
400 CDB
.ExtraClangFlags
= {"-include", testPath(HeaderFile
)};
401 EXPECT_THAT(collectIncludes().MainFileIncludes
,
402 Contains(AllOf(includeLine(2), written("<a.h>"))));
406 } // namespace clangd