//===-- IndexTests.cpp -------------------------------*- C++ -*-----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "Annotations.h" #include "SyncAPI.h" #include "TestIndex.h" #include "TestTU.h" #include "index/FileIndex.h" #include "index/Index.h" #include "index/MemIndex.h" #include "index/Merge.h" #include "index/Symbol.h" #include "clang/Index/IndexSymbol.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include using ::testing::_; using ::testing::AllOf; using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::Pair; using ::testing::Pointee; using ::testing::UnorderedElementsAre; namespace clang { namespace clangd { namespace { MATCHER_P(named, N, "") { return arg.Name == N; } MATCHER_P(refRange, Range, "") { return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), arg.Location.End.line(), arg.Location.End.column()) == std::make_tuple(Range.start.line, Range.start.character, Range.end.line, Range.end.character); } MATCHER_P(fileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } TEST(SymbolLocation, Position) { using Position = SymbolLocation::Position; Position Pos; Pos.setLine(1); EXPECT_EQ(1u, Pos.line()); Pos.setColumn(2); EXPECT_EQ(2u, Pos.column()); EXPECT_FALSE(Pos.hasOverflow()); Pos.setLine(Position::MaxLine + 1); // overflow EXPECT_TRUE(Pos.hasOverflow()); EXPECT_EQ(Pos.line(), Position::MaxLine); Pos.setLine(1); // reset the overflowed line. Pos.setColumn(Position::MaxColumn + 1); // overflow EXPECT_TRUE(Pos.hasOverflow()); EXPECT_EQ(Pos.column(), Position::MaxColumn); } TEST(SymbolSlab, FindAndIterate) { SymbolSlab::Builder B; B.insert(symbol("Z")); B.insert(symbol("Y")); B.insert(symbol("X")); EXPECT_EQ(nullptr, B.find(SymbolID("W"))); for (const char *Sym : {"X", "Y", "Z"}) EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(named(Sym))); SymbolSlab S = std::move(B).build(); EXPECT_THAT(S, UnorderedElementsAre(named("X"), named("Y"), named("Z"))); EXPECT_EQ(S.end(), S.find(SymbolID("W"))); for (const char *Sym : {"X", "Y", "Z"}) EXPECT_THAT(*S.find(SymbolID(Sym)), named(Sym)); } TEST(RelationSlab, Lookup) { SymbolID A{"A"}; SymbolID B{"B"}; SymbolID C{"C"}; SymbolID D{"D"}; RelationSlab::Builder Builder; Builder.insert(Relation{A, RelationKind::BaseOf, B}); Builder.insert(Relation{A, RelationKind::BaseOf, C}); Builder.insert(Relation{B, RelationKind::BaseOf, D}); Builder.insert(Relation{C, RelationKind::BaseOf, D}); RelationSlab Slab = std::move(Builder).build(); EXPECT_THAT(Slab.lookup(A, RelationKind::BaseOf), UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B}, Relation{A, RelationKind::BaseOf, C})); } TEST(RelationSlab, Duplicates) { SymbolID A{"A"}; SymbolID B{"B"}; SymbolID C{"C"}; RelationSlab::Builder Builder; Builder.insert(Relation{A, RelationKind::BaseOf, B}); Builder.insert(Relation{A, RelationKind::BaseOf, C}); Builder.insert(Relation{A, RelationKind::BaseOf, B}); RelationSlab Slab = std::move(Builder).build(); EXPECT_THAT(Slab, UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B}, Relation{A, RelationKind::BaseOf, C})); } TEST(SwapIndexTest, OldIndexRecycled) { auto Token = std::make_shared(); std::weak_ptr WeakToken = Token; SwapIndex S(std::make_unique(SymbolSlab(), RefSlab(), RelationSlab(), std::move(Token), /*BackingDataSize=*/0)); EXPECT_FALSE(WeakToken.expired()); // Current MemIndex keeps it alive. S.reset(std::make_unique()); // Now the MemIndex is destroyed. EXPECT_TRUE(WeakToken.expired()); // So the token is too. } TEST(MemIndexTest, MemIndexDeduplicate) { std::vector Symbols = {symbol("1"), symbol("2"), symbol("3"), symbol("2") /* duplicate */}; FuzzyFindRequest Req; Req.Query = "2"; Req.AnyScope = true; MemIndex I(Symbols, RefSlab(), RelationSlab()); EXPECT_THAT(match(I, Req), ElementsAre("2")); } TEST(MemIndexTest, MemIndexLimitedNumMatches) { auto I = MemIndex::build(generateNumSymbols(0, 100), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "5"; Req.AnyScope = true; Req.Limit = 3; bool Incomplete; auto Matches = match(*I, Req, &Incomplete); EXPECT_TRUE(Req.Limit); EXPECT_EQ(Matches.size(), *Req.Limit); EXPECT_TRUE(Incomplete); } TEST(MemIndexTest, FuzzyMatch) { auto I = MemIndex::build( generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "lol"; Req.AnyScope = true; Req.Limit = 2; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); } TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { auto I = MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "y"; Req.AnyScope = true; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); } TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { auto I = MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {""}; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); } TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { auto I = MemIndex::build( generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::"}; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); } TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { auto I = MemIndex::build( generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::", "b::"}; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); } TEST(MemIndexTest, NoMatchNestedScopes) { auto I = MemIndex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::"}; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); } TEST(MemIndexTest, IgnoreCases) { auto I = MemIndex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.Query = "AB"; Req.Scopes = {"ns::"}; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); } TEST(MemIndexTest, Lookup) { auto I = MemIndex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab(), RelationSlab()); EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), UnorderedElementsAre("ns::abc", "ns::xyz")); EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), UnorderedElementsAre("ns::xyz")); EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); } TEST(MemIndexTest, IndexedFiles) { SymbolSlab Symbols; RefSlab Refs; auto Size = Symbols.bytes() + Refs.bytes(); auto Data = std::make_pair(std::move(Symbols), std::move(Refs)); llvm::StringSet<> Files = {"unittest:///foo.cc", "unittest:///bar.cc"}; MemIndex I(std::move(Data.first), std::move(Data.second), RelationSlab(), std::move(Files), IndexContents::All, std::move(Data), Size); auto ContainsFile = I.indexedFiles(); EXPECT_EQ(ContainsFile("unittest:///foo.cc"), IndexContents::All); EXPECT_EQ(ContainsFile("unittest:///bar.cc"), IndexContents::All); EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None); } TEST(MemIndexTest, TemplateSpecialization) { SymbolSlab::Builder B; Symbol S = symbol("TempSpec"); S.ID = SymbolID("1"); B.insert(S); S = symbol("TempSpec"); S.ID = SymbolID("2"); S.TemplateSpecializationArgs = ""; S.SymInfo.Properties = static_cast( index::SymbolProperty::TemplateSpecialization); B.insert(S); S = symbol("TempSpec"); S.ID = SymbolID("3"); S.TemplateSpecializationArgs = ""; S.SymInfo.Properties = static_cast( index::SymbolProperty::TemplatePartialSpecialization); B.insert(S); auto I = MemIndex::build(std::move(B).build(), RefSlab(), RelationSlab()); FuzzyFindRequest Req; Req.AnyScope = true; Req.Query = "TempSpec"; EXPECT_THAT(match(*I, Req), UnorderedElementsAre("TempSpec", "TempSpec", "TempSpec")); // FIXME: Add filtering for template argument list. Req.Query = "TempSpec DynFiles = {"unittest:///foo.cc"}; MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second), RelationSlab(), std::move(DynFiles), IndexContents::Symbols, std::move(DynData), DynSize); SymbolSlab StaticSymbols; RefSlab StaticRefs; auto StaticData = std::make_pair(std::move(StaticSymbols), std::move(StaticRefs)); llvm::StringSet<> StaticFiles = {"unittest:///foo.cc", "unittest:///bar.cc"}; MemIndex StaticIndex( std::move(StaticData.first), std::move(StaticData.second), RelationSlab(), std::move(StaticFiles), IndexContents::References, std::move(StaticData), StaticSymbols.bytes() + StaticRefs.bytes()); MergedIndex Merge(&DynIndex, &StaticIndex); auto ContainsFile = Merge.indexedFiles(); EXPECT_EQ(ContainsFile("unittest:///foo.cc"), IndexContents::Symbols | IndexContents::References); EXPECT_EQ(ContainsFile("unittest:///bar.cc"), IndexContents::References); EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None); } TEST(MergeIndexTest, NonDocumentation) { using index::SymbolKind; Symbol L, R; L.ID = R.ID = SymbolID("x"); L.Definition.FileURI = "file:/x.h"; R.Documentation = "Forward declarations because x.h is too big to include"; for (auto ClassLikeKind : {SymbolKind::Class, SymbolKind::Struct, SymbolKind::Union}) { L.SymInfo.Kind = ClassLikeKind; EXPECT_EQ(mergeSymbol(L, R).Documentation, ""); } L.SymInfo.Kind = SymbolKind::Function; R.Documentation = "Documentation from non-class symbols should be included"; EXPECT_EQ(mergeSymbol(L, R).Documentation, R.Documentation); } MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { return (arg.IncludeHeader == IncludeHeader) && (arg.References == References); } TEST(MergeTest, MergeIncludesOnDifferentDefinitions) { Symbol L, R; L.Name = "left"; R.Name = "right"; L.ID = R.ID = SymbolID("hello"); L.IncludeHeaders.emplace_back("common", 1, Symbol::Include); R.IncludeHeaders.emplace_back("common", 1, Symbol::Include); R.IncludeHeaders.emplace_back("new", 1, Symbol::Include); // Both have no definition. Symbol M = mergeSymbol(L, R); EXPECT_THAT(M.IncludeHeaders, UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), IncludeHeaderWithRef("new", 1u))); // Only merge references of the same includes but do not merge new #includes. L.Definition.FileURI = "file:/left.h"; M = mergeSymbol(L, R); EXPECT_THAT(M.IncludeHeaders, UnorderedElementsAre(IncludeHeaderWithRef("common", 2u))); // Definitions are the same. R.Definition.FileURI = "file:/right.h"; M = mergeSymbol(L, R); EXPECT_THAT(M.IncludeHeaders, UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), IncludeHeaderWithRef("new", 1u))); // Definitions are different. R.Definition.FileURI = "file:/right.h"; M = mergeSymbol(L, R); EXPECT_THAT(M.IncludeHeaders, UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), IncludeHeaderWithRef("new", 1u))); } TEST(MergeIndexTest, IncludeHeadersMerged) { auto S = symbol("Z"); S.Definition.FileURI = "unittest:///foo.cc"; SymbolSlab::Builder DynB; S.IncludeHeaders.clear(); DynB.insert(S); SymbolSlab DynSymbols = std::move(DynB).build(); RefSlab DynRefs; auto DynSize = DynSymbols.bytes() + DynRefs.bytes(); auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs)); llvm::StringSet<> DynFiles = {S.Definition.FileURI}; MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second), RelationSlab(), std::move(DynFiles), IndexContents::Symbols, std::move(DynData), DynSize); SymbolSlab::Builder StaticB; S.IncludeHeaders.push_back({"
", 0, Symbol::Include}); StaticB.insert(S); auto StaticIndex = MemIndex::build(std::move(StaticB).build(), RefSlab(), RelationSlab()); MergedIndex Merge(&DynIndex, StaticIndex.get()); EXPECT_THAT(runFuzzyFind(Merge, S.Name), ElementsAre(testing::Field( &Symbol::IncludeHeaders, ElementsAre(IncludeHeaderWithRef("
", 0u))))); LookupRequest Req; Req.IDs = {S.ID}; std::string IncludeHeader; Merge.lookup(Req, [&](const Symbol &S) { EXPECT_TRUE(IncludeHeader.empty()); ASSERT_EQ(S.IncludeHeaders.size(), 1u); IncludeHeader = S.IncludeHeaders.front().IncludeHeader.str(); }); EXPECT_EQ(IncludeHeader, "
"); } } // namespace } // namespace clangd } // namespace clang