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 
TEST(HeaderSourceSwitchTest,FileHeuristic)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 
TEST(HeaderSourceSwitchTest,GetLocalDecls)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 
TEST(HeaderSourceSwitchTest,FromHeaderToSource)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 
TEST(HeaderSourceSwitchTest,FromSourceToHeader)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 
TEST(HeaderSourceSwitchTest,ClangdServerIntegration)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
277