brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.5 KiB · e600207 Raw
354 lines · cpp
1//===--- HeaderSourceSwitchTests.cpp - ---------------------------*- 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 "HeaderSourceSwitch.h"10 11#include "SyncAPI.h"12#include "TestFS.h"13#include "TestTU.h"14#include "index/MemIndex.h"15#include "support/Path.h"16#include "llvm/Testing/Support/SupportHelpers.h"17#include "gmock/gmock.h"18#include "gtest/gtest.h"19#include <optional>20 21namespace clang {22namespace clangd {23namespace {24 25TEST(HeaderSourceSwitchTest, FileHeuristic) {26  MockFS FS;27  auto FooCpp = testPath("foo.cpp");28  auto FooH = testPath("foo.h");29  auto Invalid = testPath("main.cpp");30 31  FS.Files[FooCpp];32  FS.Files[FooH];33  FS.Files[Invalid];34  std::optional<Path> PathResult =35      getCorrespondingHeaderOrSource(FooCpp, FS.view(std::nullopt));36  EXPECT_TRUE(PathResult.has_value());37  ASSERT_EQ(*PathResult, FooH);38 39  PathResult = getCorrespondingHeaderOrSource(FooH, FS.view(std::nullopt));40  EXPECT_TRUE(PathResult.has_value());41  ASSERT_EQ(*PathResult, FooCpp);42 43  // Test with header file in capital letters and different extension, source44  // file with different extension45  auto FooC = testPath("bar.c");46  auto FooHH = testPath("bar.HH");47 48  FS.Files[FooC];49  FS.Files[FooHH];50  PathResult = getCorrespondingHeaderOrSource(FooC, FS.view(std::nullopt));51  EXPECT_TRUE(PathResult.has_value());52  ASSERT_EQ(*PathResult, FooHH);53 54  // Test with both capital letters55  auto Foo2C = testPath("foo2.C");56  auto Foo2HH = testPath("foo2.HH");57  FS.Files[Foo2C];58  FS.Files[Foo2HH];59  PathResult = getCorrespondingHeaderOrSource(Foo2C, FS.view(std::nullopt));60  EXPECT_TRUE(PathResult.has_value());61  ASSERT_EQ(*PathResult, Foo2HH);62 63  // Test with source file as capital letter and .hxx header file64  auto Foo3C = testPath("foo3.C");65  auto Foo3HXX = testPath("foo3.hxx");66 67  FS.Files[Foo3C];68  FS.Files[Foo3HXX];69  PathResult = getCorrespondingHeaderOrSource(Foo3C, FS.view(std::nullopt));70  EXPECT_TRUE(PathResult.has_value());71  ASSERT_EQ(*PathResult, Foo3HXX);72 73  // Test if asking for a corresponding file that doesn't exist returns an empty74  // string.75  PathResult = getCorrespondingHeaderOrSource(Invalid, FS.view(std::nullopt));76  EXPECT_FALSE(PathResult.has_value());77}78 79TEST(HeaderSourceSwitchTest, ModuleInterfaces) {80  MockFS FS;81 82  auto FooCC = testPath("foo.cc");83  auto FooCPPM = testPath("foo.cppm");84  FS.Files[FooCC];85  FS.Files[FooCPPM];86  std::optional<Path> PathResult =87      getCorrespondingHeaderOrSource(FooCC, FS.view(std::nullopt));88  EXPECT_TRUE(PathResult.has_value());89  ASSERT_EQ(*PathResult, FooCPPM);90 91  auto Foo2CPP = testPath("foo2.cpp");92  auto Foo2CCM = testPath("foo2.ccm");93  FS.Files[Foo2CPP];94  FS.Files[Foo2CCM];95  PathResult = getCorrespondingHeaderOrSource(Foo2CPP, FS.view(std::nullopt));96  EXPECT_TRUE(PathResult.has_value());97  ASSERT_EQ(*PathResult, Foo2CCM);98 99  auto Foo3CXX = testPath("foo3.cxx");100  auto Foo3CXXM = testPath("foo3.cxxm");101  FS.Files[Foo3CXX];102  FS.Files[Foo3CXXM];103  PathResult = getCorrespondingHeaderOrSource(Foo3CXX, FS.view(std::nullopt));104  EXPECT_TRUE(PathResult.has_value());105  ASSERT_EQ(*PathResult, Foo3CXXM);106 107  auto Foo4CPLUSPLUS = testPath("foo4.c++");108  auto Foo4CPLUSPLUSM = testPath("foo4.c++m");109  FS.Files[Foo4CPLUSPLUS];110  FS.Files[Foo4CPLUSPLUSM];111  PathResult =112      getCorrespondingHeaderOrSource(Foo4CPLUSPLUS, FS.view(std::nullopt));113  EXPECT_TRUE(PathResult.has_value());114  ASSERT_EQ(*PathResult, Foo4CPLUSPLUSM);115}116 117MATCHER_P(declNamed, Name, "") {118  if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg))119    if (ND->getQualifiedNameAsString() == Name)120      return true;121  return false;122}123 124TEST(HeaderSourceSwitchTest, GetLocalDecls) {125  TestTU TU;126  TU.HeaderCode = R"cpp(127  void HeaderOnly();128  )cpp";129  TU.Code = R"cpp(130  void MainF1();131  class Foo {};132  namespace ns {133  class Foo {134    void method();135    int field;136  };137  } // namespace ns138 139  // Non-indexable symbols140  namespace {141  void Ignore1() {}142  }143 144  )cpp";145 146  auto AST = TU.build();147  EXPECT_THAT(getIndexableLocalDecls(AST),148              testing::UnorderedElementsAre(149                  declNamed("MainF1"), declNamed("Foo"), declNamed("ns::Foo"),150                  declNamed("ns::Foo::method"), declNamed("ns::Foo::field")));151}152 153TEST(HeaderSourceSwitchTest, FromHeaderToSource) {154  // build a proper index, which contains symbols:155  //   A_Sym1, declared in TestTU.h, defined in a.cpp156  //   B_Sym[1-2], declared in TestTU.h, defined in b.cpp157  SymbolSlab::Builder AllSymbols;158  TestTU Testing;159  Testing.HeaderFilename = "TestTU.h";160  Testing.HeaderCode = "void A_Sym1();";161  Testing.Filename = "a.cpp";162  Testing.Code = "void A_Sym1() {};";163  for (auto &Sym : Testing.headerSymbols())164    AllSymbols.insert(Sym);165 166  Testing.HeaderCode = R"cpp(167  void B_Sym1();168  void B_Sym2();169  void B_Sym3_NoDef();170  )cpp";171  Testing.Filename = "b.cpp";172  Testing.Code = R"cpp(173  void B_Sym1() {}174  void B_Sym2() {}175  )cpp";176  for (auto &Sym : Testing.headerSymbols())177    AllSymbols.insert(Sym);178  auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});179 180  // Test for switch from .h header to .cc source181  struct {182    llvm::StringRef HeaderCode;183    std::optional<std::string> ExpectedSource;184  } TestCases[] = {185      {"// empty, no header found", std::nullopt},186      {R"cpp(187         // no definition found in the index.188         void NonDefinition();189       )cpp",190       std::nullopt},191      {R"cpp(192         void A_Sym1();193       )cpp",194       testPath("a.cpp")},195      {R"cpp(196         // b.cpp wins.197         void A_Sym1();198         void B_Sym1();199         void B_Sym2();200       )cpp",201       testPath("b.cpp")},202      {R"cpp(203         // a.cpp and b.cpp have same scope, but a.cpp because "a.cpp" < "b.cpp".204         void A_Sym1();205         void B_Sym1();206       )cpp",207       testPath("a.cpp")},208 209      {R"cpp(210          // We don't have definition in the index, so stay in the header.211          void B_Sym3_NoDef();212       )cpp",213       std::nullopt},214  };215  for (const auto &Case : TestCases) {216    TestTU TU = TestTU::withCode(Case.HeaderCode);217    TU.Filename = "TestTU.h";218    TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.219    auto HeaderAST = TU.build();220    EXPECT_EQ(Case.ExpectedSource,221              getCorrespondingHeaderOrSource(testPath(TU.Filename), HeaderAST,222                                             Index.get()));223  }224}225 226TEST(HeaderSourceSwitchTest, FromSourceToHeader) {227  // build a proper index, which contains symbols:228  //   A_Sym1, declared in a.h, defined in TestTU.cpp229  //   B_Sym[1-2], declared in b.h, defined in TestTU.cpp230  TestTU TUForIndex = TestTU::withCode(R"cpp(231  #include "a.h"232  #include "b.h"233 234  void A_Sym1() {}235 236  void B_Sym1() {}237  void B_Sym2() {}238  )cpp");239  TUForIndex.AdditionalFiles["a.h"] = R"cpp(240  void A_Sym1();241  )cpp";242  TUForIndex.AdditionalFiles["b.h"] = R"cpp(243  void B_Sym1();244  void B_Sym2();245  )cpp";246  TUForIndex.Filename = "TestTU.cpp";247  auto Index = TUForIndex.index();248 249  // Test for switching from .cc source file to .h header.250  struct {251    llvm::StringRef SourceCode;252    std::optional<std::string> ExpectedResult;253  } TestCases[] = {254      {"// empty, no header found", std::nullopt},255      {R"cpp(256         // symbol not in index, no header found257         void Local() {}258       )cpp",259       std::nullopt},260 261      {R"cpp(262         // a.h wins.263         void A_Sym1() {}264       )cpp",265       testPath("a.h")},266 267      {R"cpp(268         // b.h wins.269         void A_Sym1() {}270         void B_Sym1() {}271         void B_Sym2() {}272       )cpp",273       testPath("b.h")},274 275      {R"cpp(276         // a.h and b.h have same scope, but a.h wins because "a.h" < "b.h".277         void A_Sym1() {}278         void B_Sym1() {}279       )cpp",280       testPath("a.h")},281  };282  for (const auto &Case : TestCases) {283    TestTU TU = TestTU::withCode(Case.SourceCode);284    TU.Filename = "Test.cpp";285    auto AST = TU.build();286    EXPECT_EQ(Case.ExpectedResult,287              getCorrespondingHeaderOrSource(testPath(TU.Filename), AST,288                                             Index.get()));289  }290}291 292TEST(HeaderSourceSwitchTest, ClangdServerIntegration) {293  MockCompilationDatabase CDB;294  CDB.ExtraClangFlags = {"-I" +295                         testPath("src/include")}; // add search directory.296  MockFS FS;297  // File heuristic fails here, we rely on the index to find the .h file.298  std::string CppPath = testPath("src/lib/test.cpp");299  std::string HeaderPath = testPath("src/include/test.h");300  FS.Files[HeaderPath] = "void foo();";301  const std::string FileContent = R"cpp(302    #include "test.h"303    void foo() {};304  )cpp";305  FS.Files[CppPath] = FileContent;306  auto Options = ClangdServer::optsForTest();307  Options.BuildDynamicSymbolIndex = true;308  ClangdServer Server(CDB, FS, Options);309  runAddDocument(Server, CppPath, FileContent);310  EXPECT_EQ(HeaderPath,311            *llvm::cantFail(runSwitchHeaderSource(Server, CppPath)));312}313 314TEST(HeaderSourceSwitchTest, CaseSensitivity) {315  TestTU TU = TestTU::withCode("void foo() {}");316  // Define more symbols in the header than the source file to trick heuristics317  // into picking the header as source file, if the matching for header file318  // path fails.319  TU.HeaderCode = R"cpp(320  inline void bar1() {}321  inline void bar2() {}322  void foo();)cpp";323  // Give main file and header different base names to make sure file system324  // heuristics don't work.325  TU.Filename = "Source.cpp";326  TU.HeaderFilename = "Header.h";327 328  auto Index = TU.index();329  TU.Code = std::move(TU.HeaderCode);330  TU.HeaderCode.clear();331  auto AST = TU.build();332 333  // Provide a different-cased filename in the query than what we have in the334  // index, check if we can still find the source file, which defines less335  // symbols than the header.336  auto HeaderAbsPath = testPath("HEADER.H");337  // We expect the heuristics to pick:338  // - header on case sensitive file systems, because the HeaderAbsPath doesn't339  //   match what we've seen through index.340  // - source on case insensitive file systems, as the HeaderAbsPath would match341  //   the filename in index.342#ifdef CLANGD_PATH_CASE_INSENSITIVE343  EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),344              llvm::ValueIs(testing::StrCaseEq(testPath(TU.Filename))));345#else346  EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),347              llvm::ValueIs(testing::StrCaseEq(testPath(TU.HeaderFilename))));348#endif349}350 351} // namespace352} // namespace clangd353} // namespace clang354