clang-tools 23.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
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, source
44 // file with different extension
45 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 letters
55 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 file
64 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 empty
74 // string.
75 PathResult = getCorrespondingHeaderOrSource(Invalid, FS.view(std::nullopt));
76 EXPECT_FALSE(PathResult.has_value());
77
78 // Test when both .h and .hpp exist next to a .cpp file, prefer .hpp
79 auto FooHpp = testPath("foo.hpp");
80 FS.Files[FooHpp];
81 PathResult = getCorrespondingHeaderOrSource(FooCpp, FS.view(std::nullopt));
82 EXPECT_TRUE(PathResult.has_value());
83 ASSERT_EQ(*PathResult, FooHpp);
84
85 auto BarCc = testPath("bar2.cc");
86 auto BarH = testPath("bar2.h");
87 auto BarHh = testPath("bar2.hh");
88 FS.Files[BarCc];
89 FS.Files[BarH];
90 FS.Files[BarHh];
91 PathResult = getCorrespondingHeaderOrSource(BarCc, FS.view(std::nullopt));
92 EXPECT_TRUE(PathResult.has_value());
93 ASSERT_EQ(*PathResult, BarHh);
94}
95
96TEST(HeaderSourceSwitchTest, ModuleInterfaces) {
97 MockFS FS;
98
99 auto FooCC = testPath("foo.cc");
100 auto FooCPPM = testPath("foo.cppm");
101 FS.Files[FooCC];
102 FS.Files[FooCPPM];
103 std::optional<Path> PathResult =
104 getCorrespondingHeaderOrSource(FooCC, FS.view(std::nullopt));
105 EXPECT_TRUE(PathResult.has_value());
106 ASSERT_EQ(*PathResult, FooCPPM);
107
108 auto Foo2CPP = testPath("foo2.cpp");
109 auto Foo2CCM = testPath("foo2.ccm");
110 FS.Files[Foo2CPP];
111 FS.Files[Foo2CCM];
112 PathResult = getCorrespondingHeaderOrSource(Foo2CPP, FS.view(std::nullopt));
113 EXPECT_TRUE(PathResult.has_value());
114 ASSERT_EQ(*PathResult, Foo2CCM);
115
116 auto Foo3CXX = testPath("foo3.cxx");
117 auto Foo3CXXM = testPath("foo3.cxxm");
118 FS.Files[Foo3CXX];
119 FS.Files[Foo3CXXM];
120 PathResult = getCorrespondingHeaderOrSource(Foo3CXX, FS.view(std::nullopt));
121 EXPECT_TRUE(PathResult.has_value());
122 ASSERT_EQ(*PathResult, Foo3CXXM);
123
124 auto Foo4CPLUSPLUS = testPath("foo4.c++");
125 auto Foo4CPLUSPLUSM = testPath("foo4.c++m");
126 FS.Files[Foo4CPLUSPLUS];
127 FS.Files[Foo4CPLUSPLUSM];
128 PathResult =
129 getCorrespondingHeaderOrSource(Foo4CPLUSPLUS, FS.view(std::nullopt));
130 EXPECT_TRUE(PathResult.has_value());
131 ASSERT_EQ(*PathResult, Foo4CPLUSPLUSM);
132}
133
134MATCHER_P(declNamed, Name, "") {
135 if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg))
136 if (ND->getQualifiedNameAsString() == Name)
137 return true;
138 return false;
139}
140
141TEST(HeaderSourceSwitchTest, GetLocalDecls) {
142 TestTU TU;
143 TU.HeaderCode = R"cpp(
144 void HeaderOnly();
145 )cpp";
146 TU.Code = R"cpp(
147 void MainF1();
148 class Foo {};
149 namespace ns {
150 class Foo {
151 void method();
152 int field;
153 };
154 } // namespace ns
155
156 // Non-indexable symbols
157 namespace {
158 void Ignore1() {}
159 }
160
161 )cpp";
162
163 auto AST = TU.build();
164 EXPECT_THAT(getIndexableLocalDecls(AST),
165 testing::UnorderedElementsAre(
166 declNamed("MainF1"), declNamed("Foo"), declNamed("ns::Foo"),
167 declNamed("ns::Foo::method"), declNamed("ns::Foo::field")));
168}
169
170TEST(HeaderSourceSwitchTest, FromHeaderToSource) {
171 // build a proper index, which contains symbols:
172 // A_Sym1, declared in TestTU.h, defined in a.cpp
173 // B_Sym[1-2], declared in TestTU.h, defined in b.cpp
174 SymbolSlab::Builder AllSymbols;
175 TestTU Testing;
176 Testing.HeaderFilename = "TestTU.h";
177 Testing.HeaderCode = "void A_Sym1();";
178 Testing.Filename = "a.cpp";
179 Testing.Code = "void A_Sym1() {};";
180 for (auto &Sym : Testing.headerSymbols())
181 AllSymbols.insert(Sym);
182
183 Testing.HeaderCode = R"cpp(
184 void B_Sym1();
185 void B_Sym2();
186 void B_Sym3_NoDef();
187 )cpp";
188 Testing.Filename = "b.cpp";
189 Testing.Code = R"cpp(
190 void B_Sym1() {}
191 void B_Sym2() {}
192 )cpp";
193 for (auto &Sym : Testing.headerSymbols())
194 AllSymbols.insert(Sym);
195 auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {});
196
197 // Test for switch from .h header to .cc source
198 struct {
199 llvm::StringRef HeaderCode;
200 std::optional<std::string> ExpectedSource;
201 } TestCases[] = {
202 {"// empty, no header found", std::nullopt},
203 {R"cpp(
204 // no definition found in the index.
205 void NonDefinition();
206 )cpp",
207 std::nullopt},
208 {R"cpp(
209 void A_Sym1();
210 )cpp",
211 testPath("a.cpp")},
212 {R"cpp(
213 // b.cpp wins.
214 void A_Sym1();
215 void B_Sym1();
216 void B_Sym2();
217 )cpp",
218 testPath("b.cpp")},
219 {R"cpp(
220 // a.cpp and b.cpp have same scope, but a.cpp because "a.cpp" < "b.cpp".
221 void A_Sym1();
222 void B_Sym1();
223 )cpp",
224 testPath("a.cpp")},
225
226 {R"cpp(
227 // We don't have definition in the index, so stay in the header.
228 void B_Sym3_NoDef();
229 )cpp",
230 std::nullopt},
231 };
232 for (const auto &Case : TestCases) {
233 TestTU TU = TestTU::withCode(Case.HeaderCode);
234 TU.Filename = "TestTU.h";
235 TU.ExtraArgs.push_back("-xc++-header"); // inform clang this is a header.
236 auto HeaderAST = TU.build();
237 EXPECT_EQ(Case.ExpectedSource,
238 getCorrespondingHeaderOrSource(testPath(TU.Filename), HeaderAST,
239 Index.get()));
240 }
241}
242
243TEST(HeaderSourceSwitchTest, FromSourceToHeader) {
244 // build a proper index, which contains symbols:
245 // A_Sym1, declared in a.h, defined in TestTU.cpp
246 // B_Sym[1-2], declared in b.h, defined in TestTU.cpp
247 TestTU TUForIndex = TestTU::withCode(R"cpp(
248 #include "a.h"
249 #include "b.h"
250
251 void A_Sym1() {}
252
253 void B_Sym1() {}
254 void B_Sym2() {}
255 )cpp");
256 TUForIndex.AdditionalFiles["a.h"] = R"cpp(
257 void A_Sym1();
258 )cpp";
259 TUForIndex.AdditionalFiles["b.h"] = R"cpp(
260 void B_Sym1();
261 void B_Sym2();
262 )cpp";
263 TUForIndex.Filename = "TestTU.cpp";
264 auto Index = TUForIndex.index();
265
266 // Test for switching from .cc source file to .h header.
267 struct {
268 llvm::StringRef SourceCode;
269 std::optional<std::string> ExpectedResult;
270 } TestCases[] = {
271 {"// empty, no header found", std::nullopt},
272 {R"cpp(
273 // symbol not in index, no header found
274 void Local() {}
275 )cpp",
276 std::nullopt},
277
278 {R"cpp(
279 // a.h wins.
280 void A_Sym1() {}
281 )cpp",
282 testPath("a.h")},
283
284 {R"cpp(
285 // b.h wins.
286 void A_Sym1() {}
287 void B_Sym1() {}
288 void B_Sym2() {}
289 )cpp",
290 testPath("b.h")},
291
292 {R"cpp(
293 // a.h and b.h have same scope, but a.h wins because "a.h" < "b.h".
294 void A_Sym1() {}
295 void B_Sym1() {}
296 )cpp",
297 testPath("a.h")},
298 };
299 for (const auto &Case : TestCases) {
300 TestTU TU = TestTU::withCode(Case.SourceCode);
301 TU.Filename = "Test.cpp";
302 auto AST = TU.build();
303 EXPECT_EQ(Case.ExpectedResult,
305 Index.get()));
306 }
307}
308
309TEST(HeaderSourceSwitchTest, ClangdServerIntegration) {
311 CDB.ExtraClangFlags = {"-I" +
312 testPath("src/include")}; // add search directory.
313 MockFS FS;
314 // File heuristic fails here, we rely on the index to find the .h file.
315 std::string CppPath = testPath("src/lib/test.cpp");
316 std::string HeaderPath = testPath("src/include/test.h");
317 FS.Files[HeaderPath] = "void foo();";
318 const std::string FileContent = R"cpp(
319 #include "test.h"
320 void foo() {};
321 )cpp";
322 FS.Files[CppPath] = FileContent;
323 auto Options = ClangdServer::optsForTest();
324 Options.BuildDynamicSymbolIndex = true;
325 ClangdServer Server(CDB, FS, Options);
326 runAddDocument(Server, CppPath, FileContent);
327 EXPECT_EQ(HeaderPath,
328 *llvm::cantFail(runSwitchHeaderSource(Server, CppPath)));
329}
330
331TEST(HeaderSourceSwitchTest, CaseSensitivity) {
332 TestTU TU = TestTU::withCode("void foo() {}");
333 // Define more symbols in the header than the source file to trick heuristics
334 // into picking the header as source file, if the matching for header file
335 // path fails.
336 TU.HeaderCode = R"cpp(
337 inline void bar1() {}
338 inline void bar2() {}
339 void foo();)cpp";
340 // Give main file and header different base names to make sure file system
341 // heuristics don't work.
342 TU.Filename = "Source.cpp";
343 TU.HeaderFilename = "Header.h";
344
345 auto Index = TU.index();
346 TU.Code = std::move(TU.HeaderCode);
347 TU.HeaderCode.clear();
348 auto AST = TU.build();
349
350 // Provide a different-cased filename in the query than what we have in the
351 // index, check if we can still find the source file, which defines less
352 // symbols than the header.
353 auto HeaderAbsPath = testPath("HEADER.H");
354 // We expect the heuristics to pick:
355 // - header on case sensitive file systems, because the HeaderAbsPath doesn't
356 // match what we've seen through index.
357 // - source on case insensitive file systems, as the HeaderAbsPath would match
358 // the filename in index.
359#ifdef CLANGD_PATH_CASE_INSENSITIVE
360 EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),
361 llvm::ValueIs(testing::StrCaseEq(testPath(TU.Filename))));
362#else
363 EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),
364 llvm::ValueIs(testing::StrCaseEq(testPath(TU.HeaderFilename))));
365#endif
366}
367
368} // namespace
369} // namespace clangd
370} // namespace clang
Manages a collection of source files and derived data (ASTs, indexes), and provides language-aware fe...
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:18
std::vector< std::string > ExtraClangFlags
Definition TestFS.h:68
SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab.
Definition Symbol.h:224
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
llvm::Expected< std::optional< clangd::Path > > runSwitchHeaderSource(ClangdServer &Server, PathRef File)
Definition SyncAPI.cpp:156
std::vector< const Decl * > getIndexableLocalDecls(ParsedAST &AST)
Returns all indexable decls that are present in the main file of the AST.
MATCHER_P(named, N, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition TestFS.cpp:94
TEST(BackgroundQueueTest, Priority)
void runAddDocument(ClangdServer &Server, PathRef File, llvm::StringRef Contents, llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild)
Definition SyncAPI.cpp:17
std::optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::string HeaderFilename
Definition TestTU.h:54
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36
std::string HeaderCode
Definition TestTU.h:53