| 1 | //===------ IndexActionTests.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 "Headers.h" |
| 10 | #include "TestFS.h" |
| 11 | #include "URI.h" |
| 12 | #include "index/IndexAction.h" |
| 13 | #include "index/Serialization.h" |
| 14 | #include "clang/Basic/SourceLocation.h" |
| 15 | #include "clang/Basic/SourceManager.h" |
| 16 | #include "clang/Tooling/Tooling.h" |
| 17 | #include "gmock/gmock.h" |
| 18 | #include "gtest/gtest.h" |
| 19 | #include <string> |
| 20 | |
| 21 | namespace clang { |
| 22 | namespace clangd { |
| 23 | namespace { |
| 24 | |
| 25 | using ::testing::AllOf; |
| 26 | using ::testing::ElementsAre; |
| 27 | using ::testing::EndsWith; |
| 28 | using ::testing::Not; |
| 29 | using ::testing::Pair; |
| 30 | using ::testing::UnorderedElementsAre; |
| 31 | using ::testing::UnorderedPointwise; |
| 32 | |
| 33 | std::string toUri(llvm::StringRef Path) { return URI::create(AbsolutePath: Path).toString(); } |
| 34 | |
| 35 | MATCHER(isTU, "" ) { return arg.Flags & IncludeGraphNode::SourceFlag::IsTU; } |
| 36 | |
| 37 | MATCHER_P(hasDigest, Digest, "" ) { return arg.Digest == Digest; } |
| 38 | |
| 39 | MATCHER_P(hasName, Name, "" ) { return arg.Name == Name; } |
| 40 | |
| 41 | MATCHER(hasSameURI, "" ) { |
| 42 | llvm::StringRef URI = ::testing::get<0>(arg); |
| 43 | const std::string &Path = ::testing::get<1>(arg); |
| 44 | return toUri(Path) == URI; |
| 45 | } |
| 46 | |
| 47 | MATCHER_P(, , "" ) { |
| 48 | return (arg.IncludeHeaders.size() == 1) && |
| 49 | (arg.IncludeHeaders.begin()->IncludeHeader == P); |
| 50 | } |
| 51 | |
| 52 | ::testing::Matcher<const IncludeGraphNode &> |
| 53 | includesAre(const std::vector<std::string> &Includes) { |
| 54 | return ::testing::Field(field: &IncludeGraphNode::DirectIncludes, |
| 55 | matcher: UnorderedPointwise(tuple2_matcher: hasSameURI(), rhs_container: Includes)); |
| 56 | } |
| 57 | |
| 58 | void checkNodesAreInitialized(const IndexFileIn &IndexFile, |
| 59 | const std::vector<std::string> &Paths) { |
| 60 | ASSERT_TRUE(IndexFile.Sources); |
| 61 | EXPECT_THAT(Paths.size(), IndexFile.Sources->size()); |
| 62 | for (llvm::StringRef Path : Paths) { |
| 63 | auto URI = toUri(Path); |
| 64 | const auto &Node = IndexFile.Sources->lookup(Key: URI); |
| 65 | // Uninitialized nodes will have an empty URI. |
| 66 | EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData()); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) { |
| 71 | std::map<std::string, const IncludeGraphNode &> Nodes; |
| 72 | for (auto &I : IG) |
| 73 | Nodes.emplace(args: std::string(I.getKey()), args: I.getValue()); |
| 74 | return Nodes; |
| 75 | } |
| 76 | |
| 77 | class IndexActionTest : public ::testing::Test { |
| 78 | public: |
| 79 | IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {} |
| 80 | |
| 81 | IndexFileIn |
| 82 | runIndexingAction(llvm::StringRef MainFilePath, |
| 83 | const std::vector<std::string> & = {}) { |
| 84 | IndexFileIn IndexFile; |
| 85 | llvm::IntrusiveRefCntPtr<FileManager> Files( |
| 86 | new FileManager(FileSystemOptions(), InMemoryFileSystem)); |
| 87 | |
| 88 | auto Action = createStaticIndexingAction( |
| 89 | Opts, SymbolsCallback: [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); }, |
| 90 | RefsCallback: [&](RefSlab R) { IndexFile.Refs = std::move(R); }, |
| 91 | RelationsCallback: [&](RelationSlab R) { IndexFile.Relations = std::move(R); }, |
| 92 | IncludeGraphCallback: [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); }); |
| 93 | |
| 94 | std::vector<std::string> Args = {"index_action" , "-fsyntax-only" , |
| 95 | "-xc++" , "-std=c++11" , |
| 96 | "-iquote" , testRoot()}; |
| 97 | Args.insert(position: Args.end(), first: ExtraArgs.begin(), last: ExtraArgs.end()); |
| 98 | Args.push_back(x: std::string(MainFilePath)); |
| 99 | |
| 100 | tooling::ToolInvocation Invocation( |
| 101 | Args, std::move(Action), Files.get(), |
| 102 | std::make_shared<PCHContainerOperations>()); |
| 103 | |
| 104 | Invocation.run(); |
| 105 | |
| 106 | checkNodesAreInitialized(IndexFile, Paths: FilePaths); |
| 107 | return IndexFile; |
| 108 | } |
| 109 | |
| 110 | void addFile(llvm::StringRef Path, llvm::StringRef Content) { |
| 111 | InMemoryFileSystem->addFile(Path, ModificationTime: 0, |
| 112 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: Content)); |
| 113 | FilePaths.push_back(x: std::string(Path)); |
| 114 | } |
| 115 | |
| 116 | protected: |
| 117 | SymbolCollector::Options Opts; |
| 118 | std::vector<std::string> FilePaths; |
| 119 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; |
| 120 | }; |
| 121 | |
| 122 | TEST_F(IndexActionTest, CollectIncludeGraph) { |
| 123 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 124 | std::string MainCode = "#include \"level1.h\"" ; |
| 125 | std::string = testPath(File: "level1.h" ); |
| 126 | std::string = "#include \"level2.h\"" ; |
| 127 | std::string = testPath(File: "level2.h" ); |
| 128 | std::string = "" ; |
| 129 | |
| 130 | addFile(Path: MainFilePath, Content: MainCode); |
| 131 | addFile(Path: Level1HeaderPath, Content: Level1HeaderCode); |
| 132 | addFile(Path: Level2HeaderPath, Content: Level2HeaderCode); |
| 133 | |
| 134 | IndexFileIn IndexFile = runIndexingAction(MainFilePath); |
| 135 | auto Nodes = toMap(IG: *IndexFile.Sources); |
| 136 | |
| 137 | EXPECT_THAT(Nodes, |
| 138 | UnorderedElementsAre( |
| 139 | Pair(toUri(MainFilePath), |
| 140 | AllOf(isTU(), includesAre({Level1HeaderPath}), |
| 141 | hasDigest(digest(MainCode)))), |
| 142 | Pair(toUri(Level1HeaderPath), |
| 143 | AllOf(Not(isTU()), includesAre({Level2HeaderPath}), |
| 144 | hasDigest(digest(Level1HeaderCode)))), |
| 145 | Pair(toUri(Level2HeaderPath), |
| 146 | AllOf(Not(isTU()), includesAre({}), |
| 147 | hasDigest(digest(Level2HeaderCode)))))); |
| 148 | } |
| 149 | |
| 150 | TEST_F(IndexActionTest, IncludeGraphSelfInclude) { |
| 151 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 152 | std::string MainCode = "#include \"header.h\"" ; |
| 153 | std::string = testPath(File: "header.h" ); |
| 154 | std::string = R"cpp( |
| 155 | #ifndef _GUARD_ |
| 156 | #define _GUARD_ |
| 157 | #include "header.h" |
| 158 | #endif)cpp" ; |
| 159 | |
| 160 | addFile(Path: MainFilePath, Content: MainCode); |
| 161 | addFile(Path: HeaderPath, Content: HeaderCode); |
| 162 | |
| 163 | IndexFileIn IndexFile = runIndexingAction(MainFilePath); |
| 164 | auto Nodes = toMap(IG: *IndexFile.Sources); |
| 165 | |
| 166 | EXPECT_THAT( |
| 167 | Nodes, |
| 168 | UnorderedElementsAre( |
| 169 | Pair(toUri(MainFilePath), AllOf(isTU(), includesAre({HeaderPath}), |
| 170 | hasDigest(digest(MainCode)))), |
| 171 | Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({HeaderPath}), |
| 172 | hasDigest(digest(HeaderCode)))))); |
| 173 | } |
| 174 | |
| 175 | TEST_F(IndexActionTest, IncludeGraphSkippedFile) { |
| 176 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 177 | std::string MainCode = R"cpp( |
| 178 | #include "common.h" |
| 179 | #include "header.h" |
| 180 | )cpp" ; |
| 181 | |
| 182 | std::string = testPath(File: "common.h" ); |
| 183 | std::string = R"cpp( |
| 184 | #ifndef _GUARD_ |
| 185 | #define _GUARD_ |
| 186 | void f(); |
| 187 | #endif)cpp" ; |
| 188 | |
| 189 | std::string = testPath(File: "header.h" ); |
| 190 | std::string = R"cpp( |
| 191 | #include "common.h" |
| 192 | void g();)cpp" ; |
| 193 | |
| 194 | addFile(Path: MainFilePath, Content: MainCode); |
| 195 | addFile(Path: HeaderPath, Content: HeaderCode); |
| 196 | addFile(Path: CommonHeaderPath, Content: CommonHeaderCode); |
| 197 | |
| 198 | IndexFileIn IndexFile = runIndexingAction(MainFilePath); |
| 199 | auto Nodes = toMap(IG: *IndexFile.Sources); |
| 200 | |
| 201 | EXPECT_THAT( |
| 202 | Nodes, UnorderedElementsAre( |
| 203 | Pair(toUri(MainFilePath), |
| 204 | AllOf(isTU(), includesAre({HeaderPath, CommonHeaderPath}), |
| 205 | hasDigest(digest(MainCode)))), |
| 206 | Pair(toUri(HeaderPath), |
| 207 | AllOf(Not(isTU()), includesAre({CommonHeaderPath}), |
| 208 | hasDigest(digest(HeaderCode)))), |
| 209 | Pair(toUri(CommonHeaderPath), |
| 210 | AllOf(Not(isTU()), includesAre({}), |
| 211 | hasDigest(digest(CommonHeaderCode)))))); |
| 212 | } |
| 213 | |
| 214 | TEST_F(IndexActionTest, IncludeGraphDynamicInclude) { |
| 215 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 216 | std::string MainCode = R"cpp( |
| 217 | #ifndef FOO |
| 218 | #define FOO "main.cpp" |
| 219 | #else |
| 220 | #define FOO "header.h" |
| 221 | #endif |
| 222 | |
| 223 | #include FOO)cpp" ; |
| 224 | std::string = testPath(File: "header.h" ); |
| 225 | std::string = "" ; |
| 226 | |
| 227 | addFile(Path: MainFilePath, Content: MainCode); |
| 228 | addFile(Path: HeaderPath, Content: HeaderCode); |
| 229 | |
| 230 | IndexFileIn IndexFile = runIndexingAction(MainFilePath); |
| 231 | auto Nodes = toMap(IG: *IndexFile.Sources); |
| 232 | |
| 233 | EXPECT_THAT( |
| 234 | Nodes, |
| 235 | UnorderedElementsAre( |
| 236 | Pair(toUri(MainFilePath), |
| 237 | AllOf(isTU(), includesAre({MainFilePath, HeaderPath}), |
| 238 | hasDigest(digest(MainCode)))), |
| 239 | Pair(toUri(HeaderPath), AllOf(Not(isTU()), includesAre({}), |
| 240 | hasDigest(digest(HeaderCode)))))); |
| 241 | } |
| 242 | |
| 243 | TEST_F(IndexActionTest, NoWarnings) { |
| 244 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 245 | std::string MainCode = R"cpp( |
| 246 | void foo(int x) { |
| 247 | if (x = 1) // -Wparentheses |
| 248 | return; |
| 249 | if (x = 1) // -Wparentheses |
| 250 | return; |
| 251 | } |
| 252 | void bar() {} |
| 253 | )cpp" ; |
| 254 | addFile(Path: MainFilePath, Content: MainCode); |
| 255 | // We set -ferror-limit so the warning-promoted-to-error would be fatal. |
| 256 | // This would cause indexing to stop (if warnings weren't disabled). |
| 257 | IndexFileIn IndexFile = runIndexingAction( |
| 258 | MainFilePath, ExtraArgs: {"-ferror-limit=1" , "-Wparentheses" , "-Werror" }); |
| 259 | ASSERT_TRUE(IndexFile.Sources); |
| 260 | ASSERT_NE(0u, IndexFile.Sources->size()); |
| 261 | EXPECT_THAT(*IndexFile.Symbols, ElementsAre(hasName("foo" ), hasName("bar" ))); |
| 262 | } |
| 263 | |
| 264 | TEST_F(IndexActionTest, SkipFiles) { |
| 265 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 266 | addFile(Path: MainFilePath, Content: R"cpp( |
| 267 | // clang-format off |
| 268 | #include "good.h" |
| 269 | #include "bad.h" |
| 270 | // clang-format on |
| 271 | )cpp" ); |
| 272 | addFile(Path: testPath(File: "good.h" ), Content: R"cpp( |
| 273 | struct S { int s; }; |
| 274 | void f1() { S f; } |
| 275 | auto unskippable1() { return S(); } |
| 276 | )cpp" ); |
| 277 | addFile(Path: testPath(File: "bad.h" ), Content: R"cpp( |
| 278 | struct T { S t; }; |
| 279 | void f2() { S f; } |
| 280 | auto unskippable2() { return S(); } |
| 281 | )cpp" ); |
| 282 | Opts.FileFilter = [](const SourceManager &SM, FileID F) { |
| 283 | return !SM.getFileEntryRefForID(FID: F)->getName().ends_with(Suffix: "bad.h" ); |
| 284 | }; |
| 285 | IndexFileIn IndexFile = runIndexingAction(MainFilePath, ExtraArgs: {"-std=c++14" }); |
| 286 | EXPECT_THAT(*IndexFile.Symbols, |
| 287 | UnorderedElementsAre(hasName("S" ), hasName("s" ), hasName("f1" ), |
| 288 | hasName("unskippable1" ))); |
| 289 | for (const auto &Pair : *IndexFile.Refs) |
| 290 | for (const auto &Ref : Pair.second) |
| 291 | EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h" )); |
| 292 | } |
| 293 | |
| 294 | TEST_F(IndexActionTest, SkipNestedSymbols) { |
| 295 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 296 | addFile(Path: MainFilePath, Content: R"cpp( |
| 297 | namespace ns1 { |
| 298 | namespace ns2 { |
| 299 | namespace ns3 { |
| 300 | namespace ns4 { |
| 301 | namespace ns5 { |
| 302 | namespace ns6 { |
| 303 | namespace ns7 { |
| 304 | namespace ns8 { |
| 305 | namespace ns9 { |
| 306 | class Bar {}; |
| 307 | void foo() { |
| 308 | class Baz {}; |
| 309 | } |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | } |
| 317 | } |
| 318 | })cpp" ); |
| 319 | IndexFileIn IndexFile = runIndexingAction(MainFilePath, ExtraArgs: {"-std=c++14" }); |
| 320 | EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("foo" ))); |
| 321 | EXPECT_THAT(*IndexFile.Symbols, testing::Contains(hasName("Bar" ))); |
| 322 | EXPECT_THAT(*IndexFile.Symbols, Not(testing::Contains(hasName("Baz" )))); |
| 323 | } |
| 324 | |
| 325 | TEST_F(IndexActionTest, SymbolFromCC) { |
| 326 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 327 | addFile(Path: MainFilePath, Content: R"cpp( |
| 328 | #include "main.h" |
| 329 | void foo() {} |
| 330 | )cpp" ); |
| 331 | addFile(Path: testPath(File: "main.h" ), Content: R"cpp( |
| 332 | #pragma once |
| 333 | void foo(); |
| 334 | )cpp" ); |
| 335 | Opts.FileFilter = [](const SourceManager &SM, FileID F) { |
| 336 | return !SM.getFileEntryRefForID(FID: F)->getName().ends_with(Suffix: "main.h" ); |
| 337 | }; |
| 338 | IndexFileIn IndexFile = runIndexingAction(MainFilePath, ExtraArgs: {"-std=c++14" }); |
| 339 | EXPECT_THAT(*IndexFile.Symbols, |
| 340 | UnorderedElementsAre(AllOf( |
| 341 | hasName("foo" ), |
| 342 | includeHeader(URI::create(testPath("main.h" )).toString())))); |
| 343 | } |
| 344 | |
| 345 | TEST_F(IndexActionTest, IncludeHeaderForwardDecls) { |
| 346 | std::string MainFilePath = testPath(File: "main.cpp" ); |
| 347 | addFile(Path: MainFilePath, Content: R"cpp( |
| 348 | #include "fwd.h" |
| 349 | #include "full.h" |
| 350 | )cpp" ); |
| 351 | addFile(Path: testPath(File: "fwd.h" ), Content: R"cpp( |
| 352 | #ifndef _FWD_H_ |
| 353 | #define _FWD_H_ |
| 354 | struct Foo; |
| 355 | #endif |
| 356 | )cpp" ); |
| 357 | addFile(Path: testPath(File: "full.h" ), Content: R"cpp( |
| 358 | #ifndef _FULL_H_ |
| 359 | #define _FULL_H_ |
| 360 | struct Foo {}; |
| 361 | |
| 362 | // This decl is important, as otherwise we detect control macro for the file, |
| 363 | // before handling definition of Foo. |
| 364 | void other(); |
| 365 | #endif |
| 366 | )cpp" ); |
| 367 | IndexFileIn IndexFile = runIndexingAction(MainFilePath); |
| 368 | EXPECT_THAT(*IndexFile.Symbols, |
| 369 | testing::Contains(AllOf( |
| 370 | hasName("Foo" ), |
| 371 | includeHeader(URI::create(testPath("full.h" )).toString())))) |
| 372 | << *IndexFile.Symbols->begin(); |
| 373 | } |
| 374 | } // namespace |
| 375 | } // namespace clangd |
| 376 | } // namespace clang |
| 377 | |