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"
18 using testing::ElementsAre
;
19 using testing::HasSubstr
;
20 using testing::IsEmpty
;
21 using testing::UnorderedElementsAre
;
23 // JSON-serializable type for testing.
26 friend bool operator==(Foo A
, Foo B
) { return A
.X
== B
.X
; }
28 bool fromJSON(const llvm::json::Value
&V
, Foo
&F
, llvm::json::Path P
) {
29 return fromJSON(V
, F
.X
, P
.field("X"));
31 llvm::json::Value
toJSON(const Foo
&F
) { return F
.X
; }
33 // Creates a Callback that writes its received value into an Optional<Expected>.
35 llvm::unique_function
<void(llvm::Expected
<T
>)>
36 capture(llvm::Optional
<llvm::Expected
<T
>> &Out
) {
38 return [&Out
](llvm::Expected
<T
> V
) { Out
.emplace(std::move(V
)); };
41 struct OutgoingRecorder
: public LSPBinder::RawOutgoing
{
42 llvm::StringMap
<std::vector
<llvm::json::Value
>> Received
;
44 void callMethod(llvm::StringRef Method
, llvm::json::Value Params
,
45 Callback
<llvm::json::Value
> Reply
) override
{
46 Received
[Method
].push_back(Params
);
48 return Reply(error("Params={0}", Params
));
49 Reply(Params
); // echo back the request
51 void notify(llvm::StringRef Method
, llvm::json::Value Params
) override
{
52 Received
[Method
].push_back(std::move(Params
));
55 std::vector
<llvm::json::Value
> take(llvm::StringRef Method
) {
56 std::vector
<llvm::json::Value
> Result
= Received
.lookup(Method
);
57 Received
.erase(Method
);
62 TEST(LSPBinderTest
, IncomingCalls
) {
63 LSPBinder::RawHandlers RawHandlers
;
64 OutgoingRecorder RawOutgoing
;
65 LSPBinder Binder
{RawHandlers
, RawOutgoing
};
67 void plusOne(const Foo
&Params
, Callback
<Foo
> Reply
) {
68 Reply(Foo
{Params
.X
+ 1});
70 void fail(const Foo
&Params
, Callback
<Foo
> Reply
) {
71 Reply(error("X={0}", Params
.X
));
73 void notify(const Foo
&Params
) {
74 LastNotify
= Params
.X
;
82 Binder
.method("plusOne", &H
, &Handler::plusOne
);
83 Binder
.method("fail", &H
, &Handler::fail
);
84 Binder
.notification("notify", &H
, &Handler::notify
);
85 Binder
.command("cmdPlusOne", &H
, &Handler::plusOne
);
86 ASSERT_THAT(RawHandlers
.MethodHandlers
.keys(),
87 UnorderedElementsAre("plusOne", "fail"));
88 ASSERT_THAT(RawHandlers
.NotificationHandlers
.keys(),
89 UnorderedElementsAre("notify"));
90 ASSERT_THAT(RawHandlers
.CommandHandlers
.keys(),
91 UnorderedElementsAre("cmdPlusOne"));
92 llvm::Optional
<llvm::Expected
<llvm::json::Value
>> Reply
;
94 auto &RawPlusOne
= RawHandlers
.MethodHandlers
["plusOne"];
95 RawPlusOne(1, capture(Reply
));
96 ASSERT_TRUE(Reply
.has_value());
97 EXPECT_THAT_EXPECTED(Reply
.value(), llvm::HasValue(2));
98 RawPlusOne("foo", capture(Reply
));
99 ASSERT_TRUE(Reply
.has_value());
100 EXPECT_THAT_EXPECTED(
102 llvm::FailedWithMessage(
103 HasSubstr("failed to decode plusOne request: expected integer")));
105 auto &RawFail
= RawHandlers
.MethodHandlers
["fail"];
106 RawFail(2, capture(Reply
));
107 ASSERT_TRUE(Reply
.has_value());
108 EXPECT_THAT_EXPECTED(Reply
.value(), llvm::FailedWithMessage("X=2"));
110 auto &RawNotify
= RawHandlers
.NotificationHandlers
["notify"];
112 EXPECT_EQ(H
.LastNotify
, 42);
113 EXPECT_EQ(H
.NotifyCount
, 1);
114 RawNotify("hi"); // invalid, will be logged
115 EXPECT_EQ(H
.LastNotify
, 42);
116 EXPECT_EQ(H
.NotifyCount
, 1);
118 auto &RawCmdPlusOne
= RawHandlers
.CommandHandlers
["cmdPlusOne"];
119 RawCmdPlusOne(1, capture(Reply
));
120 ASSERT_TRUE(Reply
.has_value());
121 EXPECT_THAT_EXPECTED(Reply
.value(), llvm::HasValue(2));
123 // None of this generated any outgoing traffic.
124 EXPECT_THAT(RawOutgoing
.Received
, IsEmpty());
127 TEST(LSPBinderTest
, OutgoingCalls
) {
128 LSPBinder::RawHandlers RawHandlers
;
129 OutgoingRecorder RawOutgoing
;
130 LSPBinder Binder
{RawHandlers
, RawOutgoing
};
132 LSPBinder::OutgoingMethod
<Foo
, Foo
> Echo
;
133 Echo
= Binder
.outgoingMethod("echo");
134 LSPBinder::OutgoingMethod
<Foo
, std::string
> WrongSignature
;
135 WrongSignature
= Binder
.outgoingMethod("wrongSignature");
136 LSPBinder::OutgoingMethod
<Foo
, Foo
> Fail
;
137 Fail
= Binder
.outgoingMethod("fail");
139 llvm::Optional
<llvm::Expected
<Foo
>> Reply
;
140 Echo(Foo
{2}, capture(Reply
));
141 EXPECT_THAT(RawOutgoing
.take("echo"), ElementsAre(llvm::json::Value(2)));
142 ASSERT_TRUE(Reply
.has_value());
143 EXPECT_THAT_EXPECTED(Reply
.value(), llvm::HasValue(Foo
{2}));
145 // JSON response is integer, can't be parsed as string.
146 llvm::Optional
<llvm::Expected
<std::string
>> WrongTypeReply
;
147 WrongSignature(Foo
{2}, capture(WrongTypeReply
));
148 EXPECT_THAT(RawOutgoing
.take("wrongSignature"),
149 ElementsAre(llvm::json::Value(2)));
150 ASSERT_TRUE(Reply
.has_value());
151 EXPECT_THAT_EXPECTED(WrongTypeReply
.value(),
152 llvm::FailedWithMessage(
153 HasSubstr("failed to decode wrongSignature reply")));
155 Fail(Foo
{2}, capture(Reply
));
156 EXPECT_THAT(RawOutgoing
.take("fail"), ElementsAre(llvm::json::Value(2)));
157 ASSERT_TRUE(Reply
.has_value());
158 EXPECT_THAT_EXPECTED(Reply
.value(), llvm::FailedWithMessage("Params=2"));
162 } // namespace clangd