llvm-project
164 строки · 5.5 Кб
1//===-- LSPBinderTests.cpp ------------------------------------------------===//
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 "LSPBinder.h"
10#include "llvm/Testing/Support/Error.h"
11#include "gmock/gmock.h"
12#include "gtest/gtest.h"
13#include <optional>
14
15namespace clang {
16namespace clangd {
17namespace {
18
19using testing::ElementsAre;
20using testing::HasSubstr;
21using testing::IsEmpty;
22using testing::UnorderedElementsAre;
23
24// JSON-serializable type for testing.
25struct Foo {
26int X;
27friend bool operator==(Foo A, Foo B) { return A.X == B.X; }
28};
29bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
30return fromJSON(V, F.X, P.field("X"));
31}
32llvm::json::Value toJSON(const Foo &F) { return F.X; }
33
34// Creates a Callback that writes its received value into an
35// std::optional<Expected>.
36template <typename T>
37llvm::unique_function<void(llvm::Expected<T>)>
38capture(std::optional<llvm::Expected<T>> &Out) {
39Out.reset();
40return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
41}
42
43struct OutgoingRecorder : public LSPBinder::RawOutgoing {
44llvm::StringMap<std::vector<llvm::json::Value>> Received;
45
46void callMethod(llvm::StringRef Method, llvm::json::Value Params,
47Callback<llvm::json::Value> Reply) override {
48Received[Method].push_back(Params);
49if (Method == "fail")
50return Reply(error("Params={0}", Params));
51Reply(Params); // echo back the request
52}
53void notify(llvm::StringRef Method, llvm::json::Value Params) override {
54Received[Method].push_back(std::move(Params));
55}
56
57std::vector<llvm::json::Value> take(llvm::StringRef Method) {
58std::vector<llvm::json::Value> Result = Received.lookup(Method);
59Received.erase(Method);
60return Result;
61}
62};
63
64TEST(LSPBinderTest, IncomingCalls) {
65LSPBinder::RawHandlers RawHandlers;
66OutgoingRecorder RawOutgoing;
67LSPBinder Binder{RawHandlers, RawOutgoing};
68struct Handler {
69void plusOne(const Foo &Params, Callback<Foo> Reply) {
70Reply(Foo{Params.X + 1});
71}
72void fail(const Foo &Params, Callback<Foo> Reply) {
73Reply(error("X={0}", Params.X));
74}
75void notify(const Foo &Params) {
76LastNotify = Params.X;
77++NotifyCount;
78}
79int LastNotify = -1;
80int NotifyCount = 0;
81};
82
83Handler H;
84Binder.method("plusOne", &H, &Handler::plusOne);
85Binder.method("fail", &H, &Handler::fail);
86Binder.notification("notify", &H, &Handler::notify);
87Binder.command("cmdPlusOne", &H, &Handler::plusOne);
88ASSERT_THAT(RawHandlers.MethodHandlers.keys(),
89UnorderedElementsAre("plusOne", "fail"));
90ASSERT_THAT(RawHandlers.NotificationHandlers.keys(),
91UnorderedElementsAre("notify"));
92ASSERT_THAT(RawHandlers.CommandHandlers.keys(),
93UnorderedElementsAre("cmdPlusOne"));
94std::optional<llvm::Expected<llvm::json::Value>> Reply;
95
96auto &RawPlusOne = RawHandlers.MethodHandlers["plusOne"];
97RawPlusOne(1, capture(Reply));
98ASSERT_TRUE(Reply.has_value());
99EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(2));
100RawPlusOne("foo", capture(Reply));
101ASSERT_TRUE(Reply.has_value());
102EXPECT_THAT_EXPECTED(
103*Reply, llvm::FailedWithMessage(HasSubstr(
104"failed to decode plusOne request: expected integer")));
105
106auto &RawFail = RawHandlers.MethodHandlers["fail"];
107RawFail(2, capture(Reply));
108ASSERT_TRUE(Reply.has_value());
109EXPECT_THAT_EXPECTED(*Reply, llvm::FailedWithMessage("X=2"));
110
111auto &RawNotify = RawHandlers.NotificationHandlers["notify"];
112RawNotify(42);
113EXPECT_EQ(H.LastNotify, 42);
114EXPECT_EQ(H.NotifyCount, 1);
115RawNotify("hi"); // invalid, will be logged
116EXPECT_EQ(H.LastNotify, 42);
117EXPECT_EQ(H.NotifyCount, 1);
118
119auto &RawCmdPlusOne = RawHandlers.CommandHandlers["cmdPlusOne"];
120RawCmdPlusOne(1, capture(Reply));
121ASSERT_TRUE(Reply.has_value());
122EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(2));
123
124// None of this generated any outgoing traffic.
125EXPECT_THAT(RawOutgoing.Received, IsEmpty());
126}
127
128TEST(LSPBinderTest, OutgoingCalls) {
129LSPBinder::RawHandlers RawHandlers;
130OutgoingRecorder RawOutgoing;
131LSPBinder Binder{RawHandlers, RawOutgoing};
132
133LSPBinder::OutgoingMethod<Foo, Foo> Echo;
134Echo = Binder.outgoingMethod("echo");
135LSPBinder::OutgoingMethod<Foo, std::string> WrongSignature;
136WrongSignature = Binder.outgoingMethod("wrongSignature");
137LSPBinder::OutgoingMethod<Foo, Foo> Fail;
138Fail = Binder.outgoingMethod("fail");
139
140std::optional<llvm::Expected<Foo>> Reply;
141Echo(Foo{2}, capture(Reply));
142EXPECT_THAT(RawOutgoing.take("echo"), ElementsAre(llvm::json::Value(2)));
143ASSERT_TRUE(Reply.has_value());
144EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(Foo{2}));
145
146// JSON response is integer, can't be parsed as string.
147std::optional<llvm::Expected<std::string>> WrongTypeReply;
148WrongSignature(Foo{2}, capture(WrongTypeReply));
149EXPECT_THAT(RawOutgoing.take("wrongSignature"),
150ElementsAre(llvm::json::Value(2)));
151ASSERT_TRUE(Reply.has_value());
152EXPECT_THAT_EXPECTED(*WrongTypeReply,
153llvm::FailedWithMessage(
154HasSubstr("failed to decode wrongSignature reply")));
155
156Fail(Foo{2}, capture(Reply));
157EXPECT_THAT(RawOutgoing.take("fail"), ElementsAre(llvm::json::Value(2)));
158ASSERT_TRUE(Reply.has_value());
159EXPECT_THAT_EXPECTED(*Reply, llvm::FailedWithMessage("Params=2"));
160}
161
162} // namespace
163} // namespace clangd
164} // namespace clang
165