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