| 1 | //===-- FileIndexTests.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 "Annotations.h" |
| 10 | #include "Compiler.h" |
| 11 | #include "Headers.h" |
| 12 | #include "ParsedAST.h" |
| 13 | #include "SyncAPI.h" |
| 14 | #include "TestFS.h" |
| 15 | #include "TestTU.h" |
| 16 | #include "TestWorkspace.h" |
| 17 | #include "URI.h" |
| 18 | #include "clang-include-cleaner/Record.h" |
| 19 | #include "index/FileIndex.h" |
| 20 | #include "index/Index.h" |
| 21 | #include "index/Ref.h" |
| 22 | #include "index/Relation.h" |
| 23 | #include "index/Serialization.h" |
| 24 | #include "index/Symbol.h" |
| 25 | #include "index/SymbolID.h" |
| 26 | #include "support/Threading.h" |
| 27 | #include "clang/Frontend/CompilerInvocation.h" |
| 28 | #include "clang/Tooling/CompilationDatabase.h" |
| 29 | #include "llvm/ADT/ArrayRef.h" |
| 30 | #include "llvm/Support/Allocator.h" |
| 31 | #include "gmock/gmock.h" |
| 32 | #include "gtest/gtest.h" |
| 33 | #include <memory> |
| 34 | #include <utility> |
| 35 | #include <vector> |
| 36 | |
| 37 | using ::testing::_; |
| 38 | using ::testing::AllOf; |
| 39 | using ::testing::Contains; |
| 40 | using ::testing::ElementsAre; |
| 41 | using ::testing::Gt; |
| 42 | using ::testing::IsEmpty; |
| 43 | using ::testing::Pair; |
| 44 | using ::testing::UnorderedElementsAre; |
| 45 | |
| 46 | MATCHER_P(refRange, Range, "" ) { |
| 47 | return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), |
| 48 | arg.Location.End.line(), arg.Location.End.column()) == |
| 49 | std::make_tuple(Range.start.line, Range.start.character, |
| 50 | Range.end.line, Range.end.character); |
| 51 | } |
| 52 | MATCHER_P(fileURI, F, "" ) { return llvm::StringRef(arg.Location.FileURI) == F; } |
| 53 | MATCHER_P(declURI, U, "" ) { |
| 54 | return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U; |
| 55 | } |
| 56 | MATCHER_P(defURI, U, "" ) { |
| 57 | return llvm::StringRef(arg.Definition.FileURI) == U; |
| 58 | } |
| 59 | MATCHER_P(qName, N, "" ) { return (arg.Scope + arg.Name).str() == N; } |
| 60 | MATCHER_P(numReferences, N, "" ) { return arg.References == N; } |
| 61 | MATCHER_P(hasOrign, O, "" ) { return bool(arg.Origin & O); } |
| 62 | |
| 63 | MATCHER_P(, , "" ) { |
| 64 | return (arg.IncludeHeaders.size() == 1) && |
| 65 | (arg.IncludeHeaders.begin()->IncludeHeader == P); |
| 66 | } |
| 67 | |
| 68 | namespace clang { |
| 69 | namespace clangd { |
| 70 | namespace { |
| 71 | ::testing::Matcher<const RefSlab &> |
| 72 | refsAre(std::vector<::testing::Matcher<Ref>> Matchers) { |
| 73 | return ElementsAre(matchers: ::testing::Pair(first_matcher: _, second_matcher: UnorderedElementsAreArray(container: Matchers))); |
| 74 | } |
| 75 | |
| 76 | Symbol symbol(llvm::StringRef ID) { |
| 77 | Symbol Sym; |
| 78 | Sym.ID = SymbolID(ID); |
| 79 | Sym.Name = ID; |
| 80 | return Sym; |
| 81 | } |
| 82 | |
| 83 | std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) { |
| 84 | SymbolSlab::Builder Slab; |
| 85 | for (int I = Begin; I <= End; I++) |
| 86 | Slab.insert(S: symbol(ID: std::to_string(val: I))); |
| 87 | return std::make_unique<SymbolSlab>(args: std::move(Slab).build()); |
| 88 | } |
| 89 | |
| 90 | std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) { |
| 91 | RefSlab::Builder Slab; |
| 92 | Ref R; |
| 93 | R.Location.FileURI = Path; |
| 94 | R.Kind = RefKind::Reference; |
| 95 | Slab.insert(ID, S: R); |
| 96 | return std::make_unique<RefSlab>(args: std::move(Slab).build()); |
| 97 | } |
| 98 | |
| 99 | std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) { |
| 100 | RelationSlab::Builder RelBuilder; |
| 101 | for (auto &Rel : Rels) |
| 102 | RelBuilder.insert(R: Rel); |
| 103 | return std::make_unique<RelationSlab>(args: std::move(RelBuilder).build()); |
| 104 | } |
| 105 | |
| 106 | TEST(FileSymbolsTest, UpdateAndGet) { |
| 107 | FileSymbols FS(IndexContents::All, true); |
| 108 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), "" ), IsEmpty()); |
| 109 | |
| 110 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.cc" ), Relations: nullptr, |
| 111 | CountReferences: false); |
| 112 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), "" ), |
| 113 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
| 114 | EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1" )), |
| 115 | refsAre({fileURI("f1.cc" )})); |
| 116 | } |
| 117 | |
| 118 | TEST(FileSymbolsTest, Overlap) { |
| 119 | FileSymbols FS(IndexContents::All, true); |
| 120 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 121 | FS.update(Key: "f2" , Symbols: numSlab(Begin: 3, End: 5), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 122 | for (auto Type : {IndexType::Light, IndexType::Heavy}) |
| 123 | EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), "" ), |
| 124 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ), |
| 125 | qName("4" ), qName("5" ))); |
| 126 | } |
| 127 | |
| 128 | TEST(FileSymbolsTest, MergeOverlap) { |
| 129 | FileSymbols FS(IndexContents::All, true); |
| 130 | auto OneSymboSlab = [](Symbol Sym) { |
| 131 | SymbolSlab::Builder S; |
| 132 | S.insert(S: Sym); |
| 133 | return std::make_unique<SymbolSlab>(args: std::move(S).build()); |
| 134 | }; |
| 135 | auto X1 = symbol(ID: "x" ); |
| 136 | X1.CanonicalDeclaration.FileURI = "file:///x1" ; |
| 137 | auto X2 = symbol(ID: "x" ); |
| 138 | X2.Definition.FileURI = "file:///x2" ; |
| 139 | |
| 140 | FS.update(Key: "f1" , Symbols: OneSymboSlab(X1), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 141 | FS.update(Key: "f2" , Symbols: OneSymboSlab(X2), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 142 | for (auto Type : {IndexType::Light, IndexType::Heavy}) |
| 143 | EXPECT_THAT( |
| 144 | runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x" ), |
| 145 | UnorderedElementsAre( |
| 146 | AllOf(qName("x" ), declURI("file:///x1" ), defURI("file:///x2" )))); |
| 147 | } |
| 148 | |
| 149 | TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { |
| 150 | FileSymbols FS(IndexContents::All, true); |
| 151 | |
| 152 | SymbolID ID("1" ); |
| 153 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID, Path: "f1.cc" ), Relations: nullptr, CountReferences: false); |
| 154 | |
| 155 | auto Symbols = FS.buildIndex(IndexType::Light); |
| 156 | EXPECT_THAT(runFuzzyFind(*Symbols, "" ), |
| 157 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
| 158 | EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc" )})); |
| 159 | |
| 160 | FS.update(Key: "f1" , Symbols: nullptr, Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 161 | auto Empty = FS.buildIndex(IndexType::Light); |
| 162 | EXPECT_THAT(runFuzzyFind(*Empty, "" ), IsEmpty()); |
| 163 | EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); |
| 164 | |
| 165 | EXPECT_THAT(runFuzzyFind(*Symbols, "" ), |
| 166 | UnorderedElementsAre(qName("1" ), qName("2" ), qName("3" ))); |
| 167 | EXPECT_THAT(getRefs(*Symbols, ID), refsAre({fileURI("f1.cc" )})); |
| 168 | } |
| 169 | |
| 170 | // Adds Basename.cpp, which includes Basename.h, which contains Code. |
| 171 | void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) { |
| 172 | TestTU File; |
| 173 | File.Filename = (Basename + ".cpp" ).str(); |
| 174 | File.HeaderFilename = (Basename + ".h" ).str(); |
| 175 | File.HeaderCode = std::string(Code); |
| 176 | auto AST = File.build(); |
| 177 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
| 178 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 179 | PI: AST.getPragmaIncludes()); |
| 180 | } |
| 181 | |
| 182 | TEST(FileIndexTest, CustomizedURIScheme) { |
| 183 | FileIndex M(true); |
| 184 | update(M, Basename: "f" , Code: "class string {};" ); |
| 185 | |
| 186 | EXPECT_THAT(runFuzzyFind(M, "" ), ElementsAre(declURI("unittest:///f.h" ))); |
| 187 | } |
| 188 | |
| 189 | TEST(FileIndexTest, IndexAST) { |
| 190 | FileIndex M(true); |
| 191 | update(M, Basename: "f1" , Code: "namespace ns { void f() {} class X {}; }" ); |
| 192 | |
| 193 | FuzzyFindRequest Req; |
| 194 | Req.Query = "" ; |
| 195 | Req.Scopes = {"ns::" }; |
| 196 | EXPECT_THAT(runFuzzyFind(M, Req), |
| 197 | UnorderedElementsAre(qName("ns::f" ), qName("ns::X" ))); |
| 198 | } |
| 199 | |
| 200 | TEST(FileIndexTest, NoLocal) { |
| 201 | FileIndex M(true); |
| 202 | update(M, Basename: "f1" , Code: "namespace ns { void f() { int local = 0; } class X {}; }" ); |
| 203 | |
| 204 | EXPECT_THAT( |
| 205 | runFuzzyFind(M, "" ), |
| 206 | UnorderedElementsAre(qName("ns" ), qName("ns::f" ), qName("ns::X" ))); |
| 207 | } |
| 208 | |
| 209 | TEST(FileIndexTest, IndexMultiASTAndDeduplicate) { |
| 210 | FileIndex M(true); |
| 211 | update(M, Basename: "f1" , Code: "namespace ns { void f() {} class X {}; }" ); |
| 212 | update(M, Basename: "f2" , Code: "namespace ns { void ff() {} class X {}; }" ); |
| 213 | |
| 214 | FuzzyFindRequest Req; |
| 215 | Req.Scopes = {"ns::" }; |
| 216 | EXPECT_THAT( |
| 217 | runFuzzyFind(M, Req), |
| 218 | UnorderedElementsAre(qName("ns::f" ), qName("ns::X" ), qName("ns::ff" ))); |
| 219 | } |
| 220 | |
| 221 | TEST(FileIndexTest, ClassMembers) { |
| 222 | FileIndex M(true); |
| 223 | update(M, Basename: "f1" , Code: "class X { static int m1; int m2; static void f(); };" ); |
| 224 | |
| 225 | EXPECT_THAT(runFuzzyFind(M, "" ), |
| 226 | UnorderedElementsAre(qName("X" ), qName("X::m1" ), qName("X::m2" ), |
| 227 | qName("X::f" ))); |
| 228 | } |
| 229 | |
| 230 | TEST(FileIndexTest, IncludeCollected) { |
| 231 | FileIndex M(true); |
| 232 | update( |
| 233 | M, Basename: "f" , |
| 234 | Code: "// IWYU pragma: private, include <the/good/header.h>\nclass string {};" ); |
| 235 | |
| 236 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
| 237 | EXPECT_THAT(Symbols, ElementsAre(_)); |
| 238 | EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, |
| 239 | "<the/good/header.h>" ); |
| 240 | } |
| 241 | |
| 242 | TEST(FileIndexTest, IWYUPragmaExport) { |
| 243 | FileIndex M(true); |
| 244 | |
| 245 | TestTU File; |
| 246 | File.Code = R"cpp(#pragma once |
| 247 | #include "exporter.h" |
| 248 | )cpp" ; |
| 249 | File.HeaderFilename = "exporter.h" ; |
| 250 | File.HeaderCode = R"cpp(#pragma once |
| 251 | #include "private.h" // IWYU pragma: export |
| 252 | )cpp" ; |
| 253 | File.AdditionalFiles["private.h" ] = "class Foo{};" ; |
| 254 | auto AST = File.build(); |
| 255 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
| 256 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 257 | PI: AST.getPragmaIncludes()); |
| 258 | |
| 259 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
| 260 | EXPECT_THAT( |
| 261 | Symbols, |
| 262 | UnorderedElementsAre(AllOf( |
| 263 | qName("Foo" ), |
| 264 | includeHeader(URI::create(testPath(File.HeaderFilename)).toString()), |
| 265 | declURI(URI::create(testPath("private.h" )).toString())))); |
| 266 | } |
| 267 | |
| 268 | TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) { |
| 269 | TestTU TU; |
| 270 | TU.HeaderCode = "class Foo{};" ; |
| 271 | TU.HeaderFilename = "algorithm" ; |
| 272 | |
| 273 | auto Symbols = runFuzzyFind(Index: *TU.index(), Query: "" ); |
| 274 | EXPECT_THAT(Symbols, ElementsAre(_)); |
| 275 | EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, |
| 276 | "<algorithm>" ); |
| 277 | } |
| 278 | |
| 279 | TEST(FileIndexTest, TemplateParamsInLabel) { |
| 280 | auto *Source = R"cpp( |
| 281 | template <class Ty> |
| 282 | class vector { |
| 283 | }; |
| 284 | |
| 285 | template <class Ty, class Arg> |
| 286 | vector<Ty> make_vector(Arg A) {} |
| 287 | )cpp" ; |
| 288 | |
| 289 | FileIndex M(true); |
| 290 | update(M, Basename: "f" , Code: Source); |
| 291 | |
| 292 | auto Symbols = runFuzzyFind(Index: M, Query: "" ); |
| 293 | EXPECT_THAT(Symbols, |
| 294 | UnorderedElementsAre(qName("vector" ), qName("make_vector" ))); |
| 295 | auto It = Symbols.begin(); |
| 296 | Symbol Vector = *It++; |
| 297 | Symbol MakeVector = *It++; |
| 298 | if (MakeVector.Name == "vector" ) |
| 299 | std::swap(a&: MakeVector, b&: Vector); |
| 300 | |
| 301 | EXPECT_EQ(Vector.Signature, "<class Ty>" ); |
| 302 | EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>" ); |
| 303 | |
| 304 | EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)" ); |
| 305 | EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})" ); |
| 306 | } |
| 307 | |
| 308 | TEST(FileIndexTest, RebuildWithPreamble) { |
| 309 | auto FooCpp = testPath(File: "foo.cpp" ); |
| 310 | auto FooH = testPath(File: "foo.h" ); |
| 311 | // Preparse ParseInputs. |
| 312 | ParseInputs PI; |
| 313 | PI.CompileCommand.Directory = testRoot(); |
| 314 | PI.CompileCommand.Filename = FooCpp; |
| 315 | PI.CompileCommand.CommandLine = {"clang" , "-xc++" , FooCpp}; |
| 316 | |
| 317 | MockFS FS; |
| 318 | FS.Files[FooCpp] = "" ; |
| 319 | FS.Files[FooH] = R"cpp( |
| 320 | namespace ns_in_header { |
| 321 | int func_in_header(); |
| 322 | } |
| 323 | )cpp" ; |
| 324 | PI.TFS = &FS; |
| 325 | |
| 326 | PI.Contents = R"cpp( |
| 327 | #include "foo.h" |
| 328 | namespace ns_in_source { |
| 329 | int func_in_source(); |
| 330 | } |
| 331 | )cpp" ; |
| 332 | |
| 333 | // Rebuild the file. |
| 334 | IgnoreDiagnostics IgnoreDiags; |
| 335 | auto CI = buildCompilerInvocation(Inputs: PI, D&: IgnoreDiags); |
| 336 | |
| 337 | FileIndex Index(true); |
| 338 | bool IndexUpdated = false; |
| 339 | buildPreamble( |
| 340 | FileName: FooCpp, CI: *CI, Inputs: PI, |
| 341 | /*StoreInMemory=*/true, |
| 342 | PreambleCallback: [&](CapturedASTCtx ASTCtx, |
| 343 | std::shared_ptr<const include_cleaner::PragmaIncludes> PI) { |
| 344 | auto &Ctx = ASTCtx.getASTContext(); |
| 345 | auto &PP = ASTCtx.getPreprocessor(); |
| 346 | EXPECT_FALSE(IndexUpdated) << "Expected only a single index update" ; |
| 347 | IndexUpdated = true; |
| 348 | Index.updatePreamble(Path: FooCpp, /*Version=*/"null" , AST&: Ctx, PP, PI: *PI); |
| 349 | }); |
| 350 | ASSERT_TRUE(IndexUpdated); |
| 351 | |
| 352 | // Check the index contains symbols from the preamble, but not from the main |
| 353 | // file. |
| 354 | FuzzyFindRequest Req; |
| 355 | Req.Query = "" ; |
| 356 | Req.Scopes = {"" , "ns_in_header::" }; |
| 357 | |
| 358 | EXPECT_THAT(runFuzzyFind(Index, Req), |
| 359 | UnorderedElementsAre(qName("ns_in_header" ), |
| 360 | qName("ns_in_header::func_in_header" ))); |
| 361 | } |
| 362 | |
| 363 | TEST(FileIndexTest, Refs) { |
| 364 | const char * = "class Foo {};" ; |
| 365 | Annotations MainCode(R"cpp( |
| 366 | void f() { |
| 367 | $foo[[Foo]] foo; |
| 368 | } |
| 369 | )cpp" ); |
| 370 | |
| 371 | auto Foo = |
| 372 | findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), QName: "Foo" ); |
| 373 | |
| 374 | RefsRequest Request; |
| 375 | Request.IDs = {Foo.ID}; |
| 376 | |
| 377 | FileIndex Index(true); |
| 378 | // Add test.cc |
| 379 | TestTU Test; |
| 380 | Test.HeaderCode = HeaderCode; |
| 381 | Test.Code = std::string(MainCode.code()); |
| 382 | Test.Filename = "test.cc" ; |
| 383 | auto AST = Test.build(); |
| 384 | Index.updateMain(Path: testPath(File: Test.Filename), AST); |
| 385 | // Add test2.cc |
| 386 | TestTU Test2; |
| 387 | Test2.HeaderCode = HeaderCode; |
| 388 | Test2.Code = std::string(MainCode.code()); |
| 389 | Test2.Filename = "test2.cc" ; |
| 390 | AST = Test2.build(); |
| 391 | Index.updateMain(Path: testPath(File: Test2.Filename), AST); |
| 392 | |
| 393 | EXPECT_THAT(getRefs(Index, Foo.ID), |
| 394 | refsAre({AllOf(refRange(MainCode.range("foo" )), |
| 395 | fileURI("unittest:///test.cc" )), |
| 396 | AllOf(refRange(MainCode.range("foo" )), |
| 397 | fileURI("unittest:///test2.cc" ))})); |
| 398 | } |
| 399 | |
| 400 | TEST(FileIndexTest, MacroRefs) { |
| 401 | Annotations (R"cpp( |
| 402 | #define $def1[[HEADER_MACRO]](X) (X+1) |
| 403 | )cpp" ); |
| 404 | Annotations MainCode(R"cpp( |
| 405 | #define $def2[[MAINFILE_MACRO]](X) (X+1) |
| 406 | void f() { |
| 407 | int a = $ref1[[HEADER_MACRO]](2); |
| 408 | int b = $ref2[[MAINFILE_MACRO]](1); |
| 409 | } |
| 410 | )cpp" ); |
| 411 | |
| 412 | FileIndex Index(true); |
| 413 | // Add test.cc |
| 414 | TestTU Test; |
| 415 | Test.HeaderCode = std::string(HeaderCode.code()); |
| 416 | Test.Code = std::string(MainCode.code()); |
| 417 | Test.Filename = "test.cc" ; |
| 418 | auto AST = Test.build(); |
| 419 | Index.updateMain(Path: testPath(File: Test.Filename), AST); |
| 420 | |
| 421 | auto = findSymbol(Test.headerSymbols(), QName: "HEADER_MACRO" ); |
| 422 | EXPECT_THAT(getRefs(Index, HeaderMacro.ID), |
| 423 | refsAre({AllOf(refRange(MainCode.range("ref1" )), |
| 424 | fileURI("unittest:///test.cc" ))})); |
| 425 | |
| 426 | auto MainFileMacro = findSymbol(Test.headerSymbols(), QName: "MAINFILE_MACRO" ); |
| 427 | EXPECT_THAT(getRefs(Index, MainFileMacro.ID), |
| 428 | refsAre({AllOf(refRange(MainCode.range("def2" )), |
| 429 | fileURI("unittest:///test.cc" )), |
| 430 | AllOf(refRange(MainCode.range("ref2" )), |
| 431 | fileURI("unittest:///test.cc" ))})); |
| 432 | } |
| 433 | |
| 434 | TEST(FileIndexTest, CollectMacros) { |
| 435 | FileIndex M(true); |
| 436 | update(M, Basename: "f" , Code: "#define CLANGD 1" ); |
| 437 | EXPECT_THAT(runFuzzyFind(M, "" ), Contains(qName("CLANGD" ))); |
| 438 | } |
| 439 | |
| 440 | TEST(FileIndexTest, Relations) { |
| 441 | TestTU TU; |
| 442 | TU.Filename = "f.cpp" ; |
| 443 | TU.HeaderFilename = "f.h" ; |
| 444 | TU.HeaderCode = "class A {}; class B : public A {};" ; |
| 445 | auto AST = TU.build(); |
| 446 | FileIndex Index(true); |
| 447 | Index.updatePreamble(Path: testPath(File: TU.Filename), /*Version=*/"null" , |
| 448 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 449 | PI: AST.getPragmaIncludes()); |
| 450 | SymbolID A = findSymbol(TU.headerSymbols(), QName: "A" ).ID; |
| 451 | uint32_t Results = 0; |
| 452 | RelationsRequest Req; |
| 453 | Req.Subjects.insert(V: A); |
| 454 | Req.Predicate = RelationKind::BaseOf; |
| 455 | Index.relations(Req, [&](const SymbolID &, const Symbol &) { ++Results; }); |
| 456 | EXPECT_EQ(Results, 1u); |
| 457 | } |
| 458 | |
| 459 | TEST(FileIndexTest, RelationsMultiFile) { |
| 460 | TestWorkspace Workspace; |
| 461 | Workspace.addSource(Filename: "Base.h" , Code: "class Base {};" ); |
| 462 | Workspace.addMainFile(Filename: "A.cpp" , Code: R"cpp( |
| 463 | #include "Base.h" |
| 464 | class A : public Base {}; |
| 465 | )cpp" ); |
| 466 | Workspace.addMainFile(Filename: "B.cpp" , Code: R"cpp( |
| 467 | #include "Base.h" |
| 468 | class B : public Base {}; |
| 469 | )cpp" ); |
| 470 | |
| 471 | auto Index = Workspace.index(); |
| 472 | FuzzyFindRequest FFReq; |
| 473 | FFReq.Query = "Base" ; |
| 474 | FFReq.AnyScope = true; |
| 475 | SymbolID Base; |
| 476 | Index->fuzzyFind(Req: FFReq, Callback: [&](const Symbol &S) { Base = S.ID; }); |
| 477 | |
| 478 | RelationsRequest Req; |
| 479 | Req.Subjects.insert(V: Base); |
| 480 | Req.Predicate = RelationKind::BaseOf; |
| 481 | uint32_t Results = 0; |
| 482 | Index->relations(Req, Callback: [&](const SymbolID &, const Symbol &) { ++Results; }); |
| 483 | EXPECT_EQ(Results, 2u); |
| 484 | } |
| 485 | |
| 486 | TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { |
| 487 | TestTU TU; |
| 488 | TU.HeaderCode = "class Foo{};" ; |
| 489 | Annotations Main(R"cpp( |
| 490 | void f() { |
| 491 | [[Foo]] foo; |
| 492 | } |
| 493 | )cpp" ); |
| 494 | TU.Code = std::string(Main.code()); |
| 495 | auto AST = TU.build(); |
| 496 | FileIndex Index(true); |
| 497 | Index.updateMain(Path: testPath(File: TU.Filename), AST); |
| 498 | |
| 499 | // Expect to see references in main file, references in headers are excluded |
| 500 | // because we only index main AST. |
| 501 | EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo" ).ID), |
| 502 | refsAre({refRange(Main.range())})); |
| 503 | } |
| 504 | |
| 505 | TEST(FileIndexTest, MergeMainFileSymbols) { |
| 506 | const char * = "void foo();" ; |
| 507 | TestTU = TestTU::withCode(Code: CommonHeader); |
| 508 | TestTU Cpp = TestTU::withCode(Code: "void foo() {}" ); |
| 509 | Cpp.Filename = "foo.cpp" ; |
| 510 | Cpp.HeaderFilename = "foo.h" ; |
| 511 | Cpp.HeaderCode = CommonHeader; |
| 512 | |
| 513 | FileIndex Index(true); |
| 514 | auto = Header.build(); |
| 515 | auto CppAST = Cpp.build(); |
| 516 | Index.updateMain(Path: testPath(File: "foo.h" ), AST&: HeaderAST); |
| 517 | Index.updateMain(Path: testPath(File: "foo.cpp" ), AST&: CppAST); |
| 518 | |
| 519 | auto Symbols = runFuzzyFind(Index, Query: "" ); |
| 520 | // Check foo is merged, foo in Cpp wins (as we see the definition there). |
| 521 | EXPECT_THAT(Symbols, ElementsAre(AllOf(declURI("unittest:///foo.h" ), |
| 522 | defURI("unittest:///foo.cpp" ), |
| 523 | hasOrign(SymbolOrigin::Merge)))); |
| 524 | } |
| 525 | |
| 526 | TEST(FileSymbolsTest, CountReferencesNoRefSlabs) { |
| 527 | FileSymbols FS(IndexContents::All, true); |
| 528 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: true); |
| 529 | FS.update(Key: "f2" , Symbols: numSlab(Begin: 1, End: 3), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 530 | EXPECT_THAT( |
| 531 | runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), |
| 532 | "" ), |
| 533 | UnorderedElementsAre(AllOf(qName("1" ), numReferences(0u)), |
| 534 | AllOf(qName("2" ), numReferences(0u)), |
| 535 | AllOf(qName("3" ), numReferences(0u)))); |
| 536 | } |
| 537 | |
| 538 | TEST(FileSymbolsTest, CountReferencesWithRefSlabs) { |
| 539 | FileSymbols FS(IndexContents::All, true); |
| 540 | FS.update(Key: "f1cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.cpp" ), Relations: nullptr, |
| 541 | CountReferences: true); |
| 542 | FS.update(Key: "f1h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("1" ), Path: "f1.h" ), Relations: nullptr, |
| 543 | CountReferences: false); |
| 544 | FS.update(Key: "f2cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("2" ), Path: "f2.cpp" ), Relations: nullptr, |
| 545 | CountReferences: true); |
| 546 | FS.update(Key: "f2h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("2" ), Path: "f2.h" ), Relations: nullptr, |
| 547 | CountReferences: false); |
| 548 | FS.update(Key: "f3cpp" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("3" ), Path: "f3.cpp" ), Relations: nullptr, |
| 549 | CountReferences: true); |
| 550 | FS.update(Key: "f3h" , Symbols: numSlab(Begin: 1, End: 3), Refs: refSlab(ID: SymbolID("3" ), Path: "f3.h" ), Relations: nullptr, |
| 551 | CountReferences: false); |
| 552 | EXPECT_THAT( |
| 553 | runFuzzyFind(*FS.buildIndex(IndexType::Light, DuplicateHandling::Merge), |
| 554 | "" ), |
| 555 | UnorderedElementsAre(AllOf(qName("1" ), numReferences(1u)), |
| 556 | AllOf(qName("2" ), numReferences(1u)), |
| 557 | AllOf(qName("3" ), numReferences(1u)))); |
| 558 | } |
| 559 | |
| 560 | TEST(FileIndexTest, StalePreambleSymbolsDeleted) { |
| 561 | FileIndex M(true); |
| 562 | TestTU File; |
| 563 | File.HeaderFilename = "a.h" ; |
| 564 | |
| 565 | File.Filename = "f1.cpp" ; |
| 566 | File.HeaderCode = "int a;" ; |
| 567 | auto AST = File.build(); |
| 568 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
| 569 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 570 | PI: AST.getPragmaIncludes()); |
| 571 | EXPECT_THAT(runFuzzyFind(M, "" ), UnorderedElementsAre(qName("a" ))); |
| 572 | |
| 573 | File.Filename = "f2.cpp" ; |
| 574 | File.HeaderCode = "int b;" ; |
| 575 | AST = File.build(); |
| 576 | M.updatePreamble(Path: testPath(File: File.Filename), /*Version=*/"null" , |
| 577 | AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 578 | PI: AST.getPragmaIncludes()); |
| 579 | EXPECT_THAT(runFuzzyFind(M, "" ), UnorderedElementsAre(qName("b" ))); |
| 580 | } |
| 581 | |
| 582 | // Verifies that concurrent calls to updateMain don't "lose" any updates. |
| 583 | TEST(FileIndexTest, Threadsafety) { |
| 584 | FileIndex M(true); |
| 585 | Notification Go; |
| 586 | |
| 587 | constexpr int Count = 10; |
| 588 | { |
| 589 | // Set up workers to concurrently call updateMain() with separate files. |
| 590 | AsyncTaskRunner Pool; |
| 591 | for (unsigned I = 0; I < Count; ++I) { |
| 592 | auto TU = TestTU::withCode(Code: llvm::formatv(Fmt: "int xxx{0};" , Vals&: I).str()); |
| 593 | TU.Filename = llvm::formatv(Fmt: "x{0}.c" , Vals&: I).str(); |
| 594 | Pool.runAsync(Name: TU.Filename, Action: [&, Filename(testPath(File: TU.Filename)), |
| 595 | AST(TU.build())]() mutable { |
| 596 | Go.wait(); |
| 597 | M.updateMain(Path: Filename, AST); |
| 598 | }); |
| 599 | } |
| 600 | // On your marks, get set... |
| 601 | Go.notify(); |
| 602 | } |
| 603 | |
| 604 | EXPECT_THAT(runFuzzyFind(M, "xxx" ), ::testing::SizeIs(Count)); |
| 605 | } |
| 606 | |
| 607 | TEST(FileShardedIndexTest, Sharding) { |
| 608 | auto = URI::create(AbsolutePath: testPath(File: "a.h" )).toString(); |
| 609 | auto = URI::create(AbsolutePath: testPath(File: "b.h" )).toString(); |
| 610 | auto BSourceUri = URI::create(AbsolutePath: testPath(File: "b.cc" )).toString(); |
| 611 | |
| 612 | auto Sym1 = symbol(ID: "1" ); |
| 613 | Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str(); |
| 614 | |
| 615 | auto Sym2 = symbol(ID: "2" ); |
| 616 | Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str(); |
| 617 | Sym2.Definition.FileURI = BSourceUri.c_str(); |
| 618 | |
| 619 | auto Sym3 = symbol(ID: "3" ); // not stored |
| 620 | |
| 621 | IndexFileIn IF; |
| 622 | { |
| 623 | SymbolSlab::Builder B; |
| 624 | // Should be stored in only a.h |
| 625 | B.insert(S: Sym1); |
| 626 | // Should be stored in both b.h and b.cc |
| 627 | B.insert(S: Sym2); |
| 628 | IF.Symbols.emplace(args: std::move(B).build()); |
| 629 | } |
| 630 | { |
| 631 | // Should be stored in b.cc |
| 632 | IF.Refs.emplace(args: std::move(*refSlab(ID: Sym1.ID, Path: BSourceUri.c_str()))); |
| 633 | } |
| 634 | { |
| 635 | RelationSlab::Builder B; |
| 636 | // Should be stored in a.h and b.h |
| 637 | B.insert(R: Relation{.Subject: Sym1.ID, .Predicate: RelationKind::BaseOf, .Object: Sym2.ID}); |
| 638 | // Should be stored in a.h and b.h |
| 639 | B.insert(R: Relation{.Subject: Sym2.ID, .Predicate: RelationKind::BaseOf, .Object: Sym1.ID}); |
| 640 | // Should be stored in a.h (where Sym1 is stored) even though |
| 641 | // the relation is dangling as Sym3 is unknown. |
| 642 | B.insert(R: Relation{.Subject: Sym3.ID, .Predicate: RelationKind::BaseOf, .Object: Sym1.ID}); |
| 643 | IF.Relations.emplace(args: std::move(B).build()); |
| 644 | } |
| 645 | |
| 646 | IF.Sources.emplace(); |
| 647 | IncludeGraph &IG = *IF.Sources; |
| 648 | { |
| 649 | // b.cc includes b.h |
| 650 | auto &Node = IG[BSourceUri]; |
| 651 | Node.DirectIncludes = {BHeaderUri}; |
| 652 | Node.URI = BSourceUri; |
| 653 | } |
| 654 | { |
| 655 | // b.h includes a.h |
| 656 | auto &Node = IG[BHeaderUri]; |
| 657 | Node.DirectIncludes = {AHeaderUri}; |
| 658 | Node.URI = BHeaderUri; |
| 659 | } |
| 660 | { |
| 661 | // a.h includes nothing. |
| 662 | auto &Node = IG[AHeaderUri]; |
| 663 | Node.DirectIncludes = {}; |
| 664 | Node.URI = AHeaderUri; |
| 665 | } |
| 666 | |
| 667 | IF.Cmd = tooling::CompileCommand(testRoot(), "b.cc" , {"clang" }, "out" ); |
| 668 | |
| 669 | FileShardedIndex ShardedIndex(std::move(IF)); |
| 670 | ASSERT_THAT(ShardedIndex.getAllSources(), |
| 671 | UnorderedElementsAre(AHeaderUri, BHeaderUri, BSourceUri)); |
| 672 | |
| 673 | { |
| 674 | auto Shard = ShardedIndex.getShard(Uri: AHeaderUri); |
| 675 | ASSERT_TRUE(Shard); |
| 676 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("1" ))); |
| 677 | EXPECT_THAT(*Shard->Refs, IsEmpty()); |
| 678 | EXPECT_THAT( |
| 679 | *Shard->Relations, |
| 680 | UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, |
| 681 | Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID}, |
| 682 | Relation{Sym3.ID, RelationKind::BaseOf, Sym1.ID})); |
| 683 | ASSERT_THAT(Shard->Sources->keys(), UnorderedElementsAre(AHeaderUri)); |
| 684 | EXPECT_THAT(Shard->Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty()); |
| 685 | EXPECT_TRUE(Shard->Cmd); |
| 686 | } |
| 687 | { |
| 688 | auto Shard = ShardedIndex.getShard(Uri: BHeaderUri); |
| 689 | ASSERT_TRUE(Shard); |
| 690 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2" ))); |
| 691 | EXPECT_THAT(*Shard->Refs, IsEmpty()); |
| 692 | EXPECT_THAT( |
| 693 | *Shard->Relations, |
| 694 | UnorderedElementsAre(Relation{Sym1.ID, RelationKind::BaseOf, Sym2.ID}, |
| 695 | Relation{Sym2.ID, RelationKind::BaseOf, Sym1.ID})); |
| 696 | ASSERT_THAT(Shard->Sources->keys(), |
| 697 | UnorderedElementsAre(BHeaderUri, AHeaderUri)); |
| 698 | EXPECT_THAT(Shard->Sources->lookup(BHeaderUri).DirectIncludes, |
| 699 | UnorderedElementsAre(AHeaderUri)); |
| 700 | EXPECT_TRUE(Shard->Cmd); |
| 701 | } |
| 702 | { |
| 703 | auto Shard = ShardedIndex.getShard(Uri: BSourceUri); |
| 704 | ASSERT_TRUE(Shard); |
| 705 | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("2" ))); |
| 706 | EXPECT_THAT(*Shard->Refs, UnorderedElementsAre(Pair(Sym1.ID, _))); |
| 707 | EXPECT_THAT(*Shard->Relations, IsEmpty()); |
| 708 | ASSERT_THAT(Shard->Sources->keys(), |
| 709 | UnorderedElementsAre(BSourceUri, BHeaderUri)); |
| 710 | EXPECT_THAT(Shard->Sources->lookup(BSourceUri).DirectIncludes, |
| 711 | UnorderedElementsAre(BHeaderUri)); |
| 712 | EXPECT_TRUE(Shard->Cmd); |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | TEST(FileIndexTest, Profile) { |
| 717 | FileIndex FI(true); |
| 718 | |
| 719 | auto FileName = testPath(File: "foo.cpp" ); |
| 720 | auto AST = TestTU::withHeaderCode(HeaderCode: "int a;" ).build(); |
| 721 | FI.updateMain(Path: FileName, AST); |
| 722 | FI.updatePreamble(Path: FileName, Version: "v1" , AST&: AST.getASTContext(), PP&: AST.getPreprocessor(), |
| 723 | PI: AST.getPragmaIncludes()); |
| 724 | |
| 725 | llvm::BumpPtrAllocator Alloc; |
| 726 | MemoryTree MT(&Alloc); |
| 727 | FI.profile(MT); |
| 728 | ASSERT_THAT(MT.children(), |
| 729 | UnorderedElementsAre(Pair("preamble" , _), Pair("main_file" , _))); |
| 730 | |
| 731 | ASSERT_THAT(MT.child("preamble" ).children(), |
| 732 | UnorderedElementsAre(Pair("index" , _), Pair("slabs" , _))); |
| 733 | ASSERT_THAT(MT.child("main_file" ).children(), |
| 734 | UnorderedElementsAre(Pair("index" , _), Pair("slabs" , _))); |
| 735 | |
| 736 | ASSERT_THAT(MT.child("preamble" ).child("index" ).total(), Gt(0U)); |
| 737 | ASSERT_THAT(MT.child("main_file" ).child("index" ).total(), Gt(0U)); |
| 738 | } |
| 739 | |
| 740 | TEST(FileSymbolsTest, Profile) { |
| 741 | FileSymbols FS(IndexContents::All, true); |
| 742 | FS.update(Key: "f1" , Symbols: numSlab(Begin: 1, End: 2), Refs: nullptr, Relations: nullptr, CountReferences: false); |
| 743 | FS.update(Key: "f2" , Symbols: nullptr, Refs: refSlab(ID: SymbolID("1" ), Path: "f1" ), Relations: nullptr, CountReferences: false); |
| 744 | FS.update(Key: "f3" , Symbols: nullptr, Refs: nullptr, |
| 745 | Relations: relSlab(Rels: {{.Subject: SymbolID("1" ), .Predicate: RelationKind::BaseOf, .Object: SymbolID("2" )}}), |
| 746 | CountReferences: false); |
| 747 | llvm::BumpPtrAllocator Alloc; |
| 748 | MemoryTree MT(&Alloc); |
| 749 | FS.profile(MT); |
| 750 | ASSERT_THAT(MT.children(), UnorderedElementsAre(Pair("f1" , _), Pair("f2" , _), |
| 751 | Pair("f3" , _))); |
| 752 | EXPECT_THAT(MT.child("f1" ).children(), ElementsAre(Pair("symbols" , _))); |
| 753 | EXPECT_THAT(MT.child("f1" ).total(), Gt(0U)); |
| 754 | EXPECT_THAT(MT.child("f2" ).children(), ElementsAre(Pair("references" , _))); |
| 755 | EXPECT_THAT(MT.child("f2" ).total(), Gt(0U)); |
| 756 | EXPECT_THAT(MT.child("f3" ).children(), ElementsAre(Pair("relations" , _))); |
| 757 | EXPECT_THAT(MT.child("f3" ).total(), Gt(0U)); |
| 758 | } |
| 759 | |
| 760 | TEST(FileIndexTest, MacrosFromMainFile) { |
| 761 | FileIndex Idx(true); |
| 762 | TestTU TU; |
| 763 | TU.Code = "#pragma once\n#define FOO" ; |
| 764 | TU.Filename = "foo.h" ; |
| 765 | auto AST = TU.build(); |
| 766 | Idx.updateMain(Path: testPath(File: TU.Filename), AST); |
| 767 | |
| 768 | auto Slab = runFuzzyFind(Index: Idx, Query: "" ); |
| 769 | auto &FooSymbol = findSymbol(Slab, QName: "FOO" ); |
| 770 | EXPECT_TRUE(FooSymbol.Flags & Symbol::IndexedForCodeCompletion); |
| 771 | } |
| 772 | |
| 773 | } // namespace |
| 774 | } // namespace clangd |
| 775 | } // namespace clang |
| 776 | |