llvm-project/clang-tools-extra/clangd/unittests/IndexTests.cpp

459 lines
16 KiB
C++

//===-- 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, index::SymbolRole::RelationBaseOf, B});
Builder.insert(Relation{A, index::SymbolRole::RelationBaseOf, C});
Builder.insert(Relation{B, index::SymbolRole::RelationBaseOf, D});
Builder.insert(Relation{C, index::SymbolRole::RelationBaseOf, D});
Builder.insert(Relation{B, index::SymbolRole::RelationChildOf, A});
Builder.insert(Relation{C, index::SymbolRole::RelationChildOf, A});
Builder.insert(Relation{D, index::SymbolRole::RelationChildOf, B});
Builder.insert(Relation{D, index::SymbolRole::RelationChildOf, C});
RelationSlab Slab = std::move(Builder).build();
EXPECT_THAT(
Slab.lookup(A, index::SymbolRole::RelationBaseOf),
UnorderedElementsAre(Relation{A, index::SymbolRole::RelationBaseOf, B},
Relation{A, index::SymbolRole::RelationBaseOf, C}));
}
TEST(RelationSlab, Duplicates) {
SymbolID A{"A"};
SymbolID B{"B"};
SymbolID C{"C"};
RelationSlab::Builder Builder;
Builder.insert(Relation{A, index::SymbolRole::RelationBaseOf, B});
Builder.insert(Relation{A, index::SymbolRole::RelationBaseOf, C});
Builder.insert(Relation{A, index::SymbolRole::RelationBaseOf, B});
RelationSlab Slab = std::move(Builder).build();
EXPECT_THAT(Slab, UnorderedElementsAre(
Relation{A, index::SymbolRole::RelationBaseOf, B},
Relation{A, index::SymbolRole::RelationBaseOf, C}));
}
TEST(SwapIndexTest, OldIndexRecycled) {
auto Token = std::make_shared<int>();
std::weak_ptr<int> WeakToken = Token;
SwapIndex S(std::make_unique<MemIndex>(SymbolSlab(), RefSlab(),
RelationSlab(), std::move(Token),
/*BackingDataSize=*/0));
EXPECT_FALSE(WeakToken.expired()); // Current MemIndex keeps it alive.
S.reset(std::make_unique<MemIndex>()); // Now the MemIndex is destroyed.
EXPECT_TRUE(WeakToken.expired()); // So the token is too.
}
TEST(MemIndexTest, MemIndexDeduplicate) {
std::vector<Symbol> 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 = "<int, bool>";
S.SymInfo.Properties = static_cast<index::SymbolPropertySet>(
index::SymbolProperty::TemplateSpecialization);
B.insert(S);
S = symbol("TempSpec");
S.ID = SymbolID("3");
S.TemplateSpecializationArgs = "<int, U>";
S.SymInfo.Properties = static_cast<index::SymbolPropertySet>(
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<int, bool>",
"TempSpec<int, U>"));
// FIXME: Add filtering for template argument list.
Req.Query = "TempSpec<int";
EXPECT_THAT(match(*I, Req), IsEmpty());
}
TEST(MergeIndexTest, Lookup) {
auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab(),
RelationSlab()),
J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab(),
RelationSlab());
MergedIndex M(I.get(), J.get());
EXPECT_THAT(lookup(M, SymbolID("ns::A")), UnorderedElementsAre("ns::A"));
EXPECT_THAT(lookup(M, SymbolID("ns::B")), UnorderedElementsAre("ns::B"));
EXPECT_THAT(lookup(M, SymbolID("ns::C")), UnorderedElementsAre("ns::C"));
EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::B")}),
UnorderedElementsAre("ns::A", "ns::B"));
EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::C")}),
UnorderedElementsAre("ns::A", "ns::C"));
EXPECT_THAT(lookup(M, SymbolID("ns::D")), UnorderedElementsAre());
EXPECT_THAT(lookup(M, {}), UnorderedElementsAre());
}
TEST(MergeIndexTest, FuzzyFind) {
auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab(),
RelationSlab()),
J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab(),
RelationSlab());
FuzzyFindRequest Req;
Req.Scopes = {"ns::"};
EXPECT_THAT(match(MergedIndex(I.get(), J.get()), Req),
UnorderedElementsAre("ns::A", "ns::B", "ns::C"));
}
TEST(MergeTest, Merge) {
Symbol L, R;
L.ID = R.ID = SymbolID("hello");
L.Name = R.Name = "Foo"; // same in both
L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs
R.CanonicalDeclaration.FileURI = "file:///right.h";
L.References = 1;
R.References = 2;
L.Signature = "()"; // present in left only
R.CompletionSnippetSuffix = "{$1:0}"; // present in right only
R.Documentation = "--doc--";
L.Origin = SymbolOrigin::Dynamic;
R.Origin = SymbolOrigin::Static;
R.Type = "expectedType";
Symbol M = mergeSymbol(L, R);
EXPECT_EQ(M.Name, "Foo");
EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:///left.h");
EXPECT_EQ(M.References, 3u);
EXPECT_EQ(M.Signature, "()");
EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}");
EXPECT_EQ(M.Documentation, "--doc--");
EXPECT_EQ(M.Type, "expectedType");
EXPECT_EQ(M.Origin,
SymbolOrigin::Dynamic | SymbolOrigin::Static | SymbolOrigin::Merge);
}
TEST(MergeTest, PreferSymbolWithDefn) {
Symbol L, R;
L.ID = R.ID = SymbolID("hello");
L.CanonicalDeclaration.FileURI = "file:/left.h";
R.CanonicalDeclaration.FileURI = "file:/right.h";
L.Name = "left";
R.Name = "right";
Symbol M = mergeSymbol(L, R);
EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/left.h");
EXPECT_EQ(StringRef(M.Definition.FileURI), "");
EXPECT_EQ(M.Name, "left");
R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored.
M = mergeSymbol(L, R);
EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/right.h");
EXPECT_EQ(StringRef(M.Definition.FileURI), "file:/right.cpp");
EXPECT_EQ(M.Name, "right");
}
TEST(MergeTest, PreferSymbolLocationInCodegenFile) {
Symbol L, R;
L.ID = R.ID = SymbolID("hello");
L.CanonicalDeclaration.FileURI = "file:/x.proto.h";
R.CanonicalDeclaration.FileURI = "file:/x.proto";
Symbol M = mergeSymbol(L, R);
EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/x.proto");
// Prefer L if both have codegen suffix.
L.CanonicalDeclaration.FileURI = "file:/y.proto";
M = mergeSymbol(L, R);
EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/y.proto");
}
TEST(MergeIndexTest, Refs) {
FileIndex Dyn;
FileIndex StaticIndex;
MergedIndex Merge(&Dyn, &StaticIndex);
const char *HeaderCode = "class Foo;";
auto HeaderSymbols = TestTU::withHeaderCode("class Foo;").headerSymbols();
auto Foo = findSymbol(HeaderSymbols, "Foo");
// Build dynamic index for test.cc.
Annotations Test1Code(R"(class $Foo[[Foo]];)");
TestTU Test;
Test.HeaderCode = HeaderCode;
Test.Code = Test1Code.code();
Test.Filename = "test.cc";
auto AST = Test.build();
Dyn.updateMain(Test.Filename, AST);
// Build static index for test.cc.
Test.HeaderCode = HeaderCode;
Test.Code = "// static\nclass Foo {};";
Test.Filename = "test.cc";
auto StaticAST = Test.build();
// Add stale refs for test.cc.
StaticIndex.updateMain(Test.Filename, StaticAST);
// Add refs for test2.cc
Annotations Test2Code(R"(class $Foo[[Foo]] {};)");
TestTU Test2;
Test2.HeaderCode = HeaderCode;
Test2.Code = Test2Code.code();
Test2.Filename = "test2.cc";
StaticAST = Test2.build();
StaticIndex.updateMain(Test2.Filename, StaticAST);
RefsRequest Request;
Request.IDs = {Foo.ID};
RefSlab::Builder Results;
Merge.refs(Request, [&](const Ref &O) { Results.insert(Foo.ID, O); });
EXPECT_THAT(
std::move(Results).build(),
ElementsAre(Pair(
_, UnorderedElementsAre(AllOf(RefRange(Test1Code.range("Foo")),
FileURI("unittest:///test.cc")),
AllOf(RefRange(Test2Code.range("Foo")),
FileURI("unittest:///test2.cc"))))));
Request.Limit = 1;
RefSlab::Builder Results2;
Merge.refs(Request, [&](const Ref &O) { Results2.insert(Foo.ID, O); });
EXPECT_THAT(std::move(Results2).build(),
ElementsAre(Pair(
_, ElementsAre(AnyOf(FileURI("unittest:///test.cc"),
FileURI("unittest:///test2.cc"))))));
}
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);
R.IncludeHeaders.emplace_back("common", 1);
R.IncludeHeaders.emplace_back("new", 1);
// 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)));
}
} // namespace
} // namespace clangd
} // namespace clang