//===-- 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 "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" using ::testing::_; using ::testing::AllOf; using ::testing::AnyOf; 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, 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