1 //===-- LSPClient.cpp - Helper for ClangdLSPServer tests ------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "LSPClient.h"
10 #include "Protocol.h"
11 #include "TestFS.h"
12 #include "Transport.h"
13 #include "support/Logger.h"
14 #include "support/Threading.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/raw_ostream.h"
17 #include "gtest/gtest.h"
18 #include <condition_variable>
19 #include <queue>
20
21 namespace clang {
22 namespace clangd {
23
take()24 llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() {
25 std::unique_lock<std::mutex> Lock(Mu);
26 if (!clangd::wait(Lock, CV, timeoutSeconds(10),
27 [this] { return Value.has_value(); })) {
28 ADD_FAILURE() << "No result from call after 10 seconds!";
29 return llvm::json::Value(nullptr);
30 }
31 auto Res = std::move(*Value);
32 Value.reset();
33 return Res;
34 }
35
takeValue()36 llvm::json::Value LSPClient::CallResult::takeValue() {
37 auto ExpValue = take();
38 if (!ExpValue) {
39 ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError());
40 return llvm::json::Value(nullptr);
41 }
42 return std::move(*ExpValue);
43 }
44
set(llvm::Expected<llvm::json::Value> V)45 void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) {
46 std::lock_guard<std::mutex> Lock(Mu);
47 if (Value) {
48 ADD_FAILURE() << "Multiple replies";
49 llvm::consumeError(V.takeError());
50 return;
51 }
52 Value = std::move(V);
53 CV.notify_all();
54 }
55
~CallResult()56 LSPClient::CallResult::~CallResult() {
57 if (Value && !*Value) {
58 ADD_FAILURE() << llvm::toString(Value->takeError());
59 }
60 }
61
logBody(llvm::StringRef Method,llvm::json::Value V,bool Send)62 static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) {
63 // We invert <<< and >>> as the combined log is from the server's viewpoint.
64 vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V);
65 }
66
67 class LSPClient::TransportImpl : public Transport {
68 public:
addCallSlot()69 std::pair<llvm::json::Value, CallResult *> addCallSlot() {
70 std::lock_guard<std::mutex> Lock(Mu);
71 unsigned ID = CallResults.size();
72 CallResults.emplace_back();
73 return {ID, &CallResults.back()};
74 }
75
76 // A null action causes the transport to shut down.
enqueue(std::function<void (MessageHandler &)> Action)77 void enqueue(std::function<void(MessageHandler &)> Action) {
78 std::lock_guard<std::mutex> Lock(Mu);
79 Actions.push(std::move(Action));
80 CV.notify_all();
81 }
82
takeNotifications(llvm::StringRef Method)83 std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) {
84 std::vector<llvm::json::Value> Result;
85 {
86 std::lock_guard<std::mutex> Lock(Mu);
87 std::swap(Result, Notifications[Method]);
88 }
89 return Result;
90 }
91
92 private:
reply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> V)93 void reply(llvm::json::Value ID,
94 llvm::Expected<llvm::json::Value> V) override {
95 if (V) // Nothing additional to log for error.
96 logBody("reply", *V, /*Send=*/false);
97 std::lock_guard<std::mutex> Lock(Mu);
98 if (auto I = ID.getAsInteger()) {
99 if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) {
100 CallResults[*I].set(std::move(V));
101 return;
102 }
103 }
104 ADD_FAILURE() << "Invalid reply to ID " << ID;
105 llvm::consumeError(std::move(V).takeError());
106 }
107
notify(llvm::StringRef Method,llvm::json::Value V)108 void notify(llvm::StringRef Method, llvm::json::Value V) override {
109 logBody(Method, V, /*Send=*/false);
110 std::lock_guard<std::mutex> Lock(Mu);
111 Notifications[Method].push_back(std::move(V));
112 }
113
call(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)114 void call(llvm::StringRef Method, llvm::json::Value Params,
115 llvm::json::Value ID) override {
116 logBody(Method, Params, /*Send=*/false);
117 ADD_FAILURE() << "Unexpected server->client call " << Method;
118 }
119
loop(MessageHandler & H)120 llvm::Error loop(MessageHandler &H) override {
121 std::unique_lock<std::mutex> Lock(Mu);
122 while (true) {
123 CV.wait(Lock, [&] { return !Actions.empty(); });
124 if (!Actions.front()) // Stop!
125 return llvm::Error::success();
126 auto Action = std::move(Actions.front());
127 Actions.pop();
128 Lock.unlock();
129 Action(H);
130 Lock.lock();
131 }
132 }
133
134 std::mutex Mu;
135 std::deque<CallResult> CallResults;
136 std::queue<std::function<void(Transport::MessageHandler &)>> Actions;
137 std::condition_variable CV;
138 llvm::StringMap<std::vector<llvm::json::Value>> Notifications;
139 };
140
LSPClient()141 LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {}
142 LSPClient::~LSPClient() = default;
143
call(llvm::StringRef Method,llvm::json::Value Params)144 LSPClient::CallResult &LSPClient::call(llvm::StringRef Method,
145 llvm::json::Value Params) {
146 auto Slot = T->addCallSlot();
147 T->enqueue([ID(Slot.first), Method(Method.str()),
148 Params(std::move(Params))](Transport::MessageHandler &H) {
149 logBody(Method, Params, /*Send=*/true);
150 H.onCall(Method, std::move(Params), ID);
151 });
152 return *Slot.second;
153 }
154
notify(llvm::StringRef Method,llvm::json::Value Params)155 void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) {
156 T->enqueue([Method(Method.str()),
157 Params(std::move(Params))](Transport::MessageHandler &H) {
158 logBody(Method, Params, /*Send=*/true);
159 H.onNotify(Method, std::move(Params));
160 });
161 }
162
163 std::vector<llvm::json::Value>
takeNotifications(llvm::StringRef Method)164 LSPClient::takeNotifications(llvm::StringRef Method) {
165 return T->takeNotifications(Method);
166 }
167
stop()168 void LSPClient::stop() { T->enqueue(nullptr); }
169
transport()170 Transport &LSPClient::transport() { return *T; }
171
172 using Obj = llvm::json::Object;
173
uri(llvm::StringRef Path)174 llvm::json::Value LSPClient::uri(llvm::StringRef Path) {
175 std::string Storage;
176 if (!llvm::sys::path::is_absolute(Path))
177 Path = Storage = testPath(Path);
178 return toJSON(URIForFile::canonicalize(Path, Path));
179 }
documentID(llvm::StringRef Path)180 llvm::json::Value LSPClient::documentID(llvm::StringRef Path) {
181 return Obj{{"uri", uri(Path)}};
182 }
183
didOpen(llvm::StringRef Path,llvm::StringRef Content)184 void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) {
185 notify(
186 "textDocument/didOpen",
187 Obj{{"textDocument",
188 Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}});
189 }
didChange(llvm::StringRef Path,llvm::StringRef Content)190 void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) {
191 notify("textDocument/didChange",
192 Obj{{"textDocument", documentID(Path)},
193 {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}});
194 }
didClose(llvm::StringRef Path)195 void LSPClient::didClose(llvm::StringRef Path) {
196 notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}});
197 }
198
sync()199 void LSPClient::sync() { call("sync", nullptr).takeValue(); }
200
201 llvm::Optional<std::vector<llvm::json::Value>>
diagnostics(llvm::StringRef Path)202 LSPClient::diagnostics(llvm::StringRef Path) {
203 sync();
204 auto Notifications = takeNotifications("textDocument/publishDiagnostics");
205 for (const auto &Notification : llvm::reverse(Notifications)) {
206 if (const auto *PubDiagsParams = Notification.getAsObject()) {
207 auto U = PubDiagsParams->getString("uri");
208 auto *D = PubDiagsParams->getArray("diagnostics");
209 if (!U || !D) {
210 ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams;
211 continue;
212 }
213 if (*U == uri(Path))
214 return std::vector<llvm::json::Value>(D->begin(), D->end());
215 }
216 }
217 return {};
218 }
219
220 } // namespace clangd
221 } // namespace clang
222