179 changes: 177 additions & 2 deletions clang-tools-extra/unittests/clangd/DexIndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
//
//===----------------------------------------------------------------------===//

#include "TestIndex.h"
#include "index/Index.h"
#include "index/Merge.h"
#include "index/dex/DexIndex.h"
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "index/dex/Trigram.h"
Expand All @@ -17,11 +21,13 @@
#include <string>
#include <vector>

using ::testing::ElementsAre;
using ::testing::UnorderedElementsAre;

namespace clang {
namespace clangd {
namespace dex {

using ::testing::ElementsAre;
namespace {

TEST(DexIndexIterators, DocumentIterator) {
const PostingList L = {4, 7, 8, 20, 42, 100};
Expand Down Expand Up @@ -359,6 +365,175 @@ TEST(DexIndexTrigrams, QueryTrigrams) {
"hij", "ijk", "jkl", "klm"}));
}

TEST(DexIndex, Lookup) {
DexIndex I;
I.build(generateSymbols({"ns::abc", "ns::xyz"}));
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(DexIndex, FuzzyFind) {
DexIndex Index;
Index.build(generateSymbols({"ns::ABC", "ns::BCD", "::ABC", "ns::nested::ABC",
"other::ABC", "other::A"}));
FuzzyFindRequest Req;
Req.Query = "ABC";
Req.Scopes = {"ns::"};
EXPECT_THAT(match(Index, Req), UnorderedElementsAre("ns::ABC"));
Req.Scopes = {"ns::", "ns::nested::"};
EXPECT_THAT(match(Index, Req),
UnorderedElementsAre("ns::ABC", "ns::nested::ABC"));
Req.Query = "A";
Req.Scopes = {"other::"};
EXPECT_THAT(match(Index, Req),
UnorderedElementsAre("other::A", "other::ABC"));
Req.Query = "";
Req.Scopes = {};
EXPECT_THAT(match(Index, Req),
UnorderedElementsAre("ns::ABC", "ns::BCD", "::ABC",
"ns::nested::ABC", "other::ABC",
"other::A"));
}

TEST(DexIndexTest, FuzzyMatchQ) {
DexIndex I;
I.build(
generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}));
FuzzyFindRequest Req;
Req.Query = "lol";
Req.MaxCandidateCount = 2;
EXPECT_THAT(match(I, Req),
UnorderedElementsAre("LaughingOutLoud", "LittleOldLady"));
}

TEST(DexIndexTest, DexIndexSymbolsRecycled) {
DexIndex I;
std::weak_ptr<SlabAndPointers> Symbols;
I.build(generateNumSymbols(0, 10, &Symbols));
FuzzyFindRequest Req;
Req.Query = "7";
EXPECT_THAT(match(I, Req), UnorderedElementsAre("7"));

EXPECT_FALSE(Symbols.expired());
// Release old symbols.
I.build(generateNumSymbols(0, 0));
EXPECT_TRUE(Symbols.expired());
}

// FIXME(kbobyrev): This test is different for DexIndex and MemIndex: while
// MemIndex manages response deduplication, DexIndex simply returns all matched
// symbols which means there might be equivalent symbols in the response.
// Before drop-in replacement of MemIndex with DexIndex happens, FileIndex
// should handle deduplication instead.
TEST(DexIndexTest, DexIndexDeduplicate) {
auto Symbols = generateNumSymbols(0, 10);

// Inject duplicates.
auto Sym = symbol("7");
Symbols->push_back(&Sym);
Symbols->push_back(&Sym);
Symbols->push_back(&Sym);

FuzzyFindRequest Req;
Req.Query = "7";
DexIndex I;
I.build(std::move(Symbols));
auto Matches = match(I, Req);
EXPECT_EQ(Matches.size(), 4u);
}

TEST(DexIndexTest, DexIndexLimitedNumMatches) {
DexIndex I;
I.build(generateNumSymbols(0, 100));
FuzzyFindRequest Req;
Req.Query = "5";
Req.MaxCandidateCount = 3;
bool Incomplete;
auto Matches = match(I, Req, &Incomplete);
EXPECT_EQ(Matches.size(), Req.MaxCandidateCount);
EXPECT_TRUE(Incomplete);
}

TEST(DexIndexTest, FuzzyMatch) {
DexIndex I;
I.build(
generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}));
FuzzyFindRequest Req;
Req.Query = "lol";
Req.MaxCandidateCount = 2;
EXPECT_THAT(match(I, Req),
UnorderedElementsAre("LaughingOutLoud", "LittleOldLady"));
}

