llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp

212 lines
6.4 KiB
C++

#include "LSPClient.h"
#include "gtest/gtest.h"
#include <condition_variable>
#include "Protocol.h"
#include "TestFS.h"
#include "Threading.h"
#include "Transport.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <queue>
namespace clang {
namespace clangd {
llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() {
std::unique_lock<std::mutex> Lock(Mu);
if (!clangd::wait(Lock, CV, timeoutSeconds(10),
[this] { return Value.hasValue(); })) {
ADD_FAILURE() << "No result from call after 10 seconds!";
return llvm::json::Value(nullptr);
}
return std::move(*Value);
}
llvm::json::Value LSPClient::CallResult::takeValue() {
auto ExpValue = take();
if (!ExpValue) {
ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError());
return llvm::json::Value(nullptr);
}
return std::move(*ExpValue);
}
void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) {
std::lock_guard<std::mutex> Lock(Mu);
if (Value) {
ADD_FAILURE() << "Multiple replies";
llvm::consumeError(V.takeError());
return;
}
Value = std::move(V);
CV.notify_all();
}
LSPClient::CallResult::~CallResult() {
if (Value && !*Value) {
ADD_FAILURE() << llvm::toString(Value->takeError());
}
}
static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) {
// We invert <<< and >>> as the combined log is from the server's viewpoint.
vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V);
}
class LSPClient::TransportImpl : public Transport {
public:
std::pair<llvm::json::Value, CallResult *> addCallSlot() {
std::lock_guard<std::mutex> Lock(Mu);
unsigned ID = CallResults.size();
CallResults.emplace_back();
return {ID, &CallResults.back()};
}
// A null action causes the transport to shut down.
void enqueue(std::function<void(MessageHandler &)> Action) {
std::lock_guard<std::mutex> Lock(Mu);
Actions.push(std::move(Action));
CV.notify_all();
}
std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) {
std::vector<llvm::json::Value> Result;
{
std::lock_guard<std::mutex> Lock(Mu);
std::swap(Result, Notifications[Method]);
}
return Result;
}
private:
void reply(llvm::json::Value ID,
llvm::Expected<llvm::json::Value> V) override {
if (V) // Nothing additional to log for error.
logBody("reply", *V, /*Send=*/false);
std::lock_guard<std::mutex> Lock(Mu);
if (auto I = ID.getAsInteger()) {
if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) {
CallResults[*I].set(std::move(V));
return;
}
}
ADD_FAILURE() << "Invalid reply to ID " << ID;
llvm::consumeError(std::move(V).takeError());
}
void notify(llvm::StringRef Method, llvm::json::Value V) override {
logBody(Method, V, /*Send=*/false);
std::lock_guard<std::mutex> Lock(Mu);
Notifications[Method].push_back(std::move(V));
}
void call(llvm::StringRef Method, llvm::json::Value Params,
llvm::json::Value ID) override {
logBody(Method, Params, /*Send=*/false);
ADD_FAILURE() << "Unexpected server->client call " << Method;
}
llvm::Error loop(MessageHandler &H) override {
std::unique_lock<std::mutex> Lock(Mu);
while (true) {
CV.wait(Lock, [&] { return !Actions.empty(); });
if (!Actions.front()) // Stop!
return llvm::Error::success();
auto Action = std::move(Actions.front());
Actions.pop();
Lock.unlock();
Action(H);
Lock.lock();
}
}
std::mutex Mu;
std::deque<CallResult> CallResults;
std::queue<std::function<void(Transport::MessageHandler &)>> Actions;
std::condition_variable CV;
llvm::StringMap<std::vector<llvm::json::Value>> Notifications;
};
LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {}
LSPClient::~LSPClient() = default;
LSPClient::CallResult &LSPClient::call(llvm::StringRef Method,
llvm::json::Value Params) {
auto Slot = T->addCallSlot();
T->enqueue([ID(Slot.first), Method(Method.str()),
Params(std::move(Params))](Transport::MessageHandler &H) {
logBody(Method, Params, /*Send=*/true);
H.onCall(Method, std::move(Params), ID);
});
return *Slot.second;
}
void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) {
T->enqueue([Method(Method.str()),
Params(std::move(Params))](Transport::MessageHandler &H) {
logBody(Method, Params, /*Send=*/true);
H.onNotify(Method, std::move(Params));
});
}
std::vector<llvm::json::Value>
LSPClient::takeNotifications(llvm::StringRef Method) {
return T->takeNotifications(Method);
}
void LSPClient::stop() { T->enqueue(nullptr); }
Transport &LSPClient::transport() { return *T; }
using Obj = llvm::json::Object;
llvm::json::Value LSPClient::uri(llvm::StringRef Path) {
std::string Storage;
if (!llvm::sys::path::is_absolute(Path))
Path = Storage = testPath(Path);
return toJSON(URIForFile::canonicalize(Path, Path));
}
llvm::json::Value LSPClient::documentID(llvm::StringRef Path) {
return Obj{{"uri", uri(Path)}};
}
void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) {
notify(
"textDocument/didOpen",
Obj{{"textDocument",
Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}});
}
void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) {
notify("textDocument/didChange",
Obj{{"textDocument", documentID(Path)},
{"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}});
}
void LSPClient::didClose(llvm::StringRef Path) {
notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}});
}
void LSPClient::sync() { call("sync", nullptr).takeValue(); }
llvm::Optional<std::vector<llvm::json::Value>>
LSPClient::diagnostics(llvm::StringRef Path) {
sync();
auto Notifications = takeNotifications("textDocument/publishDiagnostics");
for (const auto &Notification : llvm::reverse(Notifications)) {
if (const auto *PubDiagsParams = Notification.getAsObject()) {
auto U = PubDiagsParams->getString("uri");
auto *D = PubDiagsParams->getArray("diagnostics");
if (!U || !D) {
ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams;
continue;
}
if (*U == uri(Path))
return std::vector<llvm::json::Value>(D->begin(), D->end());
}
}
return {};
}
} // namespace clangd
} // namespace clang