1 //===--- HeaderSourceSwitchTests.cpp - ---------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "HeaderSourceSwitch.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"
25 TEST(HeaderSourceSwitchTest
, FileHeuristic
) {
27 auto FooCpp
= testPath("foo.cpp");
28 auto FooH
= testPath("foo.h");
29 auto Invalid
= testPath("main.cpp");
34 std::optional
<Path
> PathResult
=
35 getCorrespondingHeaderOrSource(FooCpp
, FS
.view(std::nullopt
));
36 EXPECT_TRUE(PathResult
.has_value());
37 ASSERT_EQ(*PathResult
, FooH
);
39 PathResult
= getCorrespondingHeaderOrSource(FooH
, FS
.view(std::nullopt
));
40 EXPECT_TRUE(PathResult
.has_value());
41 ASSERT_EQ(*PathResult
, FooCpp
);
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");
50 PathResult
= getCorrespondingHeaderOrSource(FooC
, FS
.view(std::nullopt
));
51 EXPECT_TRUE(PathResult
.has_value());
52 ASSERT_EQ(*PathResult
, FooHH
);
54 // Test with both capital letters
55 auto Foo2C
= testPath("foo2.C");
56 auto Foo2HH
= testPath("foo2.HH");
59 PathResult
= getCorrespondingHeaderOrSource(Foo2C
, FS
.view(std::nullopt
));
60 EXPECT_TRUE(PathResult
.has_value());
61 ASSERT_EQ(*PathResult
, Foo2HH
);
63 // Test with source file as capital letter and .hxx header file
64 auto Foo3C
= testPath("foo3.C");
65 auto Foo3HXX
= testPath("foo3.hxx");
69 PathResult
= getCorrespondingHeaderOrSource(Foo3C
, FS
.view(std::nullopt
));
70 EXPECT_TRUE(PathResult
.has_value());
71 ASSERT_EQ(*PathResult
, Foo3HXX
);
73 // Test if asking for a corresponding file that doesn't exist returns an empty
75 PathResult
= getCorrespondingHeaderOrSource(Invalid
, FS
.view(std::nullopt
));
76 EXPECT_FALSE(PathResult
.has_value());
79 MATCHER_P(declNamed
, Name
, "") {
80 if (const NamedDecl
*ND
= dyn_cast
<NamedDecl
>(arg
))
81 if (ND
->getQualifiedNameAsString() == Name
)
86 TEST(HeaderSourceSwitchTest
, GetLocalDecls
) {
88 TU
.HeaderCode
= R
"cpp(
101 // Non-indexable symbols
108 auto AST
= TU
.build();
109 EXPECT_THAT(getIndexableLocalDecls(AST
),
110 testing::UnorderedElementsAre(
111 declNamed("MainF1"), declNamed("Foo"), declNamed("ns::Foo"),
112 declNamed("ns::Foo::method"), declNamed("ns::Foo::field")));
115 TEST(HeaderSourceSwitchTest
, FromHeaderToSource
) {
116 // build a proper index, which contains symbols:
117 // A_Sym1, declared in TestTU.h, defined in a.cpp
118 // B_Sym[1-2], declared in TestTU.h, defined in b.cpp
119 SymbolSlab::Builder AllSymbols
;
121 Testing
.HeaderFilename
= "TestTU.h";
122 Testing
.HeaderCode
= "void A_Sym1();";
123 Testing
.Filename
= "a.cpp";
124 Testing
.Code
= "void A_Sym1() {};";
125 for (auto &Sym
: Testing
.headerSymbols())
126 AllSymbols
.insert(Sym
);
128 Testing
.HeaderCode
= R
"cpp(
133 Testing
.Filename
= "b.cpp";
134 Testing
.Code
= R
"cpp(
138 for (auto &Sym
: Testing
.headerSymbols())
139 AllSymbols
.insert(Sym
);
140 auto Index
= MemIndex::build(std::move(AllSymbols
).build(), {}, {});
142 // Test for switch from .h header to .cc source
144 llvm::StringRef HeaderCode
;
145 std::optional
<std::string
> ExpectedSource
;
147 {"// empty, no header found", std::nullopt
},
149 // no definition found in the index.
150 void NonDefinition();
165 // a.cpp and b.cpp have same scope, but a.cpp because "a
.cpp
" < "b
.cpp
".
172 // We don't have definition in the index, so stay in the header.
177 for (const auto &Case
: TestCases
) {
178 TestTU TU
= TestTU::withCode(Case
.HeaderCode
);
179 TU
.Filename
= "TestTU.h";
180 TU
.ExtraArgs
.push_back("-xc++-header"); // inform clang this is a header.
181 auto HeaderAST
= TU
.build();
182 EXPECT_EQ(Case
.ExpectedSource
,
183 getCorrespondingHeaderOrSource(testPath(TU
.Filename
), HeaderAST
,
188 TEST(HeaderSourceSwitchTest
, FromSourceToHeader
) {
189 // build a proper index, which contains symbols:
190 // A_Sym1, declared in a.h, defined in TestTU.cpp
191 // B_Sym[1-2], declared in b.h, defined in TestTU.cpp
192 TestTU TUForIndex
= TestTU::withCode(R
"cpp(
201 TUForIndex
.AdditionalFiles
["a.h"] = R
"cpp(
204 TUForIndex
.AdditionalFiles
["b.h"] = R
"cpp(
208 TUForIndex
.Filename
= "TestTU.cpp";
209 auto Index
= TUForIndex
.index();
211 // Test for switching from .cc source file to .h header.
213 llvm::StringRef SourceCode
;
214 std::optional
<std::string
> ExpectedResult
;
216 {"// empty, no header found", std::nullopt
},
218 // symbol not in index, no header found
238 // a.h and b.h have same scope, but a.h wins because "a
.h
" < "b
.h
".
244 for (const auto &Case
: TestCases
) {
245 TestTU TU
= TestTU::withCode(Case
.SourceCode
);
246 TU
.Filename
= "Test.cpp";
247 auto AST
= TU
.build();
248 EXPECT_EQ(Case
.ExpectedResult
,
249 getCorrespondingHeaderOrSource(testPath(TU
.Filename
), AST
,
254 TEST(HeaderSourceSwitchTest
, ClangdServerIntegration
) {
255 MockCompilationDatabase CDB
;
256 CDB
.ExtraClangFlags
= {"-I" +
257 testPath("src/include")}; // add search directory.
259 // File heuristic fails here, we rely on the index to find the .h file.
260 std::string CppPath
= testPath("src/lib/test.cpp");
261 std::string HeaderPath
= testPath("src/include/test.h");
262 FS
.Files
[HeaderPath
] = "void foo();";
263 const std::string FileContent
= R
"cpp(
267 FS
.Files
[CppPath
] = FileContent
;
268 auto Options
= ClangdServer::optsForTest();
269 Options
.BuildDynamicSymbolIndex
= true;
270 ClangdServer
Server(CDB
, FS
, Options
);
271 runAddDocument(Server
, CppPath
, FileContent
);
272 EXPECT_EQ(HeaderPath
,
273 *llvm::cantFail(runSwitchHeaderSource(Server
, CppPath
)));
276 TEST(HeaderSourceSwitchTest
, CaseSensitivity
) {
277 TestTU TU
= TestTU::withCode("void foo() {}");
278 // Define more symbols in the header than the source file to trick heuristics
279 // into picking the header as source file, if the matching for header file
281 TU
.HeaderCode
= R
"cpp(
282 inline void bar1() {}
283 inline void bar2() {}
285 // Give main file and header different base names to make sure file system
286 // heuristics don't work.
287 TU
.Filename
= "Source.cpp";
288 TU
.HeaderFilename
= "Header.h";
290 auto Index
= TU
.index();
291 TU
.Code
= std::move(TU
.HeaderCode
);
292 TU
.HeaderCode
.clear();
293 auto AST
= TU
.build();
295 // Provide a different-cased filename in the query than what we have in the
296 // index, check if we can still find the source file, which defines less
297 // symbols than the header.
298 auto HeaderAbsPath
= testPath("HEADER.H");
299 // We expect the heuristics to pick:
300 // - header on case sensitive file systems, because the HeaderAbsPath doesn't
301 // match what we've seen through index.
302 // - source on case insensitive file systems, as the HeaderAbsPath would match
303 // the filename in index.
304 #ifdef CLANGD_PATH_CASE_INSENSITIVE
305 EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath
, AST
, Index
.get()),
306 llvm::ValueIs(testing::StrCaseEq(testPath(TU
.Filename
))));
308 EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath
, AST
, Index
.get()),
309 llvm::ValueIs(testing::StrCaseEq(testPath(TU
.HeaderFilename
))));
314 } // namespace clangd