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
;
36 using ::testing::IsEmpty
;
38 using ::testing::UnorderedElementsAre
;
40 class HeadersTest
: public ::testing::Test
{
43 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
44 FS
.Files
[MainFile
] = "";
45 // Make sure directory sub/ exists.
46 FS
.Files
[testPath("sub/EMPTY")] = "";
50 std::unique_ptr
<CompilerInstance
> setupClang() {
51 auto Cmd
= CDB
.getCompileCommand(MainFile
);
52 assert(static_cast<bool>(Cmd
));
55 PI
.CompileCommand
= *Cmd
;
57 auto CI
= buildCompilerInvocation(PI
, 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(Cmd
->Directory
);
62 auto Clang
= prepareCompilerInstance(
63 std::move(CI
), /*Preamble=*/nullptr,
64 llvm::MemoryBuffer::getMemBuffer(FS
.Files
[MainFile
], MainFile
),
65 std::move(VFS
), IgnoreDiags
);
67 EXPECT_FALSE(Clang
->getFrontendOpts().Inputs
.empty());
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
);
80 IncludeStructure
collectIncludes() {
82 PreprocessOnlyAction Action
;
84 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
85 IncludeStructure Includes
;
86 Includes
.collect(*Clang
);
87 EXPECT_FALSE(Action
.Execute());
88 Action
.EndSourceFile();
92 // Calculates the include path, or returns "" on error or header should not be
94 std::string
calculate(PathRef Original
, PathRef Preferred
= "",
95 const std::vector
<Inclusion
> &Inclusions
= {}) {
97 PreprocessOnlyAction Action
;
99 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
101 if (Preferred
.empty())
102 Preferred
= Original
;
103 auto ToHeaderFile
= [](llvm::StringRef Header
) {
104 return HeaderFile
{std::string(Header
),
105 /*Verbatim=*/!llvm::sys::path::is_absolute(Header
)};
108 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
109 CDB
.getCompileCommand(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(Original
, Inserted
))
116 auto Path
= Inserter
.calculateIncludePath(Inserted
, MainFile
);
117 Action
.EndSourceFile();
118 return Path
.value_or("");
121 std::optional
<TextEdit
> insert(llvm::StringRef VerbatimHeader
,
122 tooling::IncludeDirective Directive
) {
123 Clang
= setupClang();
124 PreprocessOnlyAction Action
;
126 Action
.BeginSourceFile(*Clang
, Clang
->getFrontendOpts().Inputs
[0]));
128 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
129 CDB
.getCompileCommand(MainFile
)->Directory
,
130 &Clang
->getPreprocessor().getHeaderSearchInfo());
131 auto Edit
= Inserter
.insert(VerbatimHeader
, Directive
);
132 Action
.EndSourceFile();
137 MockCompilationDatabase CDB
;
138 std::string MainFile
= testPath("main.cpp");
139 std::string Subdir
= testPath("sub");
140 std::string SearchDirArg
= (llvm::Twine("-I") + Subdir
).str();
141 IgnoringDiagConsumer IgnoreDiags
;
142 std::unique_ptr
<CompilerInstance
> Clang
;
145 MATCHER_P(written
, Name
, "") { return arg
.Written
== Name
; }
146 MATCHER_P(resolved
, Name
, "") { return arg
.Resolved
== Name
; }
147 MATCHER_P(includeLine
, N
, "") { return arg
.HashLine
== N
; }
148 MATCHER_P(directive
, D
, "") { return arg
.Directive
== D
; }
150 MATCHER_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
;
158 TEST_F(HeadersTest
, CollectRewrittenAndResolved
) {
159 FS
.Files
[MainFile
] = R
"cpp(
160 #include "sub
/bar
.h
" // not shortest
162 std::string BarHeader
= testPath("sub/bar.h");
163 FS
.Files
[BarHeader
] = "";
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)));
174 TEST_F(HeadersTest
, OnlyCollectInclusionsInMain
) {
175 std::string BazHeader
= testPath("sub/baz.h");
176 FS
.Files
[BazHeader
] = "";
177 std::string BarHeader
= testPath("sub/bar.h");
178 FS
.Files
[BarHeader
] = R
"cpp(
181 FS
.Files
[MainFile
] = R
"cpp(
184 auto Includes
= collectIncludes();
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)));
198 TEST_F(HeadersTest
, CacheBySpellingIsBuiltForMainInclusions
) {
199 std::string FooHeader
= testPath("foo.h");
200 FS
.Files
[FooHeader
] = R
"cpp(
203 std::string BarHeader
= testPath("bar.h");
204 FS
.Files
[BarHeader
] = R
"cpp(
207 std::string BazHeader
= testPath("baz.h");
208 FS
.Files
[BazHeader
] = R
"cpp(
211 FS
.Files
[MainFile
] = R
"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]));
228 TEST_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(R
"cpp(
239 TU
.HeaderFilename
= "a.h"; // suppress "not found".
240 EXPECT_THAT(TU
.build().getIncludeStructure().MainFileIncludes
,
241 ElementsAre(includeLine(1), includeLine(3), includeLine(5)));
244 TEST_F(HeadersTest
, UnResolvedInclusion
) {
245 FS
.Files
[MainFile
] = R
"cpp(
249 EXPECT_THAT(collectIncludes().MainFileIncludes
,
250 UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));
251 EXPECT_THAT(collectIncludes().IncludeChildren
, IsEmpty());
254 TEST_F(HeadersTest
, IncludedFilesGraph
) {
255 FS
.Files
[MainFile
] = R
"cpp(
259 std::string BarHeader
= testPath("bar.h");
260 FS
.Files
[BarHeader
] = "";
261 std::string FooHeader
= testPath("foo.h");
262 FS
.Files
[FooHeader
] = R
"cpp(
266 std::string BazHeader
= testPath("baz.h");
267 FS
.Files
[BazHeader
] = "";
269 auto Includes
= collectIncludes();
270 llvm::DenseMap
<IncludeStructure::HeaderID
,
271 SmallVector
<IncludeStructure::HeaderID
>>
272 Expected
= {{getID(MainFile
, Includes
),
273 {getID(BarHeader
, Includes
), getID(FooHeader
, Includes
)}},
274 {getID(FooHeader
, Includes
),
275 {getID(BarHeader
, Includes
), getID(BazHeader
, Includes
)}}};
276 EXPECT_EQ(Includes
.IncludeChildren
, Expected
);
279 TEST_F(HeadersTest
, IncludeDirective
) {
280 FS
.Files
[MainFile
] = R
"cpp(
283 #include_next "foo
.h
"
286 // ms-compatibility changes meaning of #import, make sure it is turned off.
287 CDB
.ExtraClangFlags
.push_back("-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
)));
294 TEST_F(HeadersTest
, SearchPath
) {
295 FS
.Files
["foo/bar.h"] = "x";
296 FS
.Files
["foo/bar/baz.h"] = "y";
297 CDB
.ExtraClangFlags
.push_back("-Ifoo/bar");
298 CDB
.ExtraClangFlags
.push_back("-Ifoo/bar/..");
299 EXPECT_THAT(collectIncludes().SearchPathsCanonical
,
300 ElementsAre(Subdir
, testPath("foo/bar"), testPath("foo")));
303 TEST_F(HeadersTest
, InsertInclude
) {
304 std::string Path
= testPath("sub/bar.h");
306 EXPECT_EQ(calculate(Path
), "\"bar.h\"");
309 TEST_F(HeadersTest
, DoNotInsertIfInSameFile
) {
310 MainFile
= testPath("main.h");
311 EXPECT_EQ(calculate(MainFile
), "");
314 TEST_F(HeadersTest
, DoNotInsertOffIncludePath
) {
315 MainFile
= testPath("sub/main.cpp");
316 EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");
319 TEST_F(HeadersTest
, ShortenIncludesInSearchPath
) {
320 std::string BarHeader
= testPath("sub/bar.h");
321 EXPECT_EQ(calculate(BarHeader
), "\"bar.h\"");
323 SearchDirArg
= (llvm::Twine("-I") + Subdir
+ "/..").str();
324 CDB
.ExtraClangFlags
= {SearchDirArg
.c_str()};
325 BarHeader
= testPath("sub/bar.h");
326 EXPECT_EQ(calculate(BarHeader
), "\"sub/bar.h\"");
329 TEST_F(HeadersTest
, ShortenedIncludeNotInSearchPath
) {
330 std::string BarHeader
=
331 llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h"));
332 EXPECT_EQ(calculate(BarHeader
, ""), "\"sub-2/bar.h\"");
335 TEST_F(HeadersTest
, PreferredHeader
) {
336 std::string BarHeader
= testPath("sub/bar.h");
337 EXPECT_EQ(calculate(BarHeader
, "<bar>"), "<bar>");
339 std::string BazHeader
= testPath("sub/baz.h");
340 EXPECT_EQ(calculate(BarHeader
, BazHeader
), "\"baz.h\"");
343 TEST_F(HeadersTest
, DontInsertDuplicatePreferred
) {
345 Inc
.Written
= "\"bar.h\"";
347 EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc
}), "");
348 EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc
}), "");
351 TEST_F(HeadersTest
, DontInsertDuplicateResolved
) {
353 Inc
.Written
= "fake-bar.h";
354 Inc
.Resolved
= testPath("sub/bar.h");
355 EXPECT_EQ(calculate(Inc
.Resolved
, "", {Inc
}), "");
356 // Do not insert preferred.
357 EXPECT_EQ(calculate(Inc
.Resolved
, "\"BAR.h\"", {Inc
}), "");
360 TEST_F(HeadersTest
, PreferInserted
) {
361 auto Edit
= insert("<y>", tooling::IncludeDirective::Include
);
363 EXPECT_EQ(Edit
->newText
, "#include <y>\n");
365 Edit
= insert("\"header.h\"", tooling::IncludeDirective::Import
);
367 EXPECT_EQ(Edit
->newText
, "#import \"header.h\"\n");
370 TEST(Headers
, NoHeaderSearchInfo
) {
371 std::string MainFile
= testPath("main.cpp");
372 IncludeInserter
Inserter(MainFile
, /*Code=*/"", format::getLLVMStyle(),
373 /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
375 auto HeaderPath
= testPath("sub/bar.h");
376 auto Inserting
= HeaderFile
{HeaderPath
, /*Verbatim=*/false};
377 auto Verbatim
= HeaderFile
{"<x>", /*Verbatim=*/true};
379 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, MainFile
),
380 std::string("\"sub/bar.h\""));
381 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Inserting
), false);
383 EXPECT_EQ(Inserter
.calculateIncludePath(Verbatim
, MainFile
),
385 EXPECT_EQ(Inserter
.shouldInsertInclude(HeaderPath
, Verbatim
), true);
387 EXPECT_EQ(Inserter
.calculateIncludePath(Inserting
, "sub2/main2.cpp"),
391 TEST_F(HeadersTest
, PresumedLocations
) {
392 std::string HeaderFile
= "__preamble_patch__.h";
394 // Line map inclusion back to main file.
395 std::string HeaderContents
=
396 llvm::formatv("#line 0 \"{0}\"", llvm::sys::path::filename(MainFile
));
397 HeaderContents
+= R
"cpp(
400 FS
.Files
[HeaderFile
] = HeaderContents
;
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>"))));
407 // Now include through built-in file.
408 CDB
.ExtraClangFlags
= {"-include", testPath(HeaderFile
)};
409 EXPECT_THAT(collectIncludes().MainFileIncludes
,
410 Contains(AllOf(includeLine(2), written("<a.h>"))));
414 } // namespace clangd