[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clangd / unittests / LSPBinderTests.cpp
blobf640a4220bdeb2448cc973ae6fbc07ba12c9279b
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 //===----------------------------------------------------------------------===//
9 #include "LSPBinder.h"
10 #include "llvm/Testing/Support/Error.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
14 namespace clang {
15 namespace clangd {
16 namespace {
18 using testing::ElementsAre;
19 using testing::HasSubstr;
20 using testing::IsEmpty;
21 using testing::UnorderedElementsAre;
23 // JSON-serializable type for testing.
24 struct Foo {
25 int X;
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>.
34 template <typename T>
35 llvm::unique_function<void(llvm::Expected<T>)>
36 capture(llvm::Optional<llvm::Expected<T>> &Out) {
37 Out.reset();
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);
47 if (Method == "fail")
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);
58 return Result;
62 TEST(LSPBinderTest, IncomingCalls) {
63 LSPBinder::RawHandlers RawHandlers;
64 OutgoingRecorder RawOutgoing;
65 LSPBinder Binder{RawHandlers, RawOutgoing};
66 struct Handler {
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;
75 ++NotifyCount;
77 int LastNotify = -1;
78 int NotifyCount = 0;
81 Handler H;
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(
101 Reply.value(),
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"];
111 RawNotify(42);
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"));
161 } // namespace
162 } // namespace clangd
163 } // namespace clang