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