summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/unittests/clangd/ClangdTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/unittests/clangd/ClangdTests.cpp')
-rw-r--r--clang-tools-extra/unittests/clangd/ClangdTests.cpp224
1 files changed, 212 insertions, 12 deletions
diff --git a/clang-tools-extra/unittests/clangd/ClangdTests.cpp b/clang-tools-extra/unittests/clangd/ClangdTests.cpp
index a6523d8ebc2..5a182afe91b 100644
--- a/clang-tools-extra/unittests/clangd/ClangdTests.cpp
+++ b/clang-tools-extra/unittests/clangd/ClangdTests.cpp
@@ -135,6 +135,33 @@ IntrusiveRefCntPtr<vfs::FileSystem> getTempOnlyFS() {
namespace clangd {
namespace {
+struct StringWithPos {
+ std::string Text;
+ 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.");
+
+ 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");
+
+ clangd::Position MarkerPos =
+ clangd::offsetToPosition(WithoutMarker, MarkerOffset);
+ return {std::move(WithoutMarker), MarkerPos};
+}
+
// Don't wait for async ops in clangd test more than that to avoid blocking
// indefinitely in case of bugs.
static const std::chrono::seconds DefaultFutureTimeout =
@@ -305,7 +332,7 @@ protected:
ErrorCheckingDiagConsumer DiagConsumer;
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false,
+ clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
for (const auto &FileWithContents : ExtraFiles)
FS.Files[getVirtualTestFilePath(FileWithContents.first)] =
@@ -369,7 +396,8 @@ TEST_F(ClangdVFSTest, Reparse) {
ErrorCheckingDiagConsumer DiagConsumer;
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false, EmptyLogger::getInstance());
+ clangd::CodeCompleteOptions(),
+ EmptyLogger::getInstance());
const auto SourceContents = R"cpp(
#include "foo.h"
@@ -414,7 +442,8 @@ TEST_F(ClangdVFSTest, ReparseOnHeaderChange) {
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false, EmptyLogger::getInstance());
+ clangd::CodeCompleteOptions(),
+ EmptyLogger::getInstance());
const auto SourceContents = R"cpp(
#include "foo.h"
@@ -461,7 +490,7 @@ TEST_F(ClangdVFSTest, CheckVersions) {
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
// Run ClangdServer synchronously.
ClangdServer Server(CDB, DiagConsumer, FS,
- /*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false,
+ /*AsyncThreadsCount=*/0, clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
auto FooCpp = getVirtualTestFilePath("foo.cpp");
@@ -495,7 +524,7 @@ TEST_F(ClangdVFSTest, SearchLibDir) {
"-stdlib=libstdc++"});
// Run ClangdServer synchronously.
ClangdServer Server(CDB, DiagConsumer, FS,
- /*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false,
+ /*AsyncThreadsCount=*/0, clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
// Just a random gcc version string
@@ -544,7 +573,7 @@ TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
ErrorCheckingDiagConsumer DiagConsumer;
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS,
- /*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false,
+ /*AsyncThreadsCount=*/0, clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
// No need to sync reparses, because reparses are performed on the calling
// thread to true.
@@ -589,13 +618,22 @@ struct bar { T x; };
class ClangdCompletionTest : public ClangdVFSTest {
protected:
- bool ContainsItem(std::vector<CompletionItem> const &Items, StringRef Name) {
+ template <class Predicate>
+ bool ContainsItemPred(std::vector<CompletionItem> const &Items,
+ Predicate Pred) {
for (const auto &Item : Items) {
- if (Item.insertText == Name)
+ if (Pred(Item))
return true;
}
return false;
}
+
+ bool ContainsItem(std::vector<CompletionItem> const &Items, StringRef Name) {
+ return ContainsItemPred(Items, [Name](clangd::CompletionItem Item) {
+ return Item.insertText == Name;
+ });
+ return false;
+ }
};
TEST_F(ClangdCompletionTest, CheckContentsOverride) {
@@ -604,7 +642,8 @@ TEST_F(ClangdCompletionTest, CheckContentsOverride) {
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false, EmptyLogger::getInstance());
+ clangd::CodeCompleteOptions(),
+ EmptyLogger::getInstance());
auto FooCpp = getVirtualTestFilePath("foo.cpp");
const auto SourceContents = R"cpp(
@@ -655,6 +694,166 @@ int b = ;
}
}
+TEST_F(ClangdCompletionTest, CompletionOptions) {
+ MockFSProvider FS;
+ ErrorCheckingDiagConsumer DiagConsumer;
+ MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
+ CDB.ExtraClangFlags.push_back("-xc++");
+
+ auto FooCpp = getVirtualTestFilePath("foo.cpp");
+ FS.Files[FooCpp] = "";
+ FS.ExpectedFile = FooCpp;
+
+ const auto GlobalCompletionSourceTemplate = R"cpp(
+#define MACRO X
+
+int global_var;
+int global_func();
+
+struct GlobalClass {};
+
+struct ClassWithMembers {
+ /// Doc for method.
+ int method();
+};
+
+int test() {
+ struct LocalClass {};
+
+ /// Doc for local_var.
+ int local_var;
+
+ {complete}
+}
+)cpp";
+ const auto MemberCompletionSourceTemplate = R"cpp(
+#define MACRO X
+
+int global_var;
+
+int global_func();
+
+struct GlobalClass {};
+
+struct ClassWithMembers {
+ /// Doc for method.
+ int method();
+
+ int field;
+};
+
+int test() {
+ struct LocalClass {};
+
+ /// Doc for local_var.
+ int local_var;
+
+ ClassWithMembers().{complete}
+}
+)cpp";
+
+ StringWithPos GlobalCompletion =
+ parseTextMarker(GlobalCompletionSourceTemplate, "complete");
+ StringWithPos MemberCompletion =
+ parseTextMarker(MemberCompletionSourceTemplate, "complete");
+
+ auto TestWithOpts = [&](clangd::CodeCompleteOptions Opts) {
+ ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
+ Opts, 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,
+ 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, "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,
+ 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();
+ }));
+ }
+ };
+
+ for (bool IncludeMacros : {true, false})
+ for (bool IncludeGlobals : {true, false})
+ for (bool IncludeBriefComments : {true, false})
+ for (bool EnableSnippets : {true, false})
+ for (bool IncludeCodePatterns : {true, false}) {
+ TestWithOpts(clangd::CodeCompleteOptions(
+ /*EnableSnippets=*/EnableSnippets,
+ /*IncludeCodePatterns=*/IncludeCodePatterns,
+ /*IncludeMacros=*/IncludeMacros,
+ /*IncludeGlobals=*/IncludeGlobals,
+ /*IncludeBriefComments=*/IncludeBriefComments));
+ }
+}
+
class ClangdThreadingTest : public ClangdVFSTest {};
TEST_F(ClangdThreadingTest, StressTest) {
@@ -753,7 +952,7 @@ int d;
{
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false,
+ clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
// Prepare some random distributions for the test.
@@ -913,7 +1112,8 @@ TEST_F(ClangdVFSTest, CheckSourceHeaderSwitch) {
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
- /*SnippetCompletions=*/false, EmptyLogger::getInstance());
+ clangd::CodeCompleteOptions(),
+ EmptyLogger::getInstance());
auto SourceContents = R"cpp(
#include "foo.h"
@@ -1038,7 +1238,7 @@ int d;
std::move(StartSecondReparsePromise));
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
- ClangdServer Server(CDB, DiagConsumer, FS, 4, /*SnippetCompletions=*/false,
+ ClangdServer Server(CDB, DiagConsumer, FS, 4, clangd::CodeCompleteOptions(),
EmptyLogger::getInstance());
Server.addDocument(FooCpp, SourceContentsWithErrors);
StartSecondReparse.wait();
OpenPOWER on IntegriCloud