xref: /llvm-project/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
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 
15 namespace clang {
16 namespace clangd {
17 namespace {
18 
19 using testing::ElementsAre;
20 using testing::HasSubstr;
21 using testing::IsEmpty;
22 using testing::UnorderedElementsAre;
23 
24 // JSON-serializable type for testing.
25 struct Foo {
26   int X;
operator ==(Foo A,Foo B)27   friend bool operator==(Foo A, Foo B) { return A.X == B.X; }
28 };
fromJSON(const llvm::json::Value & V,Foo & F,llvm::json::Path P)29 bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
30   return fromJSON(V, F.X, P.field("X"));
31 }
toJSON(const Foo & F)32 llvm::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>.
36 template <typename T>
37 llvm::unique_function<void(llvm::Expected<T>)>
capture(std::optional<llvm::Expected<T>> & Out)38 capture(std::optional<llvm::Expected<T>> &Out) {
39   Out.reset();
40   return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
41 }
42 
43 struct OutgoingRecorder : public LSPBinder::RawOutgoing {
44   llvm::StringMap<std::vector<llvm::json::Value>> Received;
45 
callMethodclang::clangd::__anon093857260111::OutgoingRecorder46   void callMethod(llvm::StringRef Method, llvm::json::Value Params,
47                   Callback<llvm::json::Value> Reply) override {
48     Received[Method].push_back(Params);
49     if (Method == "fail")
50       return Reply(error("Params={0}", Params));
51     Reply(Params); // echo back the request
52   }
notifyclang::clangd::__anon093857260111::OutgoingRecorder53   void notify(llvm::StringRef Method, llvm::json::Value Params) override {
54     Received[Method].push_back(std::move(Params));
55   }
56 
takeclang::clangd::__anon093857260111::OutgoingRecorder57   std::vector<llvm::json::Value> take(llvm::StringRef Method) {
58     std::vector<llvm::json::Value> Result = Received.lookup(Method);
59     Received.erase(Method);
60     return Result;
61   }
62 };
63 
TEST(LSPBinderTest,IncomingCalls)64 TEST(LSPBinderTest, IncomingCalls) {
65   LSPBinder::RawHandlers RawHandlers;
66   OutgoingRecorder RawOutgoing;
67   LSPBinder Binder{RawHandlers, RawOutgoing};
68   struct Handler {
69     void plusOne(const Foo &Params, Callback<Foo> Reply) {
70       Reply(Foo{Params.X + 1});
71     }
72     void fail(const Foo &Params, Callback<Foo> Reply) {
73       Reply(error("X={0}", Params.X));
74     }
75     void notify(const Foo &Params) {
76       LastNotify = Params.X;
77       ++NotifyCount;
78     }
79     int LastNotify = -1;
80     int NotifyCount = 0;
81   };
82 
83   Handler H;
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;
95 
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")));
105 
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"));
110 
111   auto &RawNotify = RawHandlers.NotificationHandlers["notify"];
112   RawNotify(42);
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);
118 
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));
123 
124   // None of this generated any outgoing traffic.
125   EXPECT_THAT(RawOutgoing.Received, IsEmpty());
126 }
127 
TEST(LSPBinderTest,OutgoingCalls)128 TEST(LSPBinderTest, OutgoingCalls) {
129   LSPBinder::RawHandlers RawHandlers;
130   OutgoingRecorder RawOutgoing;
131   LSPBinder Binder{RawHandlers, RawOutgoing};
132 
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");
139 
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}));
145 
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")));
155 
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"));
160 }
161 
162 } // namespace
163 } // namespace clangd
164 } // namespace clang
165