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