16 #include "llvm/ADT/StringRef.h"
17 #include "llvm/Support/Error.h"
18 #include "llvm/Support/JSON.h"
19 #include "llvm/Testing/Support/Error.h"
20 #include "llvm/Testing/Support/SupportHelpers.h"
21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
27 using llvm::Succeeded;
28 using testing::ElementsAre;
31 if (
const auto *O = arg.getAsObject()) {
32 if (
const auto Msg = O->getString(
"message"))
38 class LSPTest :
public ::testing::Test {
40 LSPTest() : LogSession(L) {
44 Base.BuildDynamicSymbolIndex =
true;
49 EXPECT_FALSE(Server.hasValue()) <<
"Already initialized";
50 Server.emplace(Client.transport(),
FS,
Opts);
51 ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
52 Client.call(
"initialize", llvm::json::Object{});
58 Client.call(
"shutdown",
nullptr);
59 Client.notify(
"exit",
nullptr);
72 ClangdLSPServer::Options
Opts;
78 void log(Level L,
const char *Fmt,
79 const llvm::formatv_object_base &
Message)
override {
80 raw_ostream::Colors Color;
83 Color = raw_ostream::BLUE;
86 Color = raw_ostream::RED;
89 Color = raw_ostream::YELLOW;
92 std::lock_guard<std::mutex> Lock(LogMu);
93 (llvm::outs().changeColor(Color) <<
Message <<
"\n").resetColor();
99 LoggingSession LogSession;
100 llvm::Optional<ClangdLSPServer> Server;
101 llvm::Optional<std::thread> ServerThread;
105 TEST_F(LSPTest, GoToDefinition) {
106 Annotations
Code(R
"cpp(
108 return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
111 auto &Client = start();
113 auto &Def = Client.
call(
"textDocument/definition",
115 {
"textDocument", Client.
documentID(
"foo.cpp")},
116 {
"position",
Code.point()},
119 {
"uri", Client.
uri(
"foo.cpp")}, {
"range",
Code.range()}}};
120 EXPECT_EQ(Def.takeValue(), Want);
124 auto &Client = start();
125 Client.
didOpen(
"foo.cpp",
"void main(int, char**);");
127 llvm::ValueIs(testing::ElementsAre(
128 diagMessage(
"'main' must return 'int' (fix available)"))));
130 Client.
didChange(
"foo.cpp",
"int x = \"42\";");
132 llvm::ValueIs(testing::ElementsAre(
133 diagMessage(
"Cannot initialize a variable of type 'int' with "
134 "an lvalue of type 'const char[3]'"))));
137 EXPECT_THAT(Client.
diagnostics(
"foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
140 TEST_F(LSPTest, DiagnosticsHeaderSaved) {
141 auto &Client = start();
142 Client.
didOpen(
"foo.cpp", R
"cpp(
147 llvm::ValueIs(testing::ElementsAre(
148 diagMessage(
"'foo.h' file not found"),
149 diagMessage(
"Use of undeclared identifier 'VAR'"))));
151 FS.
Files[
"foo.h"] =
"#define VAR original";
153 "textDocument/didSave",
154 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
156 llvm::ValueIs(testing::ElementsAre(
157 diagMessage(
"Use of undeclared identifier 'original'"))));
159 FS.
Files[
"foo.h"] =
"#define VAR changed";
161 "textDocument/didSave",
162 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
165 llvm::ValueIs(testing::ElementsAre(
166 diagMessage(
"Use of undeclared identifier 'changed'"))));
169 TEST_F(LSPTest, RecordsLatencies) {
171 auto &Client = start();
172 llvm::StringLiteral MethodName =
"method_name";
173 EXPECT_THAT(
Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(0));
174 llvm::consumeError(Client.
call(MethodName, {}).
take().takeError());
176 EXPECT_THAT(
Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(1));
179 TEST_F(LSPTest, IncomingCalls) {
180 Annotations
Code(R
"cpp(
186 auto &Client = start();
189 .
call(
"textDocument/prepareCallHierarchy",
191 {
"textDocument", Client.
documentID(
"foo.cpp")},
192 {
"position",
Code.point()}})
194 auto FirstItem = (*Items.getAsArray())[0];
196 .
call(
"callHierarchy/incomingCalls",
197 llvm::json::Object{{
"item", FirstItem}})
199 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
201 auto From = *FirstCall[
"from"].getAsObject();
202 EXPECT_EQ(From[
"name"],
"caller1");
205 TEST_F(LSPTest, CDBConfigIntegration) {
215 CompilationDatabase: bar
217 FS.Files["bar/compile_flags.txt"] =
"-DFOO=BAR";
219 auto &Client = start();
221 Client.
didOpen(
"foo.cpp",
"int x = FOO;");
223 llvm::ValueIs(testing::ElementsAre(
224 diagMessage(
"Use of undeclared identifier 'FOO'"))));
226 Client.
didOpen(
"bar.cpp",
"int x = FOO;");
228 llvm::ValueIs(testing::ElementsAre(
229 diagMessage(
"Use of undeclared identifier 'BAR'"))));
232 TEST_F(LSPTest, ModulesTest) {
233 class MathModule final :
public FeatureModule {
234 OutgoingNotification<int>
Changed;
235 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
236 llvm::json::Object &ServerCaps)
override {
237 Bind.notification(
"add",
this, &MathModule::add);
238 Bind.method(
"get",
this, &MathModule::get);
239 Changed = Bind.outgoingNotification(
"changed");
244 void add(
const int &
X) {
248 void get(
const std::nullptr_t &, Callback<int> Reply) {
249 scheduler().runQuick(
251 [Reply(std::move(Reply)), Value(Value)]()
mutable { Reply(Value); });
256 auto &Client = start();
265 template <
typename T>
266 llvm::unique_function<void(llvm::Expected<T>)>
267 capture(llvm::Optional<llvm::Expected<T>> &
Out) {
269 return [&
Out](llvm::Expected<T> V) {
Out.emplace(std::move(V)); };
272 TEST_F(LSPTest, FeatureModulesThreadingTest) {
275 class AsyncCounter final :
public FeatureModule {
276 bool ShouldStop =
false;
278 std::deque<Callback<int>> Queue;
279 std::condition_variable CV;
284 std::unique_lock<std::mutex> Lock(Mu);
286 CV.wait(Lock, [&] {
return ShouldStop || !Queue.empty(); });
292 Callback<int> &Task = Queue.front();
302 bool blockUntilIdle(Deadline
D)
override {
303 std::unique_lock<std::mutex> Lock(Mu);
304 return clangd::wait(Lock, CV,
D, [
this] {
return Queue.empty(); });
307 void stop()
override {
309 std::lock_guard<std::mutex> Lock(Mu);
316 AsyncCounter() : Thread([this] { run(); }) {}
324 std::lock_guard<std::mutex> Lock(Mu);
325 EXPECT_TRUE(ShouldStop) <<
"ClangdServer should request shutdown";
326 EXPECT_EQ(Queue.size(), 0u) <<
"ClangdServer should block until idle";
331 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
332 llvm::json::Object &ServerCaps)
override {
339 std::lock_guard<std::mutex> Lock(Mu);
346 std::lock_guard<std::mutex> Lock(Mu);
347 Queue.push_back(
nullptr);
354 auto &Client = start();
356 Client.
notify(
"increment",
nullptr);
357 Client.
notify(
"increment",
nullptr);
358 Client.
notify(
"increment",
nullptr);
359 EXPECT_THAT_EXPECTED(Client.
call(
"sync",
nullptr).
take(), Succeeded());
362 Client.
notify(
"increment",
nullptr);
363 Client.
notify(
"increment",
nullptr);
364 Client.
notify(
"increment",
nullptr);
368 TEST_F(LSPTest, DiagModuleTest) {
369 static constexpr llvm::StringLiteral DiagMsg =
"DiagMsg";
370 class DiagModule final :
public FeatureModule {
371 struct DiagHooks :
public ASTListener {
373 D.Message = DiagMsg.str();
378 std::unique_ptr<ASTListener> astListeners()
override {
379 return std::make_unique<DiagHooks>();
384 auto &Client = start();
385 Client.
didOpen(
"foo.cpp",
"test;");
387 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));