683 lines · cpp
1//===--- FindHeadersTest.cpp ----------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 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/Expr.h"15#include "clang/AST/RecursiveASTVisitor.h"16#include "clang/Basic/FileEntry.h"17#include "clang/Basic/FileManager.h"18#include "clang/Basic/LLVM.h"19#include "clang/Frontend/FrontendActions.h"20#include "clang/Testing/TestAST.h"21#include "clang/Tooling/Inclusions/StandardLibrary.h"22#include "llvm/ADT/SmallVector.h"23#include "llvm/ADT/StringRef.h"24#include "gmock/gmock.h"25#include "gtest/gtest.h"26#include <cassert>27#include <memory>28 29namespace clang::include_cleaner {30namespace {31using testing::ElementsAre;32using testing::UnorderedElementsAre;33 34std::string guard(llvm::StringRef Code) {35 return "#pragma once\n" + Code.str();36}37 38class FindHeadersTest : public testing::Test {39protected:40 TestInputs Inputs;41 PragmaIncludes PI;42 std::unique_ptr<TestAST> AST;43 FindHeadersTest() {44 Inputs.MakeAction = [this] {45 struct Hook : public SyntaxOnlyAction {46 public:47 Hook(PragmaIncludes *Out) : Out(Out) {}48 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {49 Out->record(CI);50 return true;51 }52 53 PragmaIncludes *Out;54 };55 return std::make_unique<Hook>(&PI);56 };57 }58 void buildAST() { AST = std::make_unique<TestAST>(Inputs); }59 60 llvm::SmallVector<Hinted<Header>> findHeaders(llvm::StringRef FileName) {61 return include_cleaner::findHeaders(62 AST->sourceManager().translateFileLineCol(63 *AST->fileManager().getOptionalFileRef(FileName),64 /*Line=*/1, /*Col=*/1),65 AST->sourceManager(), &PI);66 }67 FileEntryRef physicalHeader(llvm::StringRef FileName) {68 return *AST->fileManager().getOptionalFileRef(FileName);69 };70};71 72TEST_F(FindHeadersTest, IWYUPrivateToPublic) {73 Inputs.Code = R"cpp(74 #include "private.h"75 )cpp";76 Inputs.ExtraFiles["private.h"] = guard(R"cpp(77 // IWYU pragma: private, include "path/public.h"78 )cpp");79 buildAST();80 EXPECT_THAT(findHeaders("private.h"),81 UnorderedElementsAre(physicalHeader("private.h"),82 Header("\"path/public.h\"")));83}84 85TEST_F(FindHeadersTest, IWYUExport) {86 Inputs.Code = R"cpp(87 #include "exporter.h"88 )cpp";89 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(90 #include "exported1.h" // IWYU pragma: export91 92 // IWYU pragma: begin_exports93 #include "exported2.h"94 // IWYU pragma: end_exports95 96 #include "normal.h"97 )cpp");98 Inputs.ExtraFiles["exported1.h"] = guard("");99 Inputs.ExtraFiles["exported2.h"] = guard("");100 Inputs.ExtraFiles["normal.h"] = guard("");101 102 buildAST();103 EXPECT_THAT(findHeaders("exported1.h"),104 UnorderedElementsAre(physicalHeader("exported1.h"),105 physicalHeader("exporter.h")));106 EXPECT_THAT(findHeaders("exported2.h"),107 UnorderedElementsAre(physicalHeader("exported2.h"),108 physicalHeader("exporter.h")));109 EXPECT_THAT(findHeaders("normal.h"),110 UnorderedElementsAre(physicalHeader("normal.h")));111 EXPECT_THAT(findHeaders("exporter.h"),112 UnorderedElementsAre(physicalHeader("exporter.h")));113}114 115TEST_F(FindHeadersTest, IWYUExportForStandardHeaders) {116 Inputs.Code = R"cpp(117 #include "exporter.h"118 )cpp";119 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(120 #include <string> // IWYU pragma: export121 )cpp");122 Inputs.ExtraFiles["string"] = guard("");123 Inputs.ExtraArgs.push_back("-isystem.");124 buildAST();125 tooling::stdlib::Symbol StdString =126 *tooling::stdlib::Symbol::named("std::", "string");127 EXPECT_THAT(128 include_cleaner::findHeaders(StdString, AST->sourceManager(), &PI),129 UnorderedElementsAre(physicalHeader("exporter.h"), StdString.header()));130}131 132TEST_F(FindHeadersTest, SelfContained) {133 Inputs.Code = R"cpp(134 #include "header.h"135 )cpp";136 Inputs.ExtraFiles["header.h"] = guard(R"cpp(137 #include "fragment.inc"138 )cpp");139 Inputs.ExtraFiles["fragment.inc"] = "";140 buildAST();141 EXPECT_THAT(findHeaders("fragment.inc"),142 UnorderedElementsAre(physicalHeader("fragment.inc"),143 physicalHeader("header.h")));144}145 146TEST_F(FindHeadersTest, NonSelfContainedTraversePrivate) {147 Inputs.Code = R"cpp(148 #include "header.h"149 )cpp";150 Inputs.ExtraFiles["header.h"] = guard(R"cpp(151 #include "fragment.inc"152 )cpp");153 Inputs.ExtraFiles["fragment.inc"] = R"cpp(154 // IWYU pragma: private, include "public.h"155 )cpp";156 157 buildAST();158 // There is a IWYU private mapping in the non self-contained header, verify159 // that we don't emit its includer.160 EXPECT_THAT(findHeaders("fragment.inc"),161 UnorderedElementsAre(physicalHeader("fragment.inc"),162 Header("\"public.h\"")));163}164 165TEST_F(FindHeadersTest, NonSelfContainedTraverseExporter) {166 Inputs.Code = R"cpp(167 #include "exporter.h"168 )cpp";169 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(170 #include "exported.h" // IWYU pragma: export171 )cpp");172 Inputs.ExtraFiles["exported.h"] = guard(R"cpp(173 #include "fragment.inc"174 )cpp");175 Inputs.ExtraFiles["fragment.inc"] = "";176 buildAST();177 // Verify that we emit exporters for each header on the path.178 EXPECT_THAT(findHeaders("fragment.inc"),179 UnorderedElementsAre(physicalHeader("fragment.inc"),180 physicalHeader("exported.h"),181 physicalHeader("exporter.h")));182}183 184TEST_F(FindHeadersTest, TargetIsExpandedFromMacroInHeader) {185 struct CustomVisitor : RecursiveASTVisitor<CustomVisitor> {186 const Decl *Out = nullptr;187 bool VisitNamedDecl(const NamedDecl *ND) {188 if (ND->getName() == "FLAG_foo" || ND->getName() == "Foo") {189 EXPECT_TRUE(Out == nullptr);190 Out = ND;191 }192 return true;193 }194 };195 196 struct {197 llvm::StringRef MacroHeader;198 llvm::StringRef DeclareHeader;199 } TestCases[] = {200 {/*MacroHeader=*/R"cpp(201 #define DEFINE_CLASS(name) class name {};202 )cpp",203 /*DeclareHeader=*/R"cpp(204 #include "macro.h"205 DEFINE_CLASS(Foo)206 )cpp"},207 {/*MacroHeader=*/R"cpp(208 #define DEFINE_Foo class Foo {};209 )cpp",210 /*DeclareHeader=*/R"cpp(211 #include "macro.h"212 DEFINE_Foo213 )cpp"},214 {/*MacroHeader=*/R"cpp(215 #define DECLARE_FLAGS(name) extern int FLAG_##name216 )cpp",217 /*DeclareHeader=*/R"cpp(218 #include "macro.h"219 DECLARE_FLAGS(foo);220 )cpp"},221 };222 223 for (const auto &T : TestCases) {224 Inputs.Code = R"cpp(#include "declare.h")cpp";225 Inputs.ExtraFiles["declare.h"] = guard(T.DeclareHeader);226 Inputs.ExtraFiles["macro.h"] = guard(T.MacroHeader);227 buildAST();228 229 CustomVisitor Visitor;230 Visitor.TraverseDecl(AST->context().getTranslationUnitDecl());231 232 auto Headers = clang::include_cleaner::findHeaders(233 Visitor.Out->getLocation(), AST->sourceManager(),234 /*PragmaIncludes=*/nullptr);235 EXPECT_THAT(Headers, UnorderedElementsAre(physicalHeader("declare.h")));236 }237}238 239MATCHER_P2(HintedHeader, Header, Hint, "") {240 return std::tie(arg.Hint, arg) == std::tie(Hint, Header);241}242 243TEST_F(FindHeadersTest, PublicHeaderHint) {244 Inputs.Code = R"cpp(245 #include "public.h"246 )cpp";247 Inputs.ExtraFiles["public.h"] = guard(R"cpp(248 #include "private.h"249 #include "private.inc"250 )cpp");251 Inputs.ExtraFiles["private.h"] = guard(R"cpp(252 // IWYU pragma: private253 )cpp");254 Inputs.ExtraFiles["private.inc"] = "";255 buildAST();256 // Non self-contained files and headers marked with IWYU private pragma257 // shouldn't have PublicHeader hint.258 EXPECT_THAT(259 findHeaders("private.inc"),260 UnorderedElementsAre(261 HintedHeader(physicalHeader("private.inc"), Hints::OriginHeader),262 HintedHeader(physicalHeader("public.h"), Hints::PublicHeader)));263 EXPECT_THAT(findHeaders("private.h"),264 UnorderedElementsAre(HintedHeader(physicalHeader("private.h"),265 Hints::OriginHeader)));266}267 268TEST_F(FindHeadersTest, PreferredHeaderHint) {269 Inputs.Code = R"cpp(270 #include "private.h"271 )cpp";272 Inputs.ExtraFiles["private.h"] = guard(R"cpp(273 // IWYU pragma: private, include "public.h"274 )cpp");275 buildAST();276 // Headers explicitly marked should've preferred signal.277 EXPECT_THAT(278 findHeaders("private.h"),279 UnorderedElementsAre(280 HintedHeader(physicalHeader("private.h"), Hints::OriginHeader),281 HintedHeader(Header("\"public.h\""),282 Hints::PreferredHeader | Hints::PublicHeader)));283}284 285class HeadersForSymbolTest : public FindHeadersTest {286protected:287 llvm::SmallVector<Header> headersFor(llvm::StringRef Name) {288 struct Visitor : public RecursiveASTVisitor<Visitor> {289 const NamedDecl *Out = nullptr;290 llvm::StringRef Name;291 Visitor(llvm::StringRef Name) : Name(Name) {}292 bool VisitNamedDecl(const NamedDecl *ND) {293 if (auto *TD = ND->getDescribedTemplate())294 ND = TD;295 296 if (ND->getName() == Name) {297 EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl())298 << "Found multiple matches for " << Name << ".";299 Out = cast<NamedDecl>(ND->getCanonicalDecl());300 }301 return true;302 }303 };304 Visitor V(Name);305 V.TraverseDecl(AST->context().getTranslationUnitDecl());306 if (!V.Out)307 ADD_FAILURE() << "Couldn't find any decls named " << Name << ".";308 assert(V.Out);309 return headersForSymbol(*V.Out, AST->preprocessor(), &PI);310 }311 llvm::SmallVector<Header> headersForFoo() { return headersFor("foo"); }312};313 314TEST_F(HeadersForSymbolTest, Deduplicates) {315 Inputs.Code = R"cpp(316 #include "foo.h"317 )cpp";318 Inputs.ExtraFiles["foo.h"] = guard(R"cpp(319 // IWYU pragma: private, include "foo.h"320 void foo();321 void foo();322 )cpp");323 buildAST();324 EXPECT_THAT(325 headersForFoo(),326 UnorderedElementsAre(physicalHeader("foo.h"),327 // FIXME: de-duplicate across different kinds.328 Header("\"foo.h\"")));329}330 331TEST_F(HeadersForSymbolTest, RankByName) {332 Inputs.Code = R"cpp(333 #include "fox.h"334 #include "bar.h"335 )cpp";336 Inputs.ExtraFiles["fox.h"] = guard(R"cpp(337 void foo();338 )cpp");339 Inputs.ExtraFiles["bar.h"] = guard(R"cpp(340 void foo();341 )cpp");342 buildAST();343 EXPECT_THAT(headersForFoo(),344 ElementsAre(physicalHeader("bar.h"), physicalHeader("fox.h")));345}346 347TEST_F(HeadersForSymbolTest, Ranking) {348 // Sorting is done over (public, complete, canonical, origin)-tuple.349 Inputs.Code = R"cpp(350 #include "private.h"351 #include "public.h"352 #include "public_complete.h"353 #include "exporter.h"354 )cpp";355 Inputs.ExtraFiles["public.h"] = guard(R"cpp(356 struct foo;357 )cpp");358 Inputs.ExtraFiles["private.h"] = guard(R"cpp(359 // IWYU pragma: private, include "canonical.h"360 struct foo;361 )cpp");362 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(363 #include "private.h" // IWYU pragma: export364 )cpp");365 Inputs.ExtraFiles["public_complete.h"] = guard("struct foo {};");366 buildAST();367 EXPECT_THAT(headersForFoo(),368 ElementsAre(physicalHeader("public_complete.h"),369 Header("\"canonical.h\""), physicalHeader("public.h"),370 physicalHeader("exporter.h"),371 physicalHeader("private.h")));372}373 374TEST_F(HeadersForSymbolTest, PreferPublicOverComplete) {375 Inputs.Code = R"cpp(376 #include "complete_private.h"377 #include "public.h"378 )cpp";379 Inputs.ExtraFiles["complete_private.h"] = guard(R"cpp(380 // IWYU pragma: private381 struct foo {};382 )cpp");383 Inputs.ExtraFiles["public.h"] = guard("struct foo;");384 buildAST();385 EXPECT_THAT(headersForFoo(),386 ElementsAre(physicalHeader("public.h"),387 physicalHeader("complete_private.h")));388}389 390TEST_F(HeadersForSymbolTest, PreferNameMatch) {391 Inputs.Code = R"cpp(392 #include "public_complete.h"393 #include "test/foo.fwd.h"394 )cpp";395 Inputs.ExtraFiles["public_complete.h"] = guard("struct foo {};");396 Inputs.ExtraFiles["test/foo.fwd.h"] = guard("struct foo;");397 buildAST();398 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public_complete.h"),399 physicalHeader("test/foo.fwd.h")));400}401 402TEST_F(HeadersForSymbolTest, MainFile) {403 Inputs.Code = R"cpp(404 #include "public_complete.h"405 struct foo;406 )cpp";407 Inputs.ExtraFiles["public_complete.h"] = guard(R"cpp(408 struct foo {};409 )cpp");410 buildAST();411 auto &SM = AST->sourceManager();412 // FIXME: Symbols provided by main file should be treated specially.413 EXPECT_THAT(414 headersForFoo(),415 ElementsAre(physicalHeader("public_complete.h"),416 Header(*SM.getFileEntryRefForID(SM.getMainFileID()))));417}418 419TEST_F(HeadersForSymbolTest, PreferExporterOfPrivate) {420 Inputs.Code = R"cpp(421 #include "private.h"422 #include "exporter.h"423 )cpp";424 Inputs.ExtraFiles["private.h"] = guard(R"cpp(425 // IWYU pragma: private426 struct foo {};427 )cpp");428 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(429 #include "private.h" // IWYU pragma: export430 )cpp");431 buildAST();432 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h"),433 physicalHeader("private.h")));434}435 436TEST_F(HeadersForSymbolTest, ExporterIsDownRanked) {437 Inputs.Code = R"cpp(438 #include "exporter.h"439 #include "zoo.h"440 )cpp";441 // Deliberately named as zoo to make sure it doesn't get name-match boost and442 // also gets lexicographically bigger order than "exporter".443 Inputs.ExtraFiles["zoo.h"] = guard(R"cpp(444 struct foo {};445 )cpp");446 Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(447 #include "zoo.h" // IWYU pragma: export448 )cpp");449 buildAST();450 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h"),451 physicalHeader("exporter.h")));452}453 454TEST_F(HeadersForSymbolTest, PreferPublicOverNameMatchOnPrivate) {455 Inputs.Code = R"cpp(456 #include "foo.h"457 )cpp";458 Inputs.ExtraFiles["foo.h"] = guard(R"cpp(459 // IWYU pragma: private, include "public.h"460 struct foo {};461 )cpp");462 buildAST();463 EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"")),464 physicalHeader("foo.h")));465}466 467TEST_F(HeadersForSymbolTest, PublicOverPrivateWithoutUmbrella) {468 Inputs.Code = R"cpp(469 #include "bar.h"470 #include "foo.h"471 )cpp";472 Inputs.ExtraFiles["bar.h"] =473 guard(R"cpp(#include "foo.h" // IWYU pragma: export)cpp");474 Inputs.ExtraFiles["foo.h"] = guard(R"cpp(475 // IWYU pragma: private476 struct foo {};477 )cpp");478 buildAST();479 EXPECT_THAT(headersForFoo(),480 ElementsAre(physicalHeader("bar.h"), physicalHeader("foo.h")));481}482 483TEST_F(HeadersForSymbolTest, IWYUTransitiveExport) {484 Inputs.Code = R"cpp(485 #include "export1.h"486 )cpp";487 Inputs.ExtraFiles["export1.h"] = guard(R"cpp(488 #include "export2.h" // IWYU pragma: export489 )cpp");490 Inputs.ExtraFiles["export2.h"] = guard(R"cpp(491 #include "foo.h" // IWYU pragma: export492 )cpp");493 Inputs.ExtraFiles["foo.h"] = guard(R"cpp(494 struct foo {};495 )cpp");496 buildAST();497 EXPECT_THAT(headersForFoo(),498 ElementsAre(physicalHeader("foo.h"), physicalHeader("export1.h"),499 physicalHeader("export2.h")));500}501 502TEST_F(HeadersForSymbolTest, IWYUTransitiveExportWithPrivate) {503 Inputs.Code = R"cpp(504 #include "export1.h"505 void bar() { foo();}506 )cpp";507 Inputs.ExtraFiles["export1.h"] = guard(R"cpp(508 // IWYU pragma: private, include "public1.h"509 #include "export2.h" // IWYU pragma: export510 void foo();511 )cpp");512 Inputs.ExtraFiles["export2.h"] = guard(R"cpp(513 // IWYU pragma: private, include "public2.h"514 #include "export3.h" // IWYU pragma: export515 )cpp");516 Inputs.ExtraFiles["export3.h"] = guard(R"cpp(517 // IWYU pragma: private, include "public3.h"518 #include "foo.h" // IWYU pragma: export519 )cpp");520 Inputs.ExtraFiles["foo.h"] = guard(R"cpp(521 void foo();522 )cpp");523 buildAST();524 EXPECT_THAT(headersForFoo(),525 ElementsAre(physicalHeader("foo.h"),526 Header(StringRef("\"public1.h\"")),527 physicalHeader("export1.h"),528 physicalHeader("export2.h"),529 physicalHeader("export3.h")));530}531 532TEST_F(HeadersForSymbolTest, AmbiguousStdSymbols) {533 struct {534 llvm::StringRef Code;535 llvm::StringRef Name;536 537 llvm::StringRef ExpectedHeader;538 } TestCases[] = {539 {540 R"cpp(541 namespace std {542 template <typename InputIt, typename OutputIt>543 constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest);544 })cpp",545 "move",546 "<algorithm>",547 },548 {549 R"cpp(550 namespace std {551 template<class ExecutionPolicy, class ForwardIt1, class ForwardIt2>552 ForwardIt2 move(ExecutionPolicy&& policy,553 ForwardIt1 first, ForwardIt1 last, ForwardIt2 d_first);554 })cpp",555 "move",556 "<algorithm>",557 },558 {559 R"cpp(560 namespace std {561 template<typename T> constexpr T move(T&& t) noexcept;562 })cpp",563 "move",564 "<utility>",565 },566 {567 R"cpp(568 namespace std {569 template<class ForwardIt, class T>570 ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);571 })cpp",572 "remove",573 "<algorithm>",574 },575 {576 "namespace std { int remove(const char*); }",577 "remove",578 "<cstdio>",579 },580 };581 582 for (const auto &T : TestCases) {583 Inputs.Code = T.Code;584 buildAST();585 EXPECT_THAT(headersFor(T.Name),586 UnorderedElementsAre(587 Header(*tooling::stdlib::Header::named(T.ExpectedHeader))));588 }589}590 591TEST_F(HeadersForSymbolTest, AmbiguousStdSymbolsUsingShadow) {592 Inputs.Code = R"cpp(593 void remove(char*);594 namespace std { using ::remove; }595 596 void k() {597 std::remove("abc");598 }599 )cpp";600 buildAST();601 602 // Find the DeclRefExpr in the std::remove("abc") function call.603 struct Visitor : public RecursiveASTVisitor<Visitor> {604 const DeclRefExpr *Out = nullptr;605 bool VisitDeclRefExpr(const DeclRefExpr *DRE) {606 EXPECT_TRUE(Out == nullptr) << "Found multiple DeclRefExpr!";607 Out = DRE;608 return true;609 }610 };611 Visitor V;612 V.TraverseDecl(AST->context().getTranslationUnitDecl());613 ASSERT_TRUE(V.Out) << "Couldn't find a DeclRefExpr!";614 EXPECT_THAT(615 headersForSymbol(*(V.Out->getFoundDecl()), AST->preprocessor(), &PI),616 UnorderedElementsAre(617 Header(*tooling::stdlib::Header::named("<cstdio>"))));618}619 620TEST_F(HeadersForSymbolTest, StandardHeaders) {621 Inputs.Code = R"cpp(622 #include "stdlib_internal.h"623 void assert();624 void foo() { assert(); }625 )cpp";626 Inputs.ExtraFiles["stdlib_internal.h"] = "void assert();";627 buildAST();628 EXPECT_THAT(629 headersFor("assert"),630 // Respect the ordering from the stdlib mapping.631 // FIXME: Report physical locations too, stdlib_internal.h and main-file632 // should also be candidates. But they should be down-ranked compared to633 // stdlib providers.634 UnorderedElementsAre(tooling::stdlib::Header::named("<cassert>"),635 tooling::stdlib::Header::named("<assert.h>")));636}637 638TEST_F(HeadersForSymbolTest, StdlibLangForMacros) {639 Inputs.Code = R"cpp(640 #define EOF 0641 void foo() { EOF; }642 )cpp";643 {644 buildAST();645 const Macro Eof{AST->preprocessor().getIdentifierInfo("EOF"), {}};646 EXPECT_THAT(647 headersForSymbol(Eof, AST->preprocessor(), nullptr),648 UnorderedElementsAre(tooling::stdlib::Header::named("<cstdio>"),649 tooling::stdlib::Header::named("<stdio.h>")));650 }651 652 {653 Inputs.ExtraArgs.push_back("-xc");654 buildAST();655 const Macro Eof{AST->preprocessor().getIdentifierInfo("EOF"), {}};656 EXPECT_THAT(headersForSymbol(Eof, AST->preprocessor(), nullptr),657 UnorderedElementsAre(tooling::stdlib::Header::named(658 "<stdio.h>", tooling::stdlib::Lang::C)));659 }660}661 662TEST_F(HeadersForSymbolTest, ExporterNoNameMatch) {663 Inputs.Code = R"cpp(664 #include "exporter/foo.h"665 #include "foo_public.h"666 )cpp";667 Inputs.ExtraArgs.emplace_back("-I.");668 // Deliberately named as foo_public to make sure it doesn't get name-match669 // boost and also gets lexicographically bigger order than "exporter/foo.h".670 Inputs.ExtraFiles["foo_public.h"] = guard(R"cpp(671 struct foo {};672 )cpp");673 Inputs.ExtraFiles["exporter/foo.h"] = guard(R"cpp(674 #include "foo_public.h" // IWYU pragma: export675 )cpp");676 buildAST();677 EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("foo_public.h"),678 physicalHeader("exporter/foo.h")));679}680 681} // namespace682} // namespace clang::include_cleaner683