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