Author: sammccall Date: Tue Dec 5 12:11:29 2017 New Revision: 319820 URL: http://llvm.org/viewvc/llvm-project?rev=319820&view=rev Log: [clangd] Clean up code complete unit tests. NFC
Modified: clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp Modified: clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp?rev=319820&r1=319819&r2=319820&view=diff ============================================================================== --- clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp (original) +++ clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp Tue Dec 5 12:11:29 2017 @@ -10,12 +10,24 @@ #include "Compiler.h" #include "Protocol.h" #include "TestFS.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace clang { namespace clangd { +// Let GMock print completion items. +void PrintTo(const CompletionItem &I, std::ostream *O) { + llvm::raw_os_ostream OS(*O); + OS << toJSON(I); +} + namespace { using namespace llvm; +using ::testing::AllOf; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::Matcher; +using ::testing::Not; class IgnoreDiagnostics : public DiagnosticsConsumer { void onDiagnosticsReady( @@ -27,161 +39,74 @@ struct StringWithPos { clangd::Position MarkerPos; }; -/// Returns location of "{mark}" substring in \p Text and removes it from \p -/// Text. Note that \p Text must contain exactly one occurence of "{mark}". -/// -/// Marker name can be configured using \p MarkerName parameter. -StringWithPos parseTextMarker(StringRef Text, StringRef MarkerName = "mark") { - SmallString<16> Marker; - Twine("{" + MarkerName + "}").toVector(/*ref*/ Marker); - - std::size_t MarkerOffset = Text.find(Marker); - assert(MarkerOffset != StringRef::npos && "{mark} wasn't found in Text."); +/// Accepts a source file with a cursor marker ^. +/// Returns the source file with the marker removed, and the marker position. +StringWithPos parseTextMarker(StringRef Text) { + std::size_t MarkerOffset = Text.find('^'); + assert(MarkerOffset != StringRef::npos && "^ wasn't found in Text."); std::string WithoutMarker; WithoutMarker += Text.take_front(MarkerOffset); - WithoutMarker += Text.drop_front(MarkerOffset + Marker.size()); - assert(StringRef(WithoutMarker).find(Marker) == StringRef::npos && - "There were multiple occurences of {mark} inside Text"); + WithoutMarker += Text.drop_front(MarkerOffset + 1); + assert(StringRef(WithoutMarker).find('^') == StringRef::npos && + "There were multiple occurences of ^ inside Text"); - clangd::Position MarkerPos = - clangd::offsetToPosition(WithoutMarker, MarkerOffset); + auto MarkerPos = offsetToPosition(WithoutMarker, MarkerOffset); return {std::move(WithoutMarker), MarkerPos}; } -class ClangdCompletionTest : public ::testing::Test { -protected: - template <class Predicate> - bool ContainsItemPred(CompletionList const &Items, Predicate Pred) { - for (const auto &Item : Items.items) { - if (Pred(Item)) - return true; - } - return false; - } - - bool ContainsItem(CompletionList const &Items, StringRef Name) { - return ContainsItemPred(Items, [Name](clangd::CompletionItem Item) { - return Item.insertText == Name; - }); - return false; - } -}; - -TEST_F(ClangdCompletionTest, CheckContentsOverride) { - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - - ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, - EmptyLogger::getInstance()); - - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - const auto SourceContents = R"cpp( -int aba; -int b = ; -)cpp"; - - const auto OverridenSourceContents = R"cpp( -int cbc; -int b = ; -)cpp"; - - // Use default options. - CodeCompleteOptions CCOpts; - // Complete after '=' sign. We need to be careful to keep the SourceContents' - // size the same. - // We complete on the 3rd line (2nd in zero-based numbering), because raw - // string literal of the SourceContents starts with a newline(it's easy to - // miss). - Position CompletePos = {2, 8}; - FS.Files[FooCpp] = SourceContents; - FS.ExpectedFile = FooCpp; - - // No need to sync reparses here as there are no asserts on diagnostics (or - // other async operations). - Server.addDocument(FooCpp, SourceContents); - - { - auto CodeCompletionResults1 = - Server.codeComplete(FooCpp, CompletePos, CCOpts, None).get().Value; - EXPECT_TRUE(ContainsItem(CodeCompletionResults1, "aba")); - EXPECT_FALSE(ContainsItem(CodeCompletionResults1, "cbc")); - } - - { - auto CodeCompletionResultsOverriden = - Server - .codeComplete(FooCpp, CompletePos, CCOpts, - StringRef(OverridenSourceContents)) - .get() - .Value; - EXPECT_TRUE(ContainsItem(CodeCompletionResultsOverriden, "cbc")); - EXPECT_FALSE(ContainsItem(CodeCompletionResultsOverriden, "aba")); - } - - { - auto CodeCompletionResults2 = - Server.codeComplete(FooCpp, CompletePos, CCOpts, None).get().Value; - EXPECT_TRUE(ContainsItem(CodeCompletionResults2, "aba")); - EXPECT_FALSE(ContainsItem(CodeCompletionResults2, "cbc")); - } +// GMock helpers for matching completion items. +MATCHER_P(Named, Name, "") { return arg.insertText == Name; } +// Shorthand for Contains(Named(Name)). +Matcher<const std::vector<CompletionItem> &> Has(std::string Name) { + return Contains(Named(std::move(Name))); +} +MATCHER(IsDocumented, "") { return !arg.documentation.empty(); } +MATCHER(IsSnippet, "") { + return arg.kind == clangd::CompletionItemKind::Snippet; } +// This is hard to write as a function, because matchers may be polymorphic. +#define EXPECT_IFF(condition, value, matcher) \ + do { \ + if (condition) \ + EXPECT_THAT(value, matcher); \ + else \ + EXPECT_THAT(value, ::testing::Not(matcher)); \ + } while (0) -TEST_F(ClangdCompletionTest, Limit) { +CompletionList completions(StringRef Text, + clangd::CodeCompleteOptions Opts = {}) { MockFSProvider FS; MockCompilationDatabase CDB; - CDB.ExtraClangFlags.push_back("-xc++"); IgnoreDiagnostics DiagConsumer; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, EmptyLogger::getInstance()); + auto File = getVirtualTestFilePath("foo.cpp"); + auto Test = parseTextMarker(Text); + Server.addDocument(File, Test.Text); + return Server.codeComplete(File, Test.MarkerPos, Opts).get().Value; +} - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - FS.Files[FooCpp] = ""; - FS.ExpectedFile = FooCpp; - StringWithPos Completion = parseTextMarker(R"cpp( +TEST(CompletionTest, Limit) { + clangd::CodeCompleteOptions Opts; + Opts.Limit = 2; + auto Results = completions(R"cpp( struct ClassWithMembers { int AAA(); int BBB(); int CCC(); } -int main() { ClassWithMembers().{complete} } +int main() { ClassWithMembers().^ } )cpp", - "complete"); - Server.addDocument(FooCpp, Completion.Text); - - clangd::CodeCompleteOptions Opts; - Opts.Limit = 2; - - /// For after-dot completion we must always get consistent results. - auto Results = Server - .codeComplete(FooCpp, Completion.MarkerPos, Opts, - StringRef(Completion.Text)) - .get() - .Value; + Opts); EXPECT_TRUE(Results.isIncomplete); - EXPECT_EQ(Opts.Limit, Results.items.size()); - EXPECT_TRUE(ContainsItem(Results, "AAA")); - EXPECT_TRUE(ContainsItem(Results, "BBB")); - EXPECT_FALSE(ContainsItem(Results, "CCC")); + EXPECT_THAT(Results.items, ElementsAre(Named("AAA"), Named("BBB"))); } -TEST_F(ClangdCompletionTest, Filter) { - MockFSProvider FS; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags.push_back("-xc++"); - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, - EmptyLogger::getInstance()); - - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - FS.Files[FooCpp] = ""; - FS.ExpectedFile = FooCpp; - const char *Body = R"cpp( +TEST(CompletionTest, Filter) { + std::string Body = R"cpp( int Abracadabra; int Alakazam; struct S { @@ -190,54 +115,29 @@ TEST_F(ClangdCompletionTest, Filter) { int Qux; }; )cpp"; - auto Complete = [&](StringRef Query) { - StringWithPos Completion = parseTextMarker( - formatv("{0} int main() { {1}{{complete}} }", Body, Query).str(), - "complete"); - Server.addDocument(FooCpp, Completion.Text); - return Server - .codeComplete(FooCpp, Completion.MarkerPos, - clangd::CodeCompleteOptions(), StringRef(Completion.Text)) - .get() - .Value; - }; - - auto Foba = Complete("S().Foba"); - EXPECT_TRUE(ContainsItem(Foba, "FooBar")); - EXPECT_TRUE(ContainsItem(Foba, "FooBaz")); - EXPECT_FALSE(ContainsItem(Foba, "Qux")); - - auto FR = Complete("S().FR"); - EXPECT_TRUE(ContainsItem(FR, "FooBar")); - EXPECT_FALSE(ContainsItem(FR, "FooBaz")); - EXPECT_FALSE(ContainsItem(FR, "Qux")); - - auto Op = Complete("S().opr"); - EXPECT_TRUE(ContainsItem(Op, "operator=")); - - auto Aaa = Complete("aaa"); - EXPECT_TRUE(ContainsItem(Aaa, "Abracadabra")); - EXPECT_TRUE(ContainsItem(Aaa, "Alakazam")); - - auto UA = Complete("_a"); - EXPECT_TRUE(ContainsItem(UA, "static_cast")); - EXPECT_FALSE(ContainsItem(UA, "Abracadabra")); -} -TEST_F(ClangdCompletionTest, CompletionOptions) { - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags.push_back("-xc++"); + EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").items, + AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux")))); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - FS.Files[FooCpp] = ""; - FS.ExpectedFile = FooCpp; + EXPECT_THAT(completions(Body + "int main() { S().FR^ }").items, + AllOf(Has("FooBar"), Not(Has("FooBaz")), Not(Has("Qux")))); + + EXPECT_THAT(completions(Body + "int main() { S().opr^ }").items, + Has("operator=")); + + EXPECT_THAT(completions(Body + "int main() { aaa^ }").items, + AllOf(Has("Abracadabra"), Has("Alakazam"))); + + EXPECT_THAT(completions(Body + "int main() { _a^ }").items, + AllOf(Has("static_cast"), Not(Has("Abracadabra")))); +} - const auto GlobalCompletionSourceTemplate = R"cpp( +void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { + auto Results = completions(R"cpp( #define MACRO X int global_var; + int global_func(); struct GlobalClass {}; @@ -245,6 +145,10 @@ struct GlobalClass {}; struct ClassWithMembers { /// Doc for method. int method(); + + int field; +private: + int private_field; }; int test() { @@ -253,14 +157,33 @@ int test() { /// Doc for local_var. int local_var; - {complete} + ClassWithMembers().^ } -)cpp"; - const auto MemberCompletionSourceTemplate = R"cpp( +)cpp", + Opts) + .items; + + // Class members. The only items that must be present in after-dot + // completion. + EXPECT_THAT(Results, AllOf(Has(Opts.EnableSnippets ? "method()" : "method"), + Has("field"))); + EXPECT_IFF(Opts.IncludeIneligibleResults, Results, Has("private_field")); + // Global items. + EXPECT_THAT(Results, Not(AnyOf(Has("global_var"), Has("global_func"), + Has("global_func()"), Has("GlobalClass"), + Has("MACRO"), Has("LocalClass")))); + // There should be no code patterns (aka snippets) in after-dot + // completion. At least there aren't any we're aware of. + EXPECT_THAT(Results, Not(Contains(IsSnippet()))); + // Check documentation. + EXPECT_IFF(Opts.IncludeBriefComments, Results, Contains(IsDocumented())); +} + +void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { + auto Results = completions(R"cpp( #define MACRO X int global_var; - int global_func(); struct GlobalClass {}; @@ -268,10 +191,6 @@ struct GlobalClass {}; struct ClassWithMembers { /// Doc for method. int method(); - - int field; -private: - int private_field; }; int test() { @@ -280,116 +199,45 @@ int test() { /// Doc for local_var. int local_var; - ClassWithMembers().{complete} + ^ +} +)cpp", + Opts) + .items; + + // Class members. Should never be present in global completions. + EXPECT_THAT(Results, + Not(AnyOf(Has("method"), Has("method()"), Has("field")))); + // Global items. + EXPECT_IFF(Opts.IncludeGlobals, Results, + AllOf(Has("global_var"), + Has(Opts.EnableSnippets ? "global_func()" : "global_func"), + Has("GlobalClass"))); + // A macro. + EXPECT_IFF(Opts.IncludeMacros, Results, Has("MACRO")); + // Local items. Must be present always. + EXPECT_THAT(Results, AllOf(Has("local_var"), Has("LocalClass"), + Contains(IsSnippet()))); + // Check documentation. + EXPECT_IFF(Opts.IncludeBriefComments, Results, Contains(IsDocumented())); } -)cpp"; - - StringWithPos GlobalCompletion = - parseTextMarker(GlobalCompletionSourceTemplate, "complete"); - StringWithPos MemberCompletion = - parseTextMarker(MemberCompletionSourceTemplate, "complete"); - - auto TestWithOpts = [&](clangd::CodeCompleteOptions Opts) { - ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, - EmptyLogger::getInstance()); - // No need to sync reparses here as there are no asserts on diagnostics (or - // other async operations). - Server.addDocument(FooCpp, GlobalCompletion.Text); - - StringRef MethodItemText = Opts.EnableSnippets ? "method()" : "method"; - StringRef GlobalFuncItemText = - Opts.EnableSnippets ? "global_func()" : "global_func"; - - /// For after-dot completion we must always get consistent results. - { - auto Results = Server - .codeComplete(FooCpp, MemberCompletion.MarkerPos, Opts, - StringRef(MemberCompletion.Text)) - .get() - .Value; - - // Class members. The only items that must be present in after-dor - // completion. - EXPECT_TRUE(ContainsItem(Results, MethodItemText)); - EXPECT_TRUE(ContainsItem(Results, MethodItemText)); - EXPECT_TRUE(ContainsItem(Results, "field")); - EXPECT_EQ(Opts.IncludeIneligibleResults, - ContainsItem(Results, "private_field")); - // Global items. - EXPECT_FALSE(ContainsItem(Results, "global_var")); - EXPECT_FALSE(ContainsItem(Results, GlobalFuncItemText)); - EXPECT_FALSE(ContainsItem(Results, "GlobalClass")); - // A macro. - EXPECT_FALSE(ContainsItem(Results, "MACRO")); - // Local items. - EXPECT_FALSE(ContainsItem(Results, "LocalClass")); - // There should be no code patterns (aka snippets) in after-dot - // completion. At least there aren't any we're aware of. - EXPECT_FALSE( - ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { - return Item.kind == clangd::CompletionItemKind::Snippet; - })); - // Check documentation. - EXPECT_EQ( - Opts.IncludeBriefComments, - ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { - return !Item.documentation.empty(); - })); - } - // Global completion differs based on the Opts that were passed. - { - auto Results = Server - .codeComplete(FooCpp, GlobalCompletion.MarkerPos, Opts, - StringRef(GlobalCompletion.Text)) - .get() - .Value; - - // Class members. Should never be present in global completions. - EXPECT_FALSE(ContainsItem(Results, MethodItemText)); - EXPECT_FALSE(ContainsItem(Results, "field")); - // Global items. - EXPECT_EQ(ContainsItem(Results, "global_var"), Opts.IncludeGlobals); - EXPECT_EQ(ContainsItem(Results, GlobalFuncItemText), Opts.IncludeGlobals); - EXPECT_EQ(ContainsItem(Results, "GlobalClass"), Opts.IncludeGlobals); - // A macro. - EXPECT_EQ(ContainsItem(Results, "MACRO"), Opts.IncludeMacros); - // Local items. Must be present always. - EXPECT_TRUE(ContainsItem(Results, "local_var")); - EXPECT_TRUE(ContainsItem(Results, "LocalClass")); - // FIXME(ibiryukov): snippets have wrong Item.kind now. Reenable this - // check after https://reviews.llvm.org/D38720 makes it in. - // - // Code patterns (aka snippets). - // EXPECT_EQ( - // Opts.IncludeCodePatterns && Opts.EnableSnippets, - // ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { - // return Item.kind == clangd::CompletionItemKind::Snippet; - // })); - - // Check documentation. - EXPECT_EQ( - Opts.IncludeBriefComments, - ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { - return !Item.documentation.empty(); - })); - } - }; - clangd::CodeCompleteOptions CCOpts; +TEST(CompletionTest, CompletionOptions) { + clangd::CodeCompleteOptions Opts; for (bool IncludeMacros : {true, false}) { - CCOpts.IncludeMacros = IncludeMacros; + Opts.IncludeMacros = IncludeMacros; for (bool IncludeGlobals : {true, false}) { - CCOpts.IncludeGlobals = IncludeGlobals; + Opts.IncludeGlobals = IncludeGlobals; for (bool IncludeBriefComments : {true, false}) { - CCOpts.IncludeBriefComments = IncludeBriefComments; + Opts.IncludeBriefComments = IncludeBriefComments; for (bool EnableSnippets : {true, false}) { - CCOpts.EnableSnippets = EnableSnippets; + Opts.EnableSnippets = EnableSnippets; for (bool IncludeCodePatterns : {true, false}) { - CCOpts.IncludeCodePatterns = IncludeCodePatterns; + Opts.IncludeCodePatterns = IncludeCodePatterns; for (bool IncludeIneligibleResults : {true, false}) { - CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; - TestWithOpts(CCOpts); + Opts.IncludeIneligibleResults = IncludeIneligibleResults; + TestAfterDotCompletion(Opts); + TestGlobalScopeCompletion(Opts); } } } @@ -398,6 +246,27 @@ int test() { } } +// Check code completion works when the file contents are overridden. +TEST(CompletionTest, CheckContentsOverride) { + MockFSProvider FS; + IgnoreDiagnostics DiagConsumer; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true, + EmptyLogger::getInstance()); + auto File = getVirtualTestFilePath("foo.cpp"); + Server.addDocument(File, "ignored text!"); + + auto Example = parseTextMarker("int cbc; int b = ^;"); + auto Results = + Server + .codeComplete(File, Example.MarkerPos, clangd::CodeCompleteOptions(), + StringRef(Example.Text)) + .get() + .Value; + EXPECT_THAT(Results.items, Contains(Named("cbc"))); +} + } // namespace } // namespace clangd } // namespace clang _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits