643 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			643 lines
		
	
	
		
			22 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 "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 <utility>
 | 
						|
 | 
						|
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<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, 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 = "<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, LookupRemovedDefinition) {
 | 
						|
  FileIndex DynamicIndex, StaticIndex;
 | 
						|
  MergedIndex Merge(&DynamicIndex, &StaticIndex);
 | 
						|
 | 
						|
  const char *HeaderCode = "class Foo;";
 | 
						|
  auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols();
 | 
						|
  auto Foo = findSymbol(HeaderSymbols, "Foo");
 | 
						|
 | 
						|
  // Build static index for test.cc with Foo definition
 | 
						|
  TestTU Test;
 | 
						|
  Test.HeaderCode = HeaderCode;
 | 
						|
  Test.Code = "class Foo {};";
 | 
						|
  Test.Filename = "test.cc";
 | 
						|
  auto AST = Test.build();
 | 
						|
  StaticIndex.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  // Remove Foo definition from test.cc, i.e. build dynamic index for test.cc
 | 
						|
  // without Foo definition.
 | 
						|
  Test.Code = "class Foo;";
 | 
						|
  AST = Test.build();
 | 
						|
  DynamicIndex.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  // Even though the definition is actually deleted in the newer version of the
 | 
						|
  // file, we still chose to merge with information coming from static index.
 | 
						|
  // This seems wrong, but is generic behavior we want for e.g. include headers
 | 
						|
  // which are always missing from the dynamic index
 | 
						|
  LookupRequest LookupReq;
 | 
						|
  LookupReq.IDs = {Foo.ID};
 | 
						|
  unsigned SymbolCounter = 0;
 | 
						|
  Merge.lookup(LookupReq, [&](const Symbol &Sym) {
 | 
						|
    ++SymbolCounter;
 | 
						|
    EXPECT_TRUE(Sym.Definition);
 | 
						|
  });
 | 
						|
  EXPECT_EQ(SymbolCounter, 1u);
 | 
						|
 | 
						|
  // Drop the symbol completely.
 | 
						|
  Test.Code = "class Bar {};";
 | 
						|
  AST = Test.build();
 | 
						|
  DynamicIndex.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  // Now we don't expect to see the symbol at all.
 | 
						|
  SymbolCounter = 0;
 | 
						|
  Merge.lookup(LookupReq, [&](const Symbol &Sym) { ++SymbolCounter; });
 | 
						|
  EXPECT_EQ(SymbolCounter, 0u);
 | 
						|
}
 | 
						|
 | 
						|
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(MergeIndexTest, FuzzyFindRemovedSymbol) {
 | 
						|
  FileIndex DynamicIndex, StaticIndex;
 | 
						|
  MergedIndex Merge(&DynamicIndex, &StaticIndex);
 | 
						|
 | 
						|
  const char *HeaderCode = "class Foo;";
 | 
						|
  auto HeaderSymbols = TestTU::withHeaderCode(HeaderCode).headerSymbols();
 | 
						|
  auto Foo = findSymbol(HeaderSymbols, "Foo");
 | 
						|
 | 
						|
  // Build static index for test.cc with Foo symbol
 | 
						|
  TestTU Test;
 | 
						|
  Test.HeaderCode = HeaderCode;
 | 
						|
  Test.Code = "class Foo {};";
 | 
						|
  Test.Filename = "test.cc";
 | 
						|
  auto AST = Test.build();
 | 
						|
  StaticIndex.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  // Remove Foo symbol, i.e. build dynamic index for test.cc, which is empty.
 | 
						|
  Test.HeaderCode = "";
 | 
						|
  Test.Code = "";
 | 
						|
  AST = Test.build();
 | 
						|
  DynamicIndex.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  // Merged index should not return removed symbol.
 | 
						|
  FuzzyFindRequest Req;
 | 
						|
  Req.AnyScope = true;
 | 
						|
  Req.Query = "Foo";
 | 
						|
  unsigned SymbolCounter = 0;
 | 
						|
  bool IsIncomplete =
 | 
						|
      Merge.fuzzyFind(Req, [&](const Symbol &) { ++SymbolCounter; });
 | 
						|
  EXPECT_FALSE(IsIncomplete);
 | 
						|
  EXPECT_EQ(SymbolCounter, 0u);
 | 
						|
}
 | 
						|
 | 
						|
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 = std::string(Test1Code.code());
 | 
						|
  Test.Filename = "test.cc";
 | 
						|
  auto AST = Test.build();
 | 
						|
  Dyn.updateMain(testPath(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(testPath(Test.Filename), StaticAST);
 | 
						|
 | 
						|
  // Add refs for test2.cc
 | 
						|
  Annotations Test2Code(R"(class $Foo[[Foo]] {};)");
 | 
						|
  TestTU Test2;
 | 
						|
  Test2.HeaderCode = HeaderCode;
 | 
						|
  Test2.Code = std::string(Test2Code.code());
 | 
						|
  Test2.Filename = "test2.cc";
 | 
						|
  StaticAST = Test2.build();
 | 
						|
  StaticIndex.updateMain(testPath(Test2.Filename), StaticAST);
 | 
						|
 | 
						|
  RefsRequest Request;
 | 
						|
  Request.IDs = {Foo.ID};
 | 
						|
  RefSlab::Builder Results;
 | 
						|
  EXPECT_FALSE(
 | 
						|
      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;
 | 
						|
  EXPECT_TRUE(
 | 
						|
      Merge.refs(Request, [&](const Ref &O) { Results2.insert(Foo.ID, O); }));
 | 
						|
 | 
						|
  // Remove all refs for test.cc from dynamic index,
 | 
						|
  // merged index should not return results from static index for test.cc.
 | 
						|
  Test.Code = "";
 | 
						|
  AST = Test.build();
 | 
						|
  Dyn.updateMain(testPath(Test.Filename), AST);
 | 
						|
 | 
						|
  Request.Limit = llvm::None;
 | 
						|
  RefSlab::Builder Results3;
 | 
						|
  EXPECT_FALSE(
 | 
						|
      Merge.refs(Request, [&](const Ref &O) { Results3.insert(Foo.ID, O); }));
 | 
						|
  EXPECT_THAT(std::move(Results3).build(),
 | 
						|
              ElementsAre(Pair(_, UnorderedElementsAre(AllOf(
 | 
						|
                                      RefRange(Test2Code.range("Foo")),
 | 
						|
                                      FileURI("unittest:///test2.cc"))))));
 | 
						|
}
 | 
						|
 | 
						|
TEST(MergeIndexTest, IndexedFiles) {
 | 
						|
  SymbolSlab DynSymbols;
 | 
						|
  RefSlab DynRefs;
 | 
						|
  auto DynSize = DynSymbols.bytes() + DynRefs.bytes();
 | 
						|
  auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs));
 | 
						|
  llvm::StringSet<> 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);
 | 
						|
  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)));
 | 
						|
}
 | 
						|
 | 
						|
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({"<header>", 0});
 | 
						|
  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("<header>", 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, "<header>");
 | 
						|
}
 | 
						|
} // namespace
 | 
						|
} // namespace clangd
 | 
						|
} // namespace clang
 |