clang-tools  14.0.0git
HeaderSourceSwitchTests.cpp
Go to the documentation of this file.
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-exception
6 //
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 "llvm/ADT/None.h"
16 #include "gmock/gmock.h"
17 #include "gtest/gtest.h"
18 
19 namespace clang {
20 namespace clangd {
21 namespace {
22 
23 TEST(HeaderSourceSwitchTest, FileHeuristic) {
24  MockFS FS;
25  auto FooCpp = testPath("foo.cpp");
26  auto FooH = testPath("foo.h");
27  auto Invalid = testPath("main.cpp");
28 
29  FS.Files[FooCpp];
30  FS.Files[FooH];
31  FS.Files[Invalid];
32  Optional<Path> PathResult =
33  getCorrespondingHeaderOrSource(FooCpp, FS.view(llvm::None));
34  EXPECT_TRUE(PathResult.hasValue());
35  ASSERT_EQ(PathResult.getValue(), FooH);
36 
37  PathResult = getCorrespondingHeaderOrSource(FooH, FS.view(llvm::None));
38  EXPECT_TRUE(PathResult.hasValue());
39  ASSERT_EQ(PathResult.getValue(), FooCpp);
40 
41  // Test with header file in capital letters and different extension, source
42  // file with different extension
43  auto FooC = testPath("bar.c");
44  auto FooHH = testPath("bar.HH");
45 
46  FS.Files[FooC];
47  FS.Files[FooHH];
48  PathResult = getCorrespondingHeaderOrSource(FooC, FS.view(llvm::None));
49  EXPECT_TRUE(PathResult.hasValue());
50  ASSERT_EQ(PathResult.getValue(), FooHH);
51 
52  // Test with both capital letters
53  auto Foo2C = testPath("foo2.C");
54  auto Foo2HH = testPath("foo2.HH");
55  FS.Files[Foo2C];
56  FS.Files[Foo2HH];
57  PathResult = getCorrespondingHeaderOrSource(Foo2C, FS.view(llvm::None));
58  EXPECT_TRUE(PathResult.hasValue());
59  ASSERT_EQ(PathResult.getValue(), Foo2HH);
60 
61  // Test with source file as capital letter and .hxx header file
62  auto Foo3C = testPath("foo3.C");
63  auto Foo3HXX = testPath("foo3.hxx");
64 
65  FS.Files[Foo3C];
66  FS.Files[Foo3HXX];
67  PathResult = getCorrespondingHeaderOrSource(Foo3C, FS.view(llvm::None));
68  EXPECT_TRUE(PathResult.hasValue());
69  ASSERT_EQ(PathResult.getValue(), Foo3HXX);
70 
71  // Test if asking for a corresponding file that doesn't exist returns an empty
72  // string.
73  PathResult = getCorrespondingHeaderOrSource(Invalid, FS.view(llvm::None));
74  EXPECT_FALSE(PathResult.hasValue());
75 }
76 
77 MATCHER_P(DeclNamed, Name, "") {
78  if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg))
79  if (ND->getQualifiedNameAsString() == Name)
80  return true;
81  return false;
82 }
83 
84 TEST(HeaderSourceSwitchTest, GetLocalDecls) {
85  TestTU TU;
86  TU.HeaderCode = R"cpp(
87  void HeaderOnly();
88  )cpp";
89  TU.Code = R"cpp(
90  void MainF1();
91  class Foo {};
92  namespace ns {
93  class Foo {
94  void method();
95  int field;
96  };
97  } // namespace ns
98 
99  // Non-indexable symbols
100  namespace {
101  void Ignore1() {}
102  }
103 
104  )cpp";
105 
106  auto AST = TU.build();
107  EXPECT_THAT(getIndexableLocalDecls(AST),
108  testing::UnorderedElementsAre(
109  DeclNamed("MainF1"), DeclNamed("Foo"), DeclNamed("ns::Foo"),
110  DeclNamed("ns::Foo::method"), DeclNamed("ns::Foo::field")));
111 }
112 
113 TEST(HeaderSourceSwitchTest, FromHeaderToSource) {
114  // build a proper index, which contains symbols:
115  // A_Sym1, declared in TestTU.h, defined in a.cpp
116  // B_Sym[1-2], declared in TestTU.h, defined in b.cpp
117  SymbolSlab::Builder AllSymbols;
118  TestTU Testing;
119  Testing.HeaderFilename = "TestTU.h";
120  Testing.HeaderCode = "void A_Sym1();";
121  Testing.Filename = "a.cpp";
122  Testing.Code = "void A_Sym1() {};";
123  for (auto &Sym : Testing.headerSymbols())
124  AllSymbols.insert(Sym);
125 
126  Testing.HeaderCode = R"cpp(
127  void B_Sym1();
128  void B_Sym2();
129  void B_Sym3_NoDef();
130  )cpp";
131  Testing.Filename = "b.cpp";
132  Testing.Code = R"cpp(
133  void B_Sym1() {}
134  void B_Sym2() {}
135  )cpp";
136  for (auto &Sym : Testing.headerSymbols())
137  AllSymbols.insert(Sym);
138  auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});
139 
140  // Test for switch from .h header to .cc source
141  struct {
142  llvm::StringRef HeaderCode;
143  llvm::Optional<std::string> ExpectedSource;
144  } TestCases[] = {
145  {"// empty, no header found", llvm::None},
146  {R"cpp(
147  // no definition found in the index.
148  void NonDefinition();
149  )cpp",
150  llvm::None},
151  {R"cpp(
152  void A_Sym1();
153  )cpp",
154  testPath("a.cpp")},
155  {R"cpp(
156  // b.cpp wins.
157  void A_Sym1();
158  void B_Sym1();
159  void B_Sym2();
160  )cpp",
161  testPath("b.cpp")},
162  {R"cpp(
163  // a.cpp and b.cpp have same scope, but a.cpp because "a.cpp" < "b.cpp".
164  void A_Sym1();
165  void B_Sym1();
166  )cpp",
167  testPath("a.cpp")},
168 
169  {R"cpp(
170  // We don't have definition in the index, so stay in the header.
171  void B_Sym3_NoDef();
172  )cpp",
173  None},
174  };
175  for (const auto &Case : TestCases) {
176  TestTU TU = TestTU::withCode(Case.HeaderCode);
177  TU.Filename = "TestTU.h";
178  TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
179  auto HeaderAST = TU.build();
180  EXPECT_EQ(Case.ExpectedSource,
181  getCorrespondingHeaderOrSource(testPath(TU.Filename), HeaderAST,
182  Index.get()));
183  }
184 }
185 
186 TEST(HeaderSourceSwitchTest, FromSourceToHeader) {
187  // build a proper index, which contains symbols:
188  // A_Sym1, declared in a.h, defined in TestTU.cpp
189  // B_Sym[1-2], declared in b.h, defined in TestTU.cpp
190  TestTU TUForIndex = TestTU::withCode(R"cpp(
191  #include "a.h"
192  #include "b.h"
193 
194  void A_Sym1() {}
195 
196  void B_Sym1() {}
197  void B_Sym2() {}
198  )cpp");
199  TUForIndex.AdditionalFiles["a.h"] = R"cpp(
200  void A_Sym1();
201  )cpp";
202  TUForIndex.AdditionalFiles["b.h"] = R"cpp(
203  void B_Sym1();
204  void B_Sym2();
205  )cpp";
206  TUForIndex.Filename = "TestTU.cpp";
207  auto Index = TUForIndex.index();
208 
209  // Test for switching from .cc source file to .h header.
210  struct {
211  llvm::StringRef SourceCode;
212  llvm::Optional<std::string> ExpectedResult;
213  } TestCases[] = {
214  {"// empty, no header found", llvm::None},
215  {R"cpp(
216  // symbol not in index, no header found
217  void Local() {}
218  )cpp",
219  llvm::None},
220 
221  {R"cpp(
222  // a.h wins.
223  void A_Sym1() {}
224  )cpp",
225  testPath("a.h")},
226 
227  {R"cpp(
228  // b.h wins.
229  void A_Sym1() {}
230  void B_Sym1() {}
231  void B_Sym2() {}
232  )cpp",
233  testPath("b.h")},
234 
235  {R"cpp(
236  // a.h and b.h have same scope, but a.h wins because "a.h" < "b.h".
237  void A_Sym1() {}
238  void B_Sym1() {}
239  )cpp",
240  testPath("a.h")},
241  };
242  for (const auto &Case : TestCases) {
243  TestTU TU = TestTU::withCode(Case.SourceCode);
244  TU.Filename = "Test.cpp";
245  auto AST = TU.build();
246  EXPECT_EQ(Case.ExpectedResult,
247  getCorrespondingHeaderOrSource(testPath(TU.Filename), AST,
248  Index.get()));
249  }
250 }
251 
252 TEST(HeaderSourceSwitchTest, ClangdServerIntegration) {
253  MockCompilationDatabase CDB;
254  CDB.ExtraClangFlags = {"-I" +
255  testPath("src/include")}; // add search directory.
256  MockFS FS;
257  // File heuristic fails here, we rely on the index to find the .h file.
258  std::string CppPath = testPath("src/lib/test.cpp");
259  std::string HeaderPath = testPath("src/include/test.h");
260  FS.Files[HeaderPath] = "void foo();";
261  const std::string FileContent = R"cpp(
262  #include "test.h"
263  void foo() {};
264  )cpp";
265  FS.Files[CppPath] = FileContent;
266  auto Options = ClangdServer::optsForTest();
267  Options.BuildDynamicSymbolIndex = true;
268  ClangdServer Server(CDB, FS, Options);
269  runAddDocument(Server, CppPath, FileContent);
270  EXPECT_EQ(HeaderPath,
271  *llvm::cantFail(runSwitchHeaderSource(Server, CppPath)));
272 }
273 
274 } // namespace
275 } // namespace clangd
276 } // namespace clang
clang::clangd::TEST
TEST(BackgroundQueueTest, Priority)
Definition: BackgroundIndexTests.cpp:751
clang::clangd::testPath
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:82
TestTU.h
clang::clangd::ClangdServer::optsForTest
static Options optsForTest()
Definition: ClangdServer.cpp:135
HeaderSourceSwitch.h
clang::clangd::runAddDocument
void runAddDocument(ClangdServer &Server, PathRef File, llvm::StringRef Contents, llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild)
Definition: SyncAPI.cpp:15
clang::clangd::ParsedAST::build
static llvm::Optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
Definition: ParsedAST.cpp:248
MemIndex.h
clang::tidy::bugprone::model::MixFlags::Invalid
@ Invalid
Sentinel bit pattern. DO NOT USE!
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:36
TestFS.h
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
SyncAPI.h
clang::clangd::runSwitchHeaderSource
llvm::Expected< llvm::Optional< clangd::Path > > runSwitchHeaderSource(ClangdServer &Server, PathRef File)
Definition: SyncAPI.cpp:154
clang::clangd::MemIndex::build
static std::unique_ptr< SymbolIndex > build(SymbolSlab Symbols, RefSlab Refs, RelationSlab Relations)
Builds an index from slabs. The index takes ownership of the data.
Definition: MemIndex.cpp:19
clang::clangd::TestTU::withCode
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:37
clang::clangd::getIndexableLocalDecls
std::vector< const Decl * > getIndexableLocalDecls(ParsedAST &AST)
Returns all indexable decls that are present in the main file of the AST.
Definition: HeaderSourceSwitch.cpp:124
Index
const SymbolIndex * Index
Definition: Dexp.cpp:99
clang::clangd::ThreadsafeFS::view
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(llvm::NoneType CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
Definition: ThreadsafeFS.h:34
clang::clangd::MATCHER_P
MATCHER_P(Named, N, "")
Definition: BackgroundIndexTests.cpp:31
clang::tidy::bugprone::model::MixFlags::None
@ None
Mix between the two parameters is not possible.
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::getCorrespondingHeaderOrSource
llvm::Optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
Definition: HeaderSourceSwitch.cpp:19