summaryrefslogtreecommitdiffstats
path: root/clangd/unittests/FileIndexTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/unittests/FileIndexTests.cpp')
-rw-r--r--clangd/unittests/FileIndexTests.cpp371
1 files changed, 371 insertions, 0 deletions
diff --git a/clangd/unittests/FileIndexTests.cpp b/clangd/unittests/FileIndexTests.cpp
new file mode 100644
index 00000000..142e2554
--- /dev/null
+++ b/clangd/unittests/FileIndexTests.cpp
@@ -0,0 +1,371 @@
+//===-- FileIndexTests.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 "AST.h"
+#include "Annotations.h"
+#include "ClangdUnit.h"
+#include "SyncAPI.h"
+#include "TestFS.h"
+#include "TestTU.h"
+#include "index/CanonicalIncludes.h"
+#include "index/FileIndex.h"
+#include "index/Index.h"
+#include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Frontend/Utils.h"
+#include "clang/Index/IndexSymbol.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Tooling/CompilationDatabase.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+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 llvm::StringRef(arg.Location.FileURI) == F; }
+MATCHER_P(DeclURI, U, "") {
+ return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U;
+}
+MATCHER_P(DefURI, U, "") {
+ return llvm::StringRef(arg.Definition.FileURI) == U;
+}
+MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; }
+
+namespace clang {
+namespace clangd {
+namespace {
+::testing::Matcher<const RefSlab &>
+RefsAre(std::vector<::testing::Matcher<Ref>> Matchers) {
+ return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers)));
+}
+
+Symbol symbol(llvm::StringRef ID) {
+ Symbol Sym;
+ Sym.ID = SymbolID(ID);
+ Sym.Name = ID;
+ return Sym;
+}
+
+std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
+ SymbolSlab::Builder Slab;
+ for (int i = Begin; i <= End; i++)
+ Slab.insert(symbol(std::to_string(i)));
+ return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
+}
+
+std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) {
+ RefSlab::Builder Slab;
+ Ref R;
+ R.Location.FileURI = Path;
+ R.Kind = RefKind::Reference;
+ Slab.insert(ID, R);
+ return llvm::make_unique<RefSlab>(std::move(Slab).build());
+}
+
+TEST(FileSymbolsTest, UpdateAndGet) {
+ FileSymbols FS;
+ EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty());
+
+ FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"));
+ EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""),
+ UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
+ EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")),
+ RefsAre({FileURI("f1.cc")}));
+}
+
+TEST(FileSymbolsTest, Overlap) {
+ FileSymbols FS;
+ FS.update("f1", numSlab(1, 3), nullptr);
+ FS.update("f2", numSlab(3, 5), nullptr);
+ for (auto Type : {IndexType::Light, IndexType::Heavy})
+ EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""),
+ UnorderedElementsAre(QName("1"), QName("2"), QName("3"),
+ QName("4"), QName("5")));
+}
+
+TEST(FileSymbolsTest, MergeOverlap) {
+ FileSymbols FS;
+ auto OneSymboSlab = [](Symbol Sym) {
+ SymbolSlab::Builder S;
+ S.insert(Sym);
+ return llvm::make_unique<SymbolSlab>(std::move(S).build());
+ };
+ auto X1 = symbol("x");
+ X1.CanonicalDeclaration.FileURI = "file:///x1";
+ auto X2 = symbol("x");
+ X2.Definition.FileURI = "file:///x2";
+
+ FS.update("f1", OneSymboSlab(X1), nullptr);
+ FS.update("f2", OneSymboSlab(X2), nullptr);
+ for (auto Type : {IndexType::Light, IndexType::Heavy})
+ EXPECT_THAT(
+ runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"),
+ UnorderedElementsAre(
+ AllOf(QName("x"), DeclURI("file:///x1"), DefURI("file:///x2"))));
+}
+
+TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
+ FileSymbols FS;
+
+ SymbolID ID("1");
+ FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"));
+
+ auto Symbols = FS.buildIndex(IndexType::Light);
+ EXPECT_THAT(runFuzzyFind(*Symbols, ""),
+ UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
+ EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
+
+ FS.update("f1", nullptr, nullptr);
+ auto Empty = FS.buildIndex(IndexType::Light);
+ EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty());
+ EXPECT_THAT(getRefs(*Empty, ID), ElementsAre());
+
+ EXPECT_THAT(runFuzzyFind(*Symbols, ""),
+ UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
+ EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
+}
+
+// Adds Basename.cpp, which includes Basename.h, which contains Code.
+void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
+ TestTU File;
+ File.Filename = (Basename + ".cpp").str();
+ File.HeaderFilename = (Basename + ".h").str();
+ File.HeaderCode = Code;
+ auto AST = File.build();
+ M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(),
+ AST.getCanonicalIncludes());
+}
+
+TEST(FileIndexTest, CustomizedURIScheme) {
+ FileIndex M;
+ update(M, "f", "class string {};");
+
+ EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(DeclURI("unittest:///f.h")));
+}
+
+TEST(FileIndexTest, IndexAST) {
+ FileIndex M;
+ update(M, "f1", "namespace ns { void f() {} class X {}; }");
+
+ FuzzyFindRequest Req;
+ Req.Query = "";
+ Req.Scopes = {"ns::"};
+ EXPECT_THAT(runFuzzyFind(M, Req),
+ UnorderedElementsAre(QName("ns::f"), QName("ns::X")));
+}
+
+TEST(FileIndexTest, NoLocal) {
+ FileIndex M;
+ update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }");
+
+ EXPECT_THAT(
+ runFuzzyFind(M, ""),
+ UnorderedElementsAre(QName("ns"), QName("ns::f"), QName("ns::X")));
+}
+
+TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
+ FileIndex M;
+ update(M, "f1", "namespace ns { void f() {} class X {}; }");
+ update(M, "f2", "namespace ns { void ff() {} class X {}; }");
+
+ FuzzyFindRequest Req;
+ Req.Scopes = {"ns::"};
+ EXPECT_THAT(
+ runFuzzyFind(M, Req),
+ UnorderedElementsAre(QName("ns::f"), QName("ns::X"), QName("ns::ff")));
+}
+
+TEST(FileIndexTest, ClassMembers) {
+ FileIndex M;
+ update(M, "f1", "class X { static int m1; int m2; static void f(); };");
+
+ EXPECT_THAT(runFuzzyFind(M, ""),
+ UnorderedElementsAre(QName("X"), QName("X::m1"), QName("X::m2"),
+ QName("X::f")));
+}
+
+TEST(FileIndexTest, IncludeCollected) {
+ FileIndex M;
+ update(
+ M, "f",
+ "// IWYU pragma: private, include <the/good/header.h>\nclass string {};");
+
+ auto Symbols = runFuzzyFind(M, "");
+ EXPECT_THAT(Symbols, ElementsAre(_));
+ EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader,
+ "<the/good/header.h>");
+}
+
+TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) {
+ TestTU TU;
+ TU.HeaderCode = "class Foo{};";
+ TU.HeaderFilename = "algorithm";
+
+ auto Symbols = runFuzzyFind(*TU.index(), "");
+ EXPECT_THAT(Symbols, ElementsAre(_));
+ EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader,
+ "<algorithm>");
+}
+
+TEST(FileIndexTest, TemplateParamsInLabel) {
+ auto Source = R"cpp(
+template <class Ty>
+class vector {
+};
+
+template <class Ty, class Arg>
+vector<Ty> make_vector(Arg A) {}
+)cpp";
+
+ FileIndex M;
+ update(M, "f", Source);
+
+ auto Symbols = runFuzzyFind(M, "");
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(QName("vector"), QName("make_vector")));
+ auto It = Symbols.begin();
+ Symbol Vector = *It++;
+ Symbol MakeVector = *It++;
+ if (MakeVector.Name == "vector")
+ std::swap(MakeVector, Vector);
+
+ EXPECT_EQ(Vector.Signature, "<class Ty>");
+ EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>");
+
+ EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)");
+ EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
+}
+
+TEST(FileIndexTest, RebuildWithPreamble) {
+ auto FooCpp = testPath("foo.cpp");
+ auto FooH = testPath("foo.h");
+ // Preparse ParseInputs.
+ ParseInputs PI;
+ PI.CompileCommand.Directory = testRoot();
+ PI.CompileCommand.Filename = FooCpp;
+ PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp};
+
+ llvm::StringMap<std::string> Files;
+ Files[FooCpp] = "";
+ Files[FooH] = R"cpp(
+ namespace ns_in_header {
+ int func_in_header();
+ }
+ )cpp";
+ PI.FS = buildTestFS(std::move(Files));
+
+ PI.Contents = R"cpp(
+ #include "foo.h"
+ namespace ns_in_source {
+ int func_in_source();
+ }
+ )cpp";
+
+ // Rebuild the file.
+ auto CI = buildCompilerInvocation(PI);
+
+ FileIndex Index;
+ bool IndexUpdated = false;
+ buildPreamble(
+ FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
+ /*StoreInMemory=*/true,
+ [&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
+ const CanonicalIncludes &CanonIncludes) {
+ EXPECT_FALSE(IndexUpdated) << "Expected only a single index update";
+ IndexUpdated = true;
+ Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes);
+ });
+ ASSERT_TRUE(IndexUpdated);
+
+ // Check the index contains symbols from the preamble, but not from the main
+ // file.
+ FuzzyFindRequest Req;
+ Req.Query = "";
+ Req.Scopes = {"", "ns_in_header::"};
+
+ EXPECT_THAT(runFuzzyFind(Index, Req),
+ UnorderedElementsAre(QName("ns_in_header"),
+ QName("ns_in_header::func_in_header")));
+}
+
+TEST(FileIndexTest, Refs) {
+ const char *HeaderCode = "class Foo {};";
+ Annotations MainCode(R"cpp(
+ void f() {
+ $foo[[Foo]] foo;
+ }
+ )cpp");
+
+ auto Foo =
+ findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo");
+
+ RefsRequest Request;
+ Request.IDs = {Foo.ID};
+
+ FileIndex Index;
+ // Add test.cc
+ TestTU Test;
+ Test.HeaderCode = HeaderCode;
+ Test.Code = MainCode.code();
+ Test.Filename = "test.cc";
+ auto AST = Test.build();
+ Index.updateMain(Test.Filename, AST);
+ // Add test2.cc
+ TestTU Test2;
+ Test2.HeaderCode = HeaderCode;
+ Test2.Code = MainCode.code();
+ Test2.Filename = "test2.cc";
+ AST = Test2.build();
+ Index.updateMain(Test2.Filename, AST);
+
+ EXPECT_THAT(getRefs(Index, Foo.ID),
+ RefsAre({AllOf(RefRange(MainCode.range("foo")),
+ FileURI("unittest:///test.cc")),
+ AllOf(RefRange(MainCode.range("foo")),
+ FileURI("unittest:///test2.cc"))}));
+}
+
+TEST(FileIndexTest, CollectMacros) {
+ FileIndex M;
+ update(M, "f", "#define CLANGD 1");
+ EXPECT_THAT(runFuzzyFind(M, ""), Contains(QName("CLANGD")));
+}
+
+TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
+ TestTU TU;
+ TU.HeaderCode = "class Foo{};";
+ Annotations Main(R"cpp(
+ #include "foo.h"
+ void f() {
+ [[Foo]] foo;
+ }
+ )cpp");
+ TU.Code = Main.code();
+ auto AST = TU.build();
+ FileIndex Index;
+ Index.updateMain(testPath(TU.Filename), AST);
+
+ // Expect to see references in main file, references in headers are excluded
+ // because we only index main AST.
+ EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID),
+ RefsAre({RefRange(Main.range())}));
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang