brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.1 KiB · 0ac2439 Raw
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