1 //===--- FindHeadersTest.cpp ----------------------------------------------===//
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 //===----------------------------------------------------------------------===//
9 #include "AnalysisInternal.h"
10 #include "TypesInternal.h"
11 #include "clang-include-cleaner/Analysis.h"
12 #include "clang-include-cleaner/Record.h"
13 #include "clang-include-cleaner/Types.h"
14 #include "clang/AST/RecursiveASTVisitor.h"
15 #include "clang/Basic/FileEntry.h"
16 #include "clang/Basic/FileManager.h"
17 #include "clang/Basic/LLVM.h"
18 #include "clang/Frontend/FrontendActions.h"
19 #include "clang/Testing/TestAST.h"
20 #include "clang/Tooling/Inclusions/StandardLibrary.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/ADT/StringRef.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
28 namespace clang::include_cleaner
{
30 using testing::ElementsAre
;
31 using testing::UnorderedElementsAre
;
33 std::string
guard(llvm::StringRef Code
) {
34 return "#pragma once\n" + Code
.str();
37 class FindHeadersTest
: public testing::Test
{
41 std::unique_ptr
<TestAST
> AST
;
43 Inputs
.MakeAction
= [this] {
44 struct Hook
: public SyntaxOnlyAction
{
46 Hook(PragmaIncludes
*Out
) : Out(Out
) {}
47 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
54 return std::make_unique
<Hook
>(&PI
);
57 void buildAST() { AST
= std::make_unique
<TestAST
>(Inputs
); }
59 llvm::SmallVector
<Hinted
<Header
>> findHeaders(llvm::StringRef FileName
) {
60 return include_cleaner::findHeaders(
61 AST
->sourceManager().translateFileLineCol(
62 AST
->fileManager().getFile(FileName
).get(),
63 /*Line=*/1, /*Col=*/1),
64 AST
->sourceManager(), &PI
);
66 const FileEntry
*physicalHeader(llvm::StringRef FileName
) {
67 return AST
->fileManager().getFile(FileName
).get();
71 TEST_F(FindHeadersTest
, IWYUPrivateToPublic
) {
75 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
76 // IWYU pragma: private, include "path
/public.h
"
79 EXPECT_THAT(findHeaders("private.h"),
80 UnorderedElementsAre(physicalHeader("private.h"),
81 Header("\"path/public.h\"")));
84 TEST_F(FindHeadersTest
, IWYUExport
) {
88 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
89 #include "exported1
.h
" // IWYU pragma: export
91 // IWYU pragma: begin_exports
92 #include "exported2
.h
"
93 // IWYU pragma: end_exports
97 Inputs
.ExtraFiles
["exported1.h"] = guard("");
98 Inputs
.ExtraFiles
["exported2.h"] = guard("");
99 Inputs
.ExtraFiles
["normal.h"] = guard("");
102 EXPECT_THAT(findHeaders("exported1.h"),
103 UnorderedElementsAre(physicalHeader("exported1.h"),
104 physicalHeader("exporter.h")));
105 EXPECT_THAT(findHeaders("exported2.h"),
106 UnorderedElementsAre(physicalHeader("exported2.h"),
107 physicalHeader("exporter.h")));
108 EXPECT_THAT(findHeaders("normal.h"),
109 UnorderedElementsAre(physicalHeader("normal.h")));
110 EXPECT_THAT(findHeaders("exporter.h"),
111 UnorderedElementsAre(physicalHeader("exporter.h")));
114 TEST_F(FindHeadersTest
, IWYUExportForStandardHeaders
) {
116 #include "exporter
.h
"
118 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
119 #include <string> // IWYU pragma: export
121 Inputs
.ExtraFiles
["string"] = guard("");
122 Inputs
.ExtraArgs
.push_back("-isystem.");
124 tooling::stdlib::Symbol StdString
=
125 *tooling::stdlib::Symbol::named("std::", "string");
127 include_cleaner::findHeaders(StdString
, AST
->sourceManager(), &PI
),
128 UnorderedElementsAre(physicalHeader("exporter.h"), StdString
.header()));
131 TEST_F(FindHeadersTest
, SelfContained
) {
135 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
136 #include "fragment
.inc
"
138 Inputs
.ExtraFiles
["fragment.inc"] = "";
140 EXPECT_THAT(findHeaders("fragment.inc"),
141 UnorderedElementsAre(physicalHeader("fragment.inc"),
142 physicalHeader("header.h")));
145 TEST_F(FindHeadersTest
, NonSelfContainedTraversePrivate
) {
149 Inputs
.ExtraFiles
["header.h"] = guard(R
"cpp(
150 #include "fragment
.inc
"
152 Inputs
.ExtraFiles
["fragment.inc"] = R
"cpp(
153 // IWYU pragma: private, include "public.h
"
157 // There is a IWYU private mapping in the non self-contained header, verify
158 // that we don't emit its includer.
159 EXPECT_THAT(findHeaders("fragment.inc"),
160 UnorderedElementsAre(physicalHeader("fragment.inc"),
161 Header("\"public.h\"")));
164 TEST_F(FindHeadersTest
, NonSelfContainedTraverseExporter
) {
166 #include "exporter
.h
"
168 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
169 #include "exported
.h
" // IWYU pragma: export
171 Inputs
.ExtraFiles
["exported.h"] = guard(R
"cpp(
172 #include "fragment
.inc
"
174 Inputs
.ExtraFiles
["fragment.inc"] = "";
176 // Verify that we emit exporters for each header on the path.
177 EXPECT_THAT(findHeaders("fragment.inc"),
178 UnorderedElementsAre(physicalHeader("fragment.inc"),
179 physicalHeader("exported.h"),
180 physicalHeader("exporter.h")));
183 TEST_F(FindHeadersTest
, TargetIsExpandedFromMacroInHeader
) {
184 struct CustomVisitor
: RecursiveASTVisitor
<CustomVisitor
> {
185 const Decl
*Out
= nullptr;
186 bool VisitNamedDecl(const NamedDecl
*ND
) {
187 if (ND
->getName() == "FLAG_foo" || ND
->getName() == "Foo") {
188 EXPECT_TRUE(Out
== nullptr);
196 llvm::StringRef MacroHeader
;
197 llvm::StringRef DeclareHeader
;
199 {/*MacroHeader=*/R
"cpp(
200 #define DEFINE_CLASS(name) class name {};
202 /*DeclareHeader=*/R
"cpp(
206 {/*MacroHeader=*/R
"cpp(
207 #define DEFINE_Foo class Foo {};
209 /*DeclareHeader=*/R
"cpp(
213 {/*MacroHeader=*/R
"cpp(
214 #define DECLARE_FLAGS(name) extern int FLAG_##name
216 /*DeclareHeader=*/R
"cpp(
222 for (const auto &T
: TestCases
) {
223 Inputs
.Code
= R
"cpp(#include "declare
.h
")cpp";
224 Inputs
.ExtraFiles
["declare.h"] = guard(T
.DeclareHeader
);
225 Inputs
.ExtraFiles
["macro.h"] = guard(T
.MacroHeader
);
228 CustomVisitor Visitor
;
229 Visitor
.TraverseDecl(AST
->context().getTranslationUnitDecl());
231 auto Headers
= clang::include_cleaner::findHeaders(
232 Visitor
.Out
->getLocation(), AST
->sourceManager(),
233 /*PragmaIncludes=*/nullptr);
234 EXPECT_THAT(Headers
, UnorderedElementsAre(physicalHeader("declare.h")));
238 MATCHER_P2(HintedHeader
, Header
, Hint
, "") {
239 return std::tie(arg
.Hint
, arg
) == std::tie(Hint
, Header
);
242 TEST_F(FindHeadersTest
, PublicHeaderHint
) {
246 Inputs
.ExtraFiles
["public.h"] = guard(R
"cpp(
248 #include "private.inc
"
250 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
251 // IWYU pragma: private
253 Inputs
.ExtraFiles
["private.inc"] = "";
255 // Non self-contained files and headers marked with IWYU private pragma
256 // shouldn't have PublicHeader hint.
258 findHeaders("private.inc"),
259 UnorderedElementsAre(
260 HintedHeader(physicalHeader("private.inc"), Hints::OriginHeader
),
261 HintedHeader(physicalHeader("public.h"), Hints::PublicHeader
)));
262 EXPECT_THAT(findHeaders("private.h"),
263 UnorderedElementsAre(HintedHeader(physicalHeader("private.h"),
264 Hints::OriginHeader
)));
267 TEST_F(FindHeadersTest
, PreferredHeaderHint
) {
271 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
272 // IWYU pragma: private, include "public.h
"
275 // Headers explicitly marked should've preferred signal.
277 findHeaders("private.h"),
278 UnorderedElementsAre(
279 HintedHeader(physicalHeader("private.h"), Hints::OriginHeader
),
280 HintedHeader(Header("\"public.h\""),
281 Hints::PreferredHeader
| Hints::PublicHeader
)));
284 class HeadersForSymbolTest
: public FindHeadersTest
{
286 llvm::SmallVector
<Header
> headersFor(llvm::StringRef Name
) {
287 struct Visitor
: public RecursiveASTVisitor
<Visitor
> {
288 const NamedDecl
*Out
= nullptr;
289 llvm::StringRef Name
;
290 Visitor(llvm::StringRef Name
) : Name(Name
) {}
291 bool VisitNamedDecl(const NamedDecl
*ND
) {
292 if (auto *TD
= ND
->getDescribedTemplate())
295 if (ND
->getName() == Name
) {
296 EXPECT_TRUE(Out
== nullptr || Out
== ND
->getCanonicalDecl())
297 << "Found multiple matches for " << Name
<< ".";
298 Out
= cast
<NamedDecl
>(ND
->getCanonicalDecl());
304 V
.TraverseDecl(AST
->context().getTranslationUnitDecl());
306 ADD_FAILURE() << "Couldn't find any decls named " << Name
<< ".";
308 return headersForSymbol(*V
.Out
, AST
->sourceManager(), &PI
);
310 llvm::SmallVector
<Header
> headersForFoo() { return headersFor("foo"); }
313 TEST_F(HeadersForSymbolTest
, Deduplicates
) {
317 Inputs
.ExtraFiles
["foo.h"] = guard(R
"cpp(
318 // IWYU pragma: private, include "foo
.h
"
325 UnorderedElementsAre(physicalHeader("foo.h"),
326 // FIXME: de-duplicate across different kinds.
327 Header("\"foo.h\"")));
330 TEST_F(HeadersForSymbolTest
, RankByName
) {
335 Inputs
.ExtraFiles
["fox.h"] = guard(R
"cpp(
338 Inputs
.ExtraFiles
["bar.h"] = guard(R
"cpp(
342 EXPECT_THAT(headersForFoo(),
343 ElementsAre(physicalHeader("bar.h"), physicalHeader("fox.h")));
346 TEST_F(HeadersForSymbolTest
, Ranking
) {
347 // Sorting is done over (canonical, public, complete, origin)-tuple.
351 #include "public_complete
.h
"
352 #include "exporter
.h
"
354 Inputs
.ExtraFiles
["public.h"] = guard(R
"cpp(
357 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
358 // IWYU pragma: private, include "canonical
.h
"
361 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
362 #include "private.h
" // IWYU pragma: export
364 Inputs
.ExtraFiles
["public_complete.h"] = guard("struct foo {};");
366 EXPECT_THAT(headersForFoo(), ElementsAre(Header("\"canonical.h\""),
367 physicalHeader("public_complete.h"),
368 physicalHeader("public.h"),
369 physicalHeader("exporter.h"),
370 physicalHeader("private.h")));
373 TEST_F(HeadersForSymbolTest
, PreferPublicOverComplete
) {
375 #include "complete_private
.h
"
378 Inputs
.ExtraFiles
["complete_private.h"] = guard(R
"cpp(
379 // IWYU pragma: private
382 Inputs
.ExtraFiles
["public.h"] = guard("struct foo;");
384 EXPECT_THAT(headersForFoo(),
385 ElementsAre(physicalHeader("public.h"),
386 physicalHeader("complete_private.h")));
389 TEST_F(HeadersForSymbolTest
, PreferNameMatch
) {
391 #include "public_complete
.h
"
392 #include "test
/foo
.fwd
.h
"
394 Inputs
.ExtraFiles
["public_complete.h"] = guard(R
"cpp(
397 Inputs
.ExtraFiles
["test/foo.fwd.h"] = guard("struct foo;");
399 EXPECT_THAT(headersForFoo(),
400 ElementsAre(physicalHeader("test/foo.fwd.h"),
401 physicalHeader("public_complete.h")));
404 TEST_F(HeadersForSymbolTest
, MainFile
) {
406 #include "public_complete
.h
"
409 Inputs
.ExtraFiles
["public_complete.h"] = guard(R
"cpp(
413 auto &SM
= AST
->sourceManager();
414 // FIXME: Symbols provided by main file should be treated specially.
415 EXPECT_THAT(headersForFoo(),
416 ElementsAre(physicalHeader("public_complete.h"),
417 Header(SM
.getFileEntryForID(SM
.getMainFileID()))));
420 TEST_F(HeadersForSymbolTest
, PreferExporterOfPrivate
) {
423 #include "exporter
.h
"
425 Inputs
.ExtraFiles
["private.h"] = guard(R
"cpp(
426 // IWYU pragma: private
429 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
430 #include "private.h
" // IWYU pragma: export
433 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h"),
434 physicalHeader("private.h")));
437 TEST_F(HeadersForSymbolTest
, ExporterIsDownRanked
) {
439 #include "exporter
.h
"
442 // Deliberately named as zoo to make sure it doesn't get name-match boost and
443 // also gets lexicographically bigger order than "exporter".
444 Inputs
.ExtraFiles
["zoo.h"] = guard(R
"cpp(
447 Inputs
.ExtraFiles
["exporter.h"] = guard(R
"cpp(
448 #include "zoo
.h
" // IWYU pragma: export
451 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h"),
452 physicalHeader("exporter.h")));
455 TEST_F(HeadersForSymbolTest
, PreferPublicOverNameMatchOnPrivate
) {
459 Inputs
.ExtraFiles
["foo.h"] = guard(R
"cpp(
460 // IWYU pragma: private, include "public.h
"
464 EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"")),
465 physicalHeader("foo.h")));
468 TEST_F(HeadersForSymbolTest
, AmbiguousStdSymbols
) {
470 llvm::StringRef Code
;
471 llvm::StringRef Name
;
473 llvm::StringRef ExpectedHeader
;
478 template <typename InputIt, typename OutputIt>
479 constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest);
487 template<typename T> constexpr T move(T&& t) noexcept;
495 template<class ForwardIt, class T>
496 ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);
502 "namespace std { int remove(const char*); }",
508 for (const auto &T
: TestCases
) {
509 Inputs
.Code
= T
.Code
;
511 EXPECT_THAT(headersFor(T
.Name
),
512 UnorderedElementsAre(
513 Header(*tooling::stdlib::Header::named(T
.ExpectedHeader
))));
517 TEST_F(HeadersForSymbolTest
, StandardHeaders
) {
518 Inputs
.Code
= "void assert();";
521 headersFor("assert"),
522 // Respect the ordering from the stdlib mapping.
523 UnorderedElementsAre(tooling::stdlib::Header::named("<cassert>"),
524 tooling::stdlib::Header::named("<assert.h>")));
528 } // namespace clang::include_cleaner