forked from OSchip/llvm-project
164 lines
5.5 KiB
C++
164 lines
5.5 KiB
C++
//===-- LSPBinderTests.cpp ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "LSPBinder.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using testing::ElementsAre;
|
|
using testing::HasSubstr;
|
|
using testing::IsEmpty;
|
|
using testing::UnorderedElementsAre;
|
|
|
|
// JSON-serializable type for testing.
|
|
struct Foo {
|
|
int X;
|
|
friend bool operator==(Foo A, Foo B) { return A.X == B.X; }
|
|
};
|
|
bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
|
|
return fromJSON(V, F.X, P.field("X"));
|
|
}
|
|
llvm::json::Value toJSON(const Foo &F) { return F.X; }
|
|
|
|
// Creates a Callback that writes its received value into an Optional<Expected>.
|
|
template <typename T>
|
|
llvm::unique_function<void(llvm::Expected<T>)>
|
|
capture(llvm::Optional<llvm::Expected<T>> &Out) {
|
|
Out.reset();
|
|
return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
|
|
}
|
|
|
|
struct OutgoingRecorder : public LSPBinder::RawOutgoing {
|
|
llvm::StringMap<std::vector<llvm::json::Value>> Received;
|
|
|
|
void callMethod(llvm::StringRef Method, llvm::json::Value Params,
|
|
Callback<llvm::json::Value> Reply) override {
|
|
Received[Method].push_back(Params);
|
|
if (Method == "fail")
|
|
return Reply(error("Params={0}", Params));
|
|
Reply(Params); // echo back the request
|
|
}
|
|
void notify(llvm::StringRef Method, llvm::json::Value Params) override {
|
|
Received[Method].push_back(std::move(Params));
|
|
}
|
|
|
|
std::vector<llvm::json::Value> take(llvm::StringRef Method) {
|
|
std::vector<llvm::json::Value> Result = Received.lookup(Method);
|
|
Received.erase(Method);
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
TEST(LSPBinderTest, IncomingCalls) {
|
|
LSPBinder::RawHandlers RawHandlers;
|
|
OutgoingRecorder RawOutgoing;
|
|
LSPBinder Binder{RawHandlers, RawOutgoing};
|
|
struct Handler {
|
|
void plusOne(const Foo &Params, Callback<Foo> Reply) {
|
|
Reply(Foo{Params.X + 1});
|
|
}
|
|
void fail(const Foo &Params, Callback<Foo> Reply) {
|
|
Reply(error("X={0}", Params.X));
|
|
}
|
|
void notify(const Foo &Params) {
|
|
LastNotify = Params.X;
|
|
++NotifyCount;
|
|
}
|
|
int LastNotify = -1;
|
|
int NotifyCount = 0;
|
|
};
|
|
|
|
Handler H;
|
|
Binder.method("plusOne", &H, &Handler::plusOne);
|
|
Binder.method("fail", &H, &Handler::fail);
|
|
Binder.notification("notify", &H, &Handler::notify);
|
|
Binder.command("cmdPlusOne", &H, &Handler::plusOne);
|
|
ASSERT_THAT(RawHandlers.MethodHandlers.keys(),
|
|
UnorderedElementsAre("plusOne", "fail"));
|
|
ASSERT_THAT(RawHandlers.NotificationHandlers.keys(),
|
|
UnorderedElementsAre("notify"));
|
|
ASSERT_THAT(RawHandlers.CommandHandlers.keys(),
|
|
UnorderedElementsAre("cmdPlusOne"));
|
|
llvm::Optional<llvm::Expected<llvm::json::Value>> Reply;
|
|
|
|
auto &RawPlusOne = RawHandlers.MethodHandlers["plusOne"];
|
|
RawPlusOne(1, capture(Reply));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(Reply.value(), llvm::HasValue(2));
|
|
RawPlusOne("foo", capture(Reply));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(
|
|
Reply.value(),
|
|
llvm::FailedWithMessage(
|
|
HasSubstr("failed to decode plusOne request: expected integer")));
|
|
|
|
auto &RawFail = RawHandlers.MethodHandlers["fail"];
|
|
RawFail(2, capture(Reply));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(Reply.value(), llvm::FailedWithMessage("X=2"));
|
|
|
|
auto &RawNotify = RawHandlers.NotificationHandlers["notify"];
|
|
RawNotify(42);
|
|
EXPECT_EQ(H.LastNotify, 42);
|
|
EXPECT_EQ(H.NotifyCount, 1);
|
|
RawNotify("hi"); // invalid, will be logged
|
|
EXPECT_EQ(H.LastNotify, 42);
|
|
EXPECT_EQ(H.NotifyCount, 1);
|
|
|
|
auto &RawCmdPlusOne = RawHandlers.CommandHandlers["cmdPlusOne"];
|
|
RawCmdPlusOne(1, capture(Reply));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(Reply.value(), llvm::HasValue(2));
|
|
|
|
// None of this generated any outgoing traffic.
|
|
EXPECT_THAT(RawOutgoing.Received, IsEmpty());
|
|
}
|
|
|
|
TEST(LSPBinderTest, OutgoingCalls) {
|
|
LSPBinder::RawHandlers RawHandlers;
|
|
OutgoingRecorder RawOutgoing;
|
|
LSPBinder Binder{RawHandlers, RawOutgoing};
|
|
|
|
LSPBinder::OutgoingMethod<Foo, Foo> Echo;
|
|
Echo = Binder.outgoingMethod("echo");
|
|
LSPBinder::OutgoingMethod<Foo, std::string> WrongSignature;
|
|
WrongSignature = Binder.outgoingMethod("wrongSignature");
|
|
LSPBinder::OutgoingMethod<Foo, Foo> Fail;
|
|
Fail = Binder.outgoingMethod("fail");
|
|
|
|
llvm::Optional<llvm::Expected<Foo>> Reply;
|
|
Echo(Foo{2}, capture(Reply));
|
|
EXPECT_THAT(RawOutgoing.take("echo"), ElementsAre(llvm::json::Value(2)));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(Reply.value(), llvm::HasValue(Foo{2}));
|
|
|
|
// JSON response is integer, can't be parsed as string.
|
|
llvm::Optional<llvm::Expected<std::string>> WrongTypeReply;
|
|
WrongSignature(Foo{2}, capture(WrongTypeReply));
|
|
EXPECT_THAT(RawOutgoing.take("wrongSignature"),
|
|
ElementsAre(llvm::json::Value(2)));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(WrongTypeReply.value(),
|
|
llvm::FailedWithMessage(
|
|
HasSubstr("failed to decode wrongSignature reply")));
|
|
|
|
Fail(Foo{2}, capture(Reply));
|
|
EXPECT_THAT(RawOutgoing.take("fail"), ElementsAre(llvm::json::Value(2)));
|
|
ASSERT_TRUE(Reply.has_value());
|
|
EXPECT_THAT_EXPECTED(Reply.value(), llvm::FailedWithMessage("Params=2"));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|