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 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 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 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 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 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