TEST(DexIndexTest, MatchQualifiedNamesWithoutSpecificScope) {
DexIndex I;
I.build(generateSymbols({"a::y1", "b::y2", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3"));
}

TEST(DexIndexTest, MatchQualifiedNamesWithGlobalScope) {
DexIndex I;
I.build(generateSymbols({"a::y1", "b::y2", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {""};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("y3"));
}

TEST(DexIndexTest, MatchQualifiedNamesWithOneScope) {
DexIndex I;
I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2"));
}

TEST(DexIndexTest, MatchQualifiedNamesWithMultipleScopes) {
DexIndex I;
I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::", "b::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3"));
}

TEST(DexIndexTest, NoMatchNestedScopes) {
DexIndex I;
I.build(generateSymbols({"a::y1", "a::b::y2"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1"));
}

TEST(DexIndexTest, IgnoreCases) {
DexIndex I;
I.build(generateSymbols({"ns::ABC", "ns::abc"}));
FuzzyFindRequest Req;
Req.Query = "AB";
Req.Scopes = {"ns::"};
EXPECT_THAT(match(I, Req), UnorderedElementsAre("ns::ABC", "ns::abc"));
}

TEST(DexIndexTest, Lookup) {
DexIndex I;
I.build(generateSymbols({"ns::abc", "ns::xyz"}));
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());
}

} // namespace
} // namespace dex
} // namespace clangd
} // namespace clang
84 changes: 3 additions & 81 deletions clang-tools-extra/unittests/clangd/IndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,20 @@
//
//===----------------------------------------------------------------------===//

#include "TestIndex.h"
#include "index/Index.h"
#include "index/MemIndex.h"
#include "index/Merge.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

using testing::UnorderedElementsAre;
using testing::Pointee;
using testing::UnorderedElementsAre;

namespace clang {
namespace clangd {
namespace {

Symbol symbol(llvm::StringRef QName) {
Symbol Sym;
Sym.ID = SymbolID(QName.str());
size_t Pos = QName.rfind("::");
if (Pos == llvm::StringRef::npos) {
Sym.Name = QName;
Sym.Scope = "";
} else {
Sym.Name = QName.substr(Pos + 2);
Sym.Scope = QName.substr(0, Pos + 2);
}
return Sym;
}

MATCHER_P(Named, N, "") { return arg.Name == N; }

TEST(SymbolSlab, FindAndIterate) {
Expand All @@ -52,59 +39,6 @@ TEST(SymbolSlab, FindAndIterate) {
EXPECT_THAT(*S.find(SymbolID(Sym)), Named(Sym));
}

struct SlabAndPointers {
SymbolSlab Slab;
std::vector<const Symbol *> Pointers;
};

// Create a slab of symbols with the given qualified names as both IDs and
// names. The life time of the slab is managed by the returned shared pointer.
// If \p WeakSymbols is provided, it will be pointed to the managed object in
// the returned shared pointer.
std::shared_ptr<std::vector<const Symbol *>>
generateSymbols(std::vector<std::string> QualifiedNames,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr) {
SymbolSlab::Builder Slab;
for (llvm::StringRef QName : QualifiedNames)
Slab.insert(symbol(QName));

auto Storage = std::make_shared<SlabAndPointers>();
Storage->Slab = std::move(Slab).build();
for (const auto &Sym : Storage->Slab)
Storage->Pointers.push_back(&Sym);
if (WeakSymbols)
*WeakSymbols = Storage;
auto *Pointers = &Storage->Pointers;
return {std::move(Storage), Pointers};
}

// Create a slab of symbols with IDs and names [Begin, End], otherwise identical
// to the `generateSymbols` above.
std::shared_ptr<std::vector<const Symbol *>>
generateNumSymbols(int Begin, int End,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr) {
std::vector<std::string> Names;
for (int i = Begin; i <= End; i++)
Names.push_back(std::to_string(i));
return generateSymbols(Names, WeakSymbols);
}

std::string getQualifiedName(const Symbol &Sym) {
return (Sym.Scope + Sym.Name).str();
}

std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req,
bool *Incomplete = nullptr) {
std::vector<std::string> Matches;
bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) {
Matches.push_back(getQualifiedName(Sym));
});
if (Incomplete)
*Incomplete = IsIncomplete;
return Matches;
}

TEST(MemIndexTest, MemIndexSymbolsRecycled) {
MemIndex I;
std::weak_ptr<SlabAndPointers> Symbols;
Expand Down Expand Up @@ -212,18 +146,6 @@ TEST(MemIndexTest, IgnoreCases) {
EXPECT_THAT(match(I, Req), UnorderedElementsAre("ns::ABC", "ns::abc"));
}

// Returns qualified names of symbols with any of IDs in the index.
std::vector<std::string> lookup(const SymbolIndex &I,
llvm::ArrayRef<SymbolID> IDs) {
LookupRequest Req;
Req.IDs.insert(IDs.begin(), IDs.end());
std::vector<std::string> Results;
I.lookup(Req, [&](const Symbol &Sym) {
Results.push_back(getQualifiedName(Sym));
});
return Results;
}

TEST(MemIndexTest, Lookup) {
MemIndex I;
I.build(generateSymbols({"ns::abc", "ns::xyz"}));
Expand Down Expand Up @@ -269,7 +191,7 @@ TEST(MergeIndexTest, FuzzyFind) {
TEST(MergeTest, Merge) {
Symbol L, R;
L.ID = R.ID = SymbolID("hello");
L.Name = R.Name = "Foo"; // same in both
L.Name = R.Name = "Foo"; // same in both
L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs
R.CanonicalDeclaration.FileURI = "file:///right.h";
L.References = 1;
Expand Down
83 changes: 83 additions & 0 deletions clang-tools-extra/unittests/clangd/TestIndex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===-- IndexHelpers.cpp ----------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "TestIndex.h"

namespace clang {
namespace clangd {

Symbol symbol(llvm::StringRef QName) {
Symbol Sym;
Sym.ID = SymbolID(QName.str());
size_t Pos = QName.rfind("::");
if (Pos == llvm::StringRef::npos) {
Sym.Name = QName;
Sym.Scope = "";
} else {
Sym.Name = QName.substr(Pos + 2);
Sym.Scope = QName.substr(0, Pos + 2);
}
return Sym;
}

std::shared_ptr<std::vector<const Symbol *>>
generateSymbols(std::vector<std::string> QualifiedNames,
std::weak_ptr<SlabAndPointers> *WeakSymbols) {
SymbolSlab::Builder Slab;
for (llvm::StringRef QName : QualifiedNames)
Slab.insert(symbol(QName));

auto Storage = std::make_shared<SlabAndPointers>();
Storage->Slab = std::move(Slab).build();
for (const auto &Sym : Storage->Slab)
Storage->Pointers.push_back(&Sym);
if (WeakSymbols)
*WeakSymbols = Storage;
auto *Pointers = &Storage->Pointers;
return {std::move(Storage), Pointers};
}

std::shared_ptr<std::vector<const Symbol *>>
generateNumSymbols(int Begin, int End,
std::weak_ptr<SlabAndPointers> *WeakSymbols) {
std::vector<std::string> Names;
for (int i = Begin; i <= End; i++)
Names.push_back(std::to_string(i));
return generateSymbols(Names, WeakSymbols);
}

std::string getQualifiedName(const Symbol &Sym) {
return (Sym.Scope + Sym.Name).str();
}

std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req, bool *Incomplete) {
std::vector<std::string> Matches;
bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) {
Matches.push_back(clang::clangd::getQualifiedName(Sym));
});
if (Incomplete)
*Incomplete = IsIncomplete;
return Matches;
}

// Returns qualified names of symbols with any of IDs in the index.
std::vector<std::string> lookup(const SymbolIndex &I,
llvm::ArrayRef<SymbolID> IDs) {
LookupRequest Req;
Req.IDs.insert(IDs.begin(), IDs.end());
std::vector<std::string> Results;
I.lookup(Req, [&](const Symbol &Sym) {
Results.push_back(getQualifiedName(Sym));
});
return Results;
}

} // namespace clangd
} // namespace clang
64 changes: 64 additions & 0 deletions clang-tools-extra/unittests/clangd/TestIndex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//===-- IndexHelpers.h ------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_INDEXTESTCOMMON_H
#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_INDEXTESTCOMMON_H

#include "index/Index.h"
#include "index/Merge.h"
#include "index/dex/DexIndex.h"
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "index/dex/Trigram.h"

namespace clang {
namespace clangd {

// Creates Symbol instance and sets SymbolID to given QualifiedName.
Symbol symbol(llvm::StringRef QName);

// Bundles symbol pointers with the actual symbol slab the pointers refer to in
// order to ensure that the slab isn't destroyed while it's used by and index.
struct SlabAndPointers {
SymbolSlab Slab;
std::vector<const Symbol *> Pointers;
};

// Create a slab of symbols with the given qualified names as both IDs and
// names. The life time of the slab is managed by the returned shared pointer.
// If \p WeakSymbols is provided, it will be pointed to the managed object in
// the returned shared pointer.
std::shared_ptr<std::vector<const Symbol *>>
generateSymbols(std::vector<std::string> QualifiedNames,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr);

// Create a slab of symbols with IDs and names [Begin, End], otherwise identical
// to the `generateSymbols` above.
std::shared_ptr<std::vector<const Symbol *>>
generateNumSymbols(int Begin, int End,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr);

// Returns fully-qualified name out of given symbol.
std::string getQualifiedName(const Symbol &Sym);

// Performs fuzzy matching-based symbol lookup given a query and an index.
// Incomplete is set true if more items than requested can be retrieved, false
// otherwise.
std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req,
bool *Incomplete = nullptr);

// Returns qualified names of symbols with any of IDs in the index.
std::vector<std::string> lookup(const SymbolIndex &I,
llvm::ArrayRef<SymbolID> IDs);

} // namespace clangd
} // namespace clang

#endif