From a15c2d683e95bc3fca5f8c80b116b2802788c8db Mon Sep 17 00:00:00 2001 From: Sam McCall Date: Thu, 18 Jan 2018 09:27:56 +0000 Subject: [PATCH] [clangd] CodeCompleteTests cleanup: naming, ordering, helpers. NFC llvm-svn: 322824 --- .../unittests/clangd/CodeCompleteTests.cpp | 365 +++++++++--------- 1 file changed, 179 insertions(+), 186 deletions(-) diff --git a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp b/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp index 289f69dda475..e81be67da445 100644 --- a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp +++ b/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp @@ -97,8 +97,25 @@ Matcher &> Has(std::string Name, } MATCHER(IsDocumented, "") { return !arg.documentation.empty(); } +std::unique_ptr memIndex(std::vector Symbols) { + SymbolSlab::Builder Slab; + for (const auto &Sym : Symbols) + Slab.insert(Sym); + return MemIndex::build(std::move(Slab).build()); +} + +// Builds a server and runs code completion. +// If IndexSymbols is non-empty, an index will be built and passed to opts. CompletionList completions(StringRef Text, + std::vector IndexSymbols = {}, clangd::CodeCompleteOptions Opts = {}) { + std::unique_ptr OverrideIndex; + if (!IndexSymbols.empty()) { + assert(!Opts.Index && "both Index and IndexSymbols given!"); + OverrideIndex = memIndex(std::move(IndexSymbols)); + Opts.Index = OverrideIndex.get(); + } + MockFSProvider FS; MockCompilationDatabase CDB; IgnoreDiagnostics DiagConsumer; @@ -116,6 +133,27 @@ CompletionList completions(StringRef Text, return CompletionList; } +// Helpers to produce fake index symbols for memIndex() or completions(). +Symbol sym(StringRef QName, index::SymbolKind Kind) { + Symbol Sym; + Sym.ID = SymbolID(QName); + 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); + } + Sym.CompletionPlainInsertText = Sym.Name; + Sym.CompletionLabel = Sym.Name; + Sym.SymInfo.Kind = Kind; + return Sym; +} +Symbol func(StringRef Name) { return sym(Name, index::SymbolKind::Function); } +Symbol cls(StringRef Name) { return sym(Name, index::SymbolKind::Class); } +Symbol var(StringRef Name) { return sym(Name, index::SymbolKind::Variable); } + TEST(CompletionTest, Limit) { clangd::CodeCompleteOptions Opts; Opts.Limit = 2; @@ -127,7 +165,7 @@ struct ClassWithMembers { } int main() { ClassWithMembers().^ } )cpp", - Opts); + /*IndexSymbols=*/{}, Opts); EXPECT_TRUE(Results.isIncomplete); EXPECT_THAT(Results.items, ElementsAre(Named("AAA"), Named("BBB"))); @@ -188,7 +226,7 @@ void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { ClassWithMembers().^ } )cpp", - Opts); + /*IndexSymbols=*/{}, Opts); // Class members. The only items that must be present in after-dot // completion. @@ -233,7 +271,7 @@ void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { ^ } )cpp", - Opts); + /*IndexSymbols=*/{}, Opts); // Class members. Should never be present in global completions. EXPECT_THAT(Results.items, @@ -355,7 +393,7 @@ TEST(CompletionTest, Snippets) { f.^ } )cpp", - Opts); + /*IndexSymbols=*/{}, Opts); EXPECT_THAT(Results.items, HasSubsequence(Snippet("a"), Snippet("f(${1:int i}, ${2:const float f})"))); @@ -375,9 +413,6 @@ TEST(CompletionTest, Kinds) { EXPECT_THAT(Results.items, Has("Struct", CompletionItemKind::Class)); EXPECT_THAT(Results.items, Has("MACRO", CompletionItemKind::Text)); - clangd::CodeCompleteOptions Opts; - Opts.EnableSnippets = true; // Needed for code patterns. - Results = completions("nam^"); EXPECT_THAT(Results.items, Has("namespace", CompletionItemKind::Snippet)); } @@ -406,6 +441,143 @@ TEST(CompletionTest, FuzzyRanking) { EXPECT_THAT(Items, ElementsAre(Named("BigBang"), Named("Babble"))); } +TEST(CompletionTest, NoIndex) { + auto Results = completions(R"cpp( + namespace ns { class Local {}; } + void f() { ns::^ } + )cpp"); + EXPECT_THAT(Results.items, Has("Local")); +} + +TEST(CompletionTest, StaticAndDynamicIndex) { + clangd::CodeCompleteOptions Opts; + auto StaticIdx = memIndex({cls("ns::XYZ")}); + auto DynamicIdx = memIndex({func("ns::foo")}); + auto Merge = mergeIndex(DynamicIdx.get(), StaticIdx.get()); + Opts.Index = Merge.get(); + + auto Results = completions( + R"cpp( + void f() { ::ns::^ } + )cpp", + /*IndexSymbols=*/{}, Opts); + EXPECT_THAT(Results.items, Contains(Labeled("[I]XYZ"))); + EXPECT_THAT(Results.items, Contains(Labeled("[I]foo"))); +} + +TEST(CompletionTest, IndexScope) { + auto Results = completions( + R"cpp( + namespace ns { int local; } + void f() { ns::^ } + )cpp", + {cls("ns::XYZ"), cls("nx::XYZ"), func("ns::foo")}); + EXPECT_THAT(Results.items, + UnorderedElementsAre(Named("XYZ"), Named("foo"), Named("local"))); +} + +TEST(CompletionTest, IndexBasedWithFilter) { + auto Results = completions( + R"cpp( + void f() { ns::x^ } + )cpp", + {cls("ns::XYZ"), func("ns::foo")}); + EXPECT_THAT(Results.items, + UnorderedElementsAre(AllOf(Named("XYZ"), Filter("XYZ")))); +} + +TEST(CompletionTest, IndexGlobalQualified) { + auto Results = completions( + R"cpp( + void f() { ::^ } + )cpp", + {cls("XYZ")}); + EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class), + Has("f", CompletionItemKind::Function))); +} + +TEST(CompletionTest, IndexFullyQualifiedScope) { + auto Results = completions( + R"cpp( + void f() { ::ns::^ } + )cpp", + {cls("ns::XYZ")}); + EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); +} + +TEST(CompletionTest, IndexSuppressesPreambleCompletions) { + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true); + + FS.Files[getVirtualTestFilePath("bar.h")] = + R"cpp(namespace ns { int preamble; })cpp"; + auto File = getVirtualTestFilePath("foo.cpp"); + Annotations Test(R"cpp( + #include "bar.h" + namespace ns { int local; } + void f() { ns::^ } + )cpp"); + Server.addDocument(Context::empty(), File, Test.code()).wait(); + clangd::CodeCompleteOptions Opts = {}; + + auto WithoutIndex = + Server.codeComplete(Context::empty(), File, Test.point(), Opts) + .get() + .second.Value; + EXPECT_THAT(WithoutIndex.items, + UnorderedElementsAre(Named("local"), Named("preamble"))); + + auto I = memIndex({var("ns::index")}); + Opts.Index = I.get(); + auto WithIndex = + Server.codeComplete(Context::empty(), File, Test.point(), Opts) + .get() + .second.Value; + EXPECT_THAT(WithIndex.items, + UnorderedElementsAre(Named("local"), Named("index"))); +} + +TEST(CompletionTest, DynamicIndexMultiFile) { + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + /*BuildDynamicSymbolIndex=*/true); + + Server + .addDocument(Context::empty(), getVirtualTestFilePath("foo.cpp"), R"cpp( + namespace ns { class XYZ {}; void foo(int x) {} } + )cpp") + .wait(); + + auto File = getVirtualTestFilePath("bar.cpp"); + Annotations Test(R"cpp( + namespace ns { + class XXX {}; + /// Doooc + void fooooo() {} + } + void f() { ns::^ } + )cpp"); + Server.addDocument(Context::empty(), File, Test.code()).wait(); + + auto Results = Server.codeComplete(Context::empty(), File, Test.point(), {}) + .get() + .second.Value; + // "XYZ" and "foo" are not included in the file being completed but are still + // visible through the index. + EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); + EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function)); + EXPECT_THAT(Results.items, Has("XXX", CompletionItemKind::Class)); + EXPECT_THAT(Results.items, Contains(AllOf(Named("fooooo"), Filter("fooooo"), + Kind(CompletionItemKind::Function), + Doc("Doooc"), Detail("void")))); +} + SignatureHelp signatures(StringRef Text) { MockFSProvider FS; MockCompilationDatabase CDB; @@ -481,185 +653,6 @@ TEST(SignatureHelpTest, ActiveArg) { EXPECT_EQ(1, Results.activeParameter); } -std::unique_ptr simpleIndexFromSymbols( - std::vector> Symbols) { - SymbolSlab::Builder Slab; - for (const auto &Pair : Symbols) { - Symbol Sym; - Sym.ID = SymbolID(Pair.first); - llvm::StringRef QName = Pair.first; - 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); - } - Sym.CompletionPlainInsertText = Sym.Name; - Sym.SymInfo.Kind = Pair.second; - Slab.insert(Sym); - } - return MemIndex::build(std::move(Slab).build()); -} - -TEST(CompletionTest, NoIndex) { - clangd::CodeCompleteOptions Opts; - Opts.Index = nullptr; - - auto Results = completions(R"cpp( - namespace ns { class Local {}; } - void f() { ns::^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Has("Local")); -} - -TEST(CompletionTest, StaticAndDynamicIndex) { - clangd::CodeCompleteOptions Opts; - auto StaticIdx = - simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class}}); - auto DynamicIdx = - simpleIndexFromSymbols({{"ns::foo", index::SymbolKind::Function}}); - auto Merge = mergeIndex(DynamicIdx.get(), StaticIdx.get()); - Opts.Index = Merge.get(); - - auto Results = completions(R"cpp( - void f() { ::ns::^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Contains(Labeled("[I]XYZ"))); - EXPECT_THAT(Results.items, Contains(Labeled("[I]foo"))); -} - -TEST(CompletionTest, SimpleIndexBased) { - clangd::CodeCompleteOptions Opts; - auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class}, - {"nx::XYZ", index::SymbolKind::Class}, - {"ns::foo", index::SymbolKind::Function}}); - Opts.Index = I.get(); - - auto Results = completions(R"cpp( - namespace ns { int local; } - void f() { ns::^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); - EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function)); - EXPECT_THAT(Results.items, Has("local")); -} - -TEST(CompletionTest, IndexBasedWithFilter) { - clangd::CodeCompleteOptions Opts; - auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class}, - {"ns::foo", index::SymbolKind::Function}}); - Opts.Index = I.get(); - - auto Results = completions(R"cpp( - void f() { ns::x^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Contains(AllOf(Named("XYZ"), Filter("XYZ")))); - EXPECT_THAT(Results.items, Not(Has("foo"))); -} - -TEST(CompletionTest, GlobalQualified) { - clangd::CodeCompleteOptions Opts; - auto I = simpleIndexFromSymbols({{"XYZ", index::SymbolKind::Class}}); - Opts.Index = I.get(); - - auto Results = completions(R"cpp( - void f() { ::^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); -} - -TEST(CompletionTest, FullyQualifiedScope) { - clangd::CodeCompleteOptions Opts; - auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class}}); - Opts.Index = I.get(); - - auto Results = completions(R"cpp( - void f() { ::ns::^ } - )cpp", - Opts); - EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); -} - -TEST(CompletionTest, IndexSuppressesPreambleCompletions) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true); - - FS.Files[getVirtualTestFilePath("bar.h")] = - R"cpp(namespace ns { int preamble; })cpp"; - auto File = getVirtualTestFilePath("foo.cpp"); - Annotations Test(R"cpp( - #include "bar.h" - namespace ns { int local; } - void f() { ns::^ } - )cpp"); - Server.addDocument(Context::empty(), File, Test.code()).wait(); - clangd::CodeCompleteOptions Opts = {}; - - auto WithoutIndex = - Server.codeComplete(Context::empty(), File, Test.point(), Opts) - .get() - .second.Value; - EXPECT_THAT(WithoutIndex.items, - UnorderedElementsAre(Named("local"), Named("preamble"))); - - auto I = simpleIndexFromSymbols({{"ns::index", index::SymbolKind::Variable}}); - Opts.Index = I.get(); - auto WithIndex = - Server.codeComplete(Context::empty(), File, Test.point(), Opts) - .get() - .second.Value; - EXPECT_THAT(WithIndex.items, - UnorderedElementsAre(Named("local"), Named("index"))); -} - -TEST(CompletionTest, ASTIndexMultiFile) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, - /*BuildDynamicSymbolIndex=*/true); - - Server - .addDocument(Context::empty(), getVirtualTestFilePath("foo.cpp"), R"cpp( - namespace ns { class XYZ {}; void foo(int x) {} } - )cpp") - .wait(); - - auto File = getVirtualTestFilePath("bar.cpp"); - Annotations Test(R"cpp( - namespace ns { - class XXX {}; - /// Doooc - void fooooo() {} - } - void f() { ns::^ } - )cpp"); - Server.addDocument(Context::empty(), File, Test.code()).wait(); - - auto Results = Server.codeComplete(Context::empty(), File, Test.point(), {}) - .get() - .second.Value; - // "XYZ" and "foo" are not included in the file being completed but are still - // visible through the index. - EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); - EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function)); - EXPECT_THAT(Results.items, Has("XXX", CompletionItemKind::Class)); - EXPECT_THAT(Results.items, Contains(AllOf(Named("fooooo"), Filter("fooooo"), - Kind(CompletionItemKind::Function), - Doc("Doooc"), Detail("void")))); -} - } // namespace } // namespace clangd } // namespace clang