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 "support/Path.h" 16 #include "llvm/ADT/None.h" 17 #include "llvm/Testing/Support/SupportHelpers.h" 18 #include "gmock/gmock.h" 19 #include "gtest/gtest.h" 20 21 namespace clang { 22 namespace clangd { 23 namespace { 24 25 TEST(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 Optional<Path> PathResult = 35 getCorrespondingHeaderOrSource(FooCpp, FS.view(llvm::None)); 36 EXPECT_TRUE(PathResult.has_value()); 37 ASSERT_EQ(PathResult.value(), FooH); 38 39 PathResult = getCorrespondingHeaderOrSource(FooH, FS.view(llvm::None)); 40 EXPECT_TRUE(PathResult.has_value()); 41 ASSERT_EQ(PathResult.value(), 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(llvm::None)); 51 EXPECT_TRUE(PathResult.has_value()); 52 ASSERT_EQ(PathResult.value(), 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(llvm::None)); 60 EXPECT_TRUE(PathResult.has_value()); 61 ASSERT_EQ(PathResult.value(), 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(llvm::None)); 70 EXPECT_TRUE(PathResult.has_value()); 71 ASSERT_EQ(PathResult.value(), 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(llvm::None)); 76 EXPECT_FALSE(PathResult.has_value()); 77 } 78 79 MATCHER_P(declNamed, Name, "") { 80 if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg)) 81 if (ND->getQualifiedNameAsString() == Name) 82 return true; 83 return false; 84 } 85 86 TEST(HeaderSourceSwitchTest, GetLocalDecls) { 87 TestTU TU; 88 TU.HeaderCode = R"cpp( 89 void HeaderOnly(); 90 )cpp"; 91 TU.Code = R"cpp( 92 void MainF1(); 93 class Foo {}; 94 namespace ns { 95 class Foo { 96 void method(); 97 int field; 98 }; 99 } // namespace ns 100 101 // Non-indexable symbols 102 namespace { 103 void Ignore1() {} 104 } 105 106 )cpp"; 107 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"))); 113 } 114 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; 120 TestTU Testing; 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); 127 128 Testing.HeaderCode = R"cpp( 129 void B_Sym1(); 130 void B_Sym2(); 131 void B_Sym3_NoDef(); 132 )cpp"; 133 Testing.Filename = "b.cpp"; 134 Testing.Code = R"cpp( 135 void B_Sym1() {} 136 void B_Sym2() {} 137 )cpp"; 138 for (auto &Sym : Testing.headerSymbols()) 139 AllSymbols.insert(Sym); 140 auto Index = MemIndex::build(std::move(AllSymbols).build(), {}, {}); 141 142 // Test for switch from .h header to .cc source 143 struct { 144 llvm::StringRef HeaderCode; 145 llvm::Optional<std::string> ExpectedSource; 146 } TestCases[] = { 147 {"// empty, no header found", llvm::None}, 148 {R"cpp( 149 // no definition found in the index. 150 void NonDefinition(); 151 )cpp", 152 llvm::None}, 153 {R"cpp( 154 void A_Sym1(); 155 )cpp", 156 testPath("a.cpp")}, 157 {R"cpp( 158 // b.cpp wins. 159 void A_Sym1(); 160 void B_Sym1(); 161 void B_Sym2(); 162 )cpp", 163 testPath("b.cpp")}, 164 {R"cpp( 165 // a.cpp and b.cpp have same scope, but a.cpp because "a.cpp" < "b.cpp". 166 void A_Sym1(); 167 void B_Sym1(); 168 )cpp", 169 testPath("a.cpp")}, 170 171 {R"cpp( 172 // We don't have definition in the index, so stay in the header. 173 void B_Sym3_NoDef(); 174 )cpp", 175 None}, 176 }; 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, 184 Index.get())); 185 } 186 } 187 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( 193 #include "a.h" 194 #include "b.h" 195 196 void A_Sym1() {} 197 198 void B_Sym1() {} 199 void B_Sym2() {} 200 )cpp"); 201 TUForIndex.AdditionalFiles["a.h"] = R"cpp( 202 void A_Sym1(); 203 )cpp"; 204 TUForIndex.AdditionalFiles["b.h"] = R"cpp( 205 void B_Sym1(); 206 void B_Sym2(); 207 )cpp"; 208 TUForIndex.Filename = "TestTU.cpp"; 209 auto Index = TUForIndex.index(); 210 211 // Test for switching from .cc source file to .h header. 212 struct { 213 llvm::StringRef SourceCode; 214 llvm::Optional<std::string> ExpectedResult; 215 } TestCases[] = { 216 {"// empty, no header found", llvm::None}, 217 {R"cpp( 218 // symbol not in index, no header found 219 void Local() {} 220 )cpp", 221 llvm::None}, 222 223 {R"cpp( 224 // a.h wins. 225 void A_Sym1() {} 226 )cpp", 227 testPath("a.h")}, 228 229 {R"cpp( 230 // b.h wins. 231 void A_Sym1() {} 232 void B_Sym1() {} 233 void B_Sym2() {} 234 )cpp", 235 testPath("b.h")}, 236 237 {R"cpp( 238 // a.h and b.h have same scope, but a.h wins because "a.h" < "b.h". 239 void A_Sym1() {} 240 void B_Sym1() {} 241 )cpp", 242 testPath("a.h")}, 243 }; 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, 250 Index.get())); 251 } 252 } 253 254 TEST(HeaderSourceSwitchTest, ClangdServerIntegration) { 255 MockCompilationDatabase CDB; 256 CDB.ExtraClangFlags = {"-I" + 257 testPath("src/include")}; // add search directory. 258 MockFS FS; 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( 264 #include "test.h" 265 void foo() {}; 266 )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))); 274 } 275 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 280 // path fails. 281 TU.HeaderCode = R"cpp( 282 inline void bar1() {} 283 inline void bar2() {} 284 void foo();)cpp"; 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"; 289 290 auto Index = TU.index(); 291 TU.Code = std::move(TU.HeaderCode); 292 TU.HeaderCode.clear(); 293 auto AST = TU.build(); 294 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)))); 307 #else 308 EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()), 309 llvm::ValueIs(testing::StrCaseEq(testPath(TU.HeaderFilename)))); 310 #endif 311 } 312 313 } // namespace 314 } // namespace clangd 315 } // namespace clang 316