brintos

brintos / llvm-project-archived public Read only

0
0
Text · 15.6 KiB · 440582e Raw
450 lines · cpp
1//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===//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 "Headers.h"10 11#include "Compiler.h"12#include "Matchers.h"13#include "TestFS.h"14#include "TestTU.h"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"27#include <optional>28 29namespace clang {30namespace clangd {31namespace {32 33using ::testing::AllOf;34using ::testing::Contains;35using ::testing::ElementsAre;36using ::testing::IsEmpty;37using ::testing::Not;38using ::testing::UnorderedElementsAre;39 40class HeadersTest : public ::testing::Test {41public:42  HeadersTest() {43    CDB.ExtraClangFlags = {SearchDirArg.c_str()};44    FS.Files[MainFile] = "";45    // Make sure directory sub/ exists.46    FS.Files[testPath("sub/EMPTY")] = "";47  }48 49private:50  std::unique_ptr<CompilerInstance> setupClang() {51    auto Cmd = CDB.getCompileCommand(MainFile);52    assert(static_cast<bool>(Cmd));53 54    ParseInputs PI;55    PI.CompileCommand = *Cmd;56    PI.TFS = &FS;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);66 67    EXPECT_FALSE(Clang->getFrontendOpts().Inputs.empty());68    return Clang;69  }70 71protected: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);78  }79 80  IncludeStructure collectIncludes() {81    Clang = setupClang();82    PreprocessOnlyAction Action;83    EXPECT_TRUE(84        Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));85    IncludeStructure Includes;86    Includes.collect(*Clang);87    EXPECT_FALSE(Action.Execute());88    Action.EndSourceFile();89    return Includes;90  }91 92  // Calculates the include path, or returns "" on error or header should not be93  // inserted.94  std::string calculate(PathRef Original, PathRef Preferred = "",95                        const std::vector<Inclusion> &Inclusions = {}) {96    Clang = setupClang();97    PreprocessOnlyAction Action;98    EXPECT_TRUE(99        Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));100 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)};106    };107 108    IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),109                             CDB.getCompileCommand(MainFile)->Directory,110                             &Clang->getPreprocessor().getHeaderSearchInfo(),111                             QuotedHeaders, AngledHeaders);112    for (const auto &Inc : Inclusions)113      Inserter.addExisting(Inc);114    auto Inserted = ToHeaderFile(Preferred);115    if (!Inserter.shouldInsertInclude(Original, Inserted))116      return "";117    auto Path = Inserter.calculateIncludePath(Inserted, MainFile);118    Action.EndSourceFile();119    return Path.value_or("");120  }121 122  std::optional<TextEdit> insert(llvm::StringRef VerbatimHeader,123                                 tooling::IncludeDirective Directive) {124    Clang = setupClang();125    PreprocessOnlyAction Action;126    EXPECT_TRUE(127        Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));128 129    IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),130                             CDB.getCompileCommand(MainFile)->Directory,131                             &Clang->getPreprocessor().getHeaderSearchInfo(),132                             QuotedHeaders, AngledHeaders);133    auto Edit = Inserter.insert(VerbatimHeader, Directive);134    Action.EndSourceFile();135    return Edit;136  }137 138  MockFS FS;139  MockCompilationDatabase CDB;140  std::string MainFile = testPath("main.cpp");141  std::string Subdir = testPath("sub");142  std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str();143  IgnoringDiagConsumer IgnoreDiags;144  std::vector<std::function<bool(llvm::StringRef)>> QuotedHeaders;145  std::vector<std::function<bool(llvm::StringRef)>> AngledHeaders;146  std::unique_ptr<CompilerInstance> Clang;147};148 149MATCHER_P(written, Name, "") { return arg.Written == Name; }150MATCHER_P(resolved, Name, "") { return arg.Resolved == Name; }151MATCHER_P(includeLine, N, "") { return arg.HashLine == N; }152MATCHER_P(directive, D, "") { return arg.Directive == D; }153 154MATCHER_P2(Distance, File, D, "") {155  if (arg.getFirst() != File)156    *result_listener << "file =" << static_cast<unsigned>(arg.getFirst());157  if (arg.getSecond() != D)158    *result_listener << "distance =" << arg.getSecond();159  return arg.getFirst() == File && arg.getSecond() == D;160}161 162TEST_F(HeadersTest, CollectRewrittenAndResolved) {163  FS.Files[MainFile] = R"cpp(164#include "sub/bar.h" // not shortest165)cpp";166  std::string BarHeader = testPath("sub/bar.h");167  FS.Files[BarHeader] = "";168 169  auto Includes = collectIncludes();170  EXPECT_THAT(Includes.MainFileIncludes,171              UnorderedElementsAre(172                  AllOf(written("\"sub/bar.h\""), resolved(BarHeader))));173  EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),174              UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),175                                   Distance(getID(BarHeader, Includes), 1u)));176}177 178TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {179  std::string BazHeader = testPath("sub/baz.h");180  FS.Files[BazHeader] = "";181  std::string BarHeader = testPath("sub/bar.h");182  FS.Files[BarHeader] = R"cpp(183#include "baz.h"184)cpp";185  FS.Files[MainFile] = R"cpp(186#include "bar.h"187)cpp";188  auto Includes = collectIncludes();189  EXPECT_THAT(190      Includes.MainFileIncludes,191      UnorderedElementsAre(AllOf(written("\"bar.h\""), resolved(BarHeader))));192  EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),193              UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),194                                   Distance(getID(BarHeader, Includes), 1u),195                                   Distance(getID(BazHeader, Includes), 2u)));196  // includeDepth() also works for non-main files.197  EXPECT_THAT(Includes.includeDepth(getID(BarHeader, Includes)),198              UnorderedElementsAre(Distance(getID(BarHeader, Includes), 0u),199                                   Distance(getID(BazHeader, Includes), 1u)));200}201 202TEST_F(HeadersTest, CacheBySpellingIsBuiltForMainInclusions) {203  std::string FooHeader = testPath("foo.h");204  FS.Files[FooHeader] = R"cpp(205  void foo();206)cpp";207  std::string BarHeader = testPath("bar.h");208  FS.Files[BarHeader] = R"cpp(209  void bar();210)cpp";211  std::string BazHeader = testPath("baz.h");212  FS.Files[BazHeader] = R"cpp(213  void baz();214)cpp";215  FS.Files[MainFile] = R"cpp(216#include "foo.h"217#include "bar.h"218#include "baz.h"219)cpp";220  auto Includes = collectIncludes();221  EXPECT_THAT(Includes.MainFileIncludes,222              UnorderedElementsAre(written("\"foo.h\""), written("\"bar.h\""),223                                   written("\"baz.h\"")));224  EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"foo.h\""),225              UnorderedElementsAre(&Includes.MainFileIncludes[0]));226  EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"bar.h\""),227              UnorderedElementsAre(&Includes.MainFileIncludes[1]));228  EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"baz.h\""),229              UnorderedElementsAre(&Includes.MainFileIncludes[2]));230}231 232TEST_F(HeadersTest, PreambleIncludesPresentOnce) {233  // We use TestTU here, to ensure we use the preamble replay logic.234  // We're testing that the logic doesn't crash, and doesn't result in duplicate235  // includes. (We'd test more directly, but it's pretty well encapsulated!)236  auto TU = TestTU::withCode(R"cpp(237    #include "a.h"238 239    #include "a.h"240    void foo();241    #include "a.h"242  )cpp");243  TU.HeaderFilename = "a.h"; // suppress "not found".244  EXPECT_THAT(TU.build().getIncludeStructure().MainFileIncludes,245              ElementsAre(includeLine(1), includeLine(3), includeLine(5)));246}247 248TEST_F(HeadersTest, UnResolvedInclusion) {249  FS.Files[MainFile] = R"cpp(250#include "foo.h"251)cpp";252 253  EXPECT_THAT(collectIncludes().MainFileIncludes,254              UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));255  EXPECT_THAT(collectIncludes().IncludeChildren, IsEmpty());256}257 258TEST_F(HeadersTest, IncludedFilesGraph) {259  FS.Files[MainFile] = R"cpp(260#include "bar.h"261#include "foo.h"262)cpp";263  std::string BarHeader = testPath("bar.h");264  FS.Files[BarHeader] = "";265  std::string FooHeader = testPath("foo.h");266  FS.Files[FooHeader] = R"cpp(267#include "bar.h"268#include "baz.h"269)cpp";270  std::string BazHeader = testPath("baz.h");271  FS.Files[BazHeader] = "";272 273  auto Includes = collectIncludes();274  llvm::DenseMap<IncludeStructure::HeaderID,275                 SmallVector<IncludeStructure::HeaderID>>276      Expected = {{getID(MainFile, Includes),277                   {getID(BarHeader, Includes), getID(FooHeader, Includes)}},278                  {getID(FooHeader, Includes),279                   {getID(BarHeader, Includes), getID(BazHeader, Includes)}}};280  EXPECT_EQ(Includes.IncludeChildren, Expected);281}282 283TEST_F(HeadersTest, IncludeDirective) {284  FS.Files[MainFile] = R"cpp(285#include "foo.h"286#import "foo.h"287#include_next "foo.h"288)cpp";289 290  // ms-compatibility changes meaning of #import, make sure it is turned off.291  CDB.ExtraClangFlags.push_back("-fno-ms-compatibility");292  EXPECT_THAT(collectIncludes().MainFileIncludes,293              UnorderedElementsAre(directive(tok::pp_include),294                                   directive(tok::pp_import),295                                   directive(tok::pp_include_next)));296}297 298TEST_F(HeadersTest, SearchPath) {299  FS.Files["foo/bar.h"] = "x";300  FS.Files["foo/bar/baz.h"] = "y";301  CDB.ExtraClangFlags.push_back("-Ifoo/bar");302  CDB.ExtraClangFlags.push_back("-Ifoo/bar/..");303  EXPECT_THAT(collectIncludes().SearchPathsCanonical,304              ElementsAre(Subdir, testPath("foo/bar"), testPath("foo")));305}306 307TEST_F(HeadersTest, InsertInclude) {308  std::string Path = testPath("sub/bar.h");309  FS.Files[Path] = "";310  EXPECT_EQ(calculate(Path), "\"bar.h\"");311 312  AngledHeaders.push_back([](auto Path) { return true; });313  EXPECT_EQ(calculate(Path), "<bar.h>");314}315 316TEST_F(HeadersTest, DoNotInsertIfInSameFile) {317  MainFile = testPath("main.h");318  EXPECT_EQ(calculate(MainFile), "");319}320 321TEST_F(HeadersTest, DoNotInsertOffIncludePath) {322  MainFile = testPath("sub/main.cpp");323  EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");324}325 326TEST_F(HeadersTest, ShortenIncludesInSearchPath) {327  std::string BarHeader = testPath("sub/bar.h");328  EXPECT_EQ(calculate(BarHeader), "\"bar.h\"");329 330  SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str();331  CDB.ExtraClangFlags = {SearchDirArg.c_str()};332  BarHeader = testPath("sub/bar.h");333  EXPECT_EQ(calculate(BarHeader), "\"sub/bar.h\"");334}335 336TEST_F(HeadersTest, ShortenIncludesInSearchPathBracketed) {337  AngledHeaders.push_back([](auto Path) { return true; });338  std::string BarHeader = testPath("sub/bar.h");339  EXPECT_EQ(calculate(BarHeader), "<bar.h>");340 341  SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str();342  CDB.ExtraClangFlags = {SearchDirArg.c_str()};343  BarHeader = testPath("sub/bar.h");344  EXPECT_EQ(calculate(BarHeader), "<sub/bar.h>");345}346 347TEST_F(HeadersTest, ShortenIncludesInSearchPathBracketedFilterByFullPath) {348  // The filter receives the full path of the header, so it is able to filter by349  // the parent directory, even if it is part of the include search path350  AngledHeaders.push_back([](auto Path) {351    llvm::Regex Pattern("sub/.*");352    return Pattern.match(Path);353  });354  std::string BarHeader = testPath("sub/bar.h");355  EXPECT_EQ(calculate(BarHeader), "<bar.h>");356}357 358TEST_F(HeadersTest, ShortenedIncludeNotInSearchPath) {359  std::string BarHeader =360      llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h"));361  EXPECT_EQ(calculate(BarHeader, ""), "\"sub-2/bar.h\"");362}363 364TEST_F(HeadersTest, PreferredHeader) {365  std::string BarHeader = testPath("sub/bar.h");366  EXPECT_EQ(calculate(BarHeader, "<bar>"), "<bar>");367 368  std::string BazHeader = testPath("sub/baz.h");369  EXPECT_EQ(calculate(BarHeader, BazHeader), "\"baz.h\"");370 371  AngledHeaders.push_back([](auto Path) { return true; });372  std::string BiffHeader = testPath("sub/biff.h");373  EXPECT_EQ(calculate(BarHeader, BiffHeader), "<biff.h>");374}375 376TEST_F(HeadersTest, DontInsertDuplicatePreferred) {377  Inclusion Inc;378  Inc.Written = "\"bar.h\"";379  Inc.Resolved = "";380  EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc}), "");381  EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc}), "");382}383 384TEST_F(HeadersTest, DontInsertDuplicateResolved) {385  Inclusion Inc;386  Inc.Written = "fake-bar.h";387  Inc.Resolved = testPath("sub/bar.h");388  EXPECT_EQ(calculate(Inc.Resolved, "", {Inc}), "");389  // Do not insert preferred.390  EXPECT_EQ(calculate(Inc.Resolved, "\"BAR.h\"", {Inc}), "");391}392 393TEST_F(HeadersTest, PreferInserted) {394  auto Edit = insert("<y>", tooling::IncludeDirective::Include);395  ASSERT_TRUE(Edit);396  EXPECT_EQ(Edit->newText, "#include <y>\n");397 398  Edit = insert("\"header.h\"", tooling::IncludeDirective::Import);399  ASSERT_TRUE(Edit);400  EXPECT_EQ(Edit->newText, "#import \"header.h\"\n");401}402 403TEST(Headers, NoHeaderSearchInfo) {404  std::string MainFile = testPath("main.cpp");405  IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),406                           /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr,407                           /*QuotedHeaders=*/{}, /*AngledHeaders=*/{});408 409  auto HeaderPath = testPath("sub/bar.h");410  auto Inserting = HeaderFile{HeaderPath, /*Verbatim=*/false};411  auto Verbatim = HeaderFile{"<x>", /*Verbatim=*/true};412 413  EXPECT_EQ(Inserter.calculateIncludePath(Inserting, MainFile),414            std::string("\"sub/bar.h\""));415  EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Inserting), false);416 417  EXPECT_EQ(Inserter.calculateIncludePath(Verbatim, MainFile),418            std::string("<x>"));419  EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Verbatim), true);420 421  EXPECT_EQ(Inserter.calculateIncludePath(Inserting, "sub2/main2.cpp"),422            std::nullopt);423}424 425TEST_F(HeadersTest, PresumedLocations) {426  std::string HeaderFile = "__preamble_patch__.h";427 428  // Line map inclusion back to main file.429  std::string HeaderContents =430      llvm::formatv("#line 0 \"{0}\"", llvm::sys::path::filename(MainFile));431  HeaderContents += R"cpp(432#line 3433#include <a.h>)cpp";434  FS.Files[HeaderFile] = HeaderContents;435 436  // Including through non-builtin file has no effects.437  FS.Files[MainFile] = "#include \"__preamble_patch__.h\"\n\n";438  EXPECT_THAT(collectIncludes().MainFileIncludes,439              Not(Contains(written("<a.h>"))));440 441  // Now include through built-in file.442  CDB.ExtraClangFlags = {"-include", testPath(HeaderFile)};443  EXPECT_THAT(collectIncludes().MainFileIncludes,444              Contains(AllOf(includeLine(2), written("<a.h>"))));445}446 447} // namespace448} // namespace clangd449} // namespace clang450