xref: /llvm-project/clang-tools-extra/clangd/unittests/LSPBinderTests.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
15786f64aSSam McCall //===-- LSPBinderTests.cpp ------------------------------------------------===//
25786f64aSSam McCall //
35786f64aSSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45786f64aSSam McCall // See https://llvm.org/LICENSE.txt for license information.
55786f64aSSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65786f64aSSam McCall //
75786f64aSSam McCall //===----------------------------------------------------------------------===//
85786f64aSSam McCall 
95786f64aSSam McCall #include "LSPBinder.h"
105786f64aSSam McCall #include "llvm/Testing/Support/Error.h"
115786f64aSSam McCall #include "gmock/gmock.h"
125786f64aSSam McCall #include "gtest/gtest.h"
1371f55735SKazu Hirata #include <optional>
145786f64aSSam McCall 
155786f64aSSam McCall namespace clang {
165786f64aSSam McCall namespace clangd {
175786f64aSSam McCall namespace {
185786f64aSSam McCall 
197b83837aSSam McCall using testing::ElementsAre;
205786f64aSSam McCall using testing::HasSubstr;
217b83837aSSam McCall using testing::IsEmpty;
225786f64aSSam McCall using testing::UnorderedElementsAre;
235786f64aSSam McCall 
245786f64aSSam McCall // JSON-serializable type for testing.
255786f64aSSam McCall struct Foo {
265786f64aSSam McCall   int X;
operator ==(Foo A,Foo B)277b83837aSSam McCall   friend bool operator==(Foo A, Foo B) { return A.X == B.X; }
285786f64aSSam McCall };
fromJSON(const llvm::json::Value & V,Foo & F,llvm::json::Path P)295786f64aSSam McCall bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
305786f64aSSam McCall   return fromJSON(V, F.X, P.field("X"));
315786f64aSSam McCall }
toJSON(const Foo & F)325786f64aSSam McCall llvm::json::Value toJSON(const Foo &F) { return F.X; }
335786f64aSSam McCall 
34*f71ffd3bSKazu Hirata // Creates a Callback that writes its received value into an
35*f71ffd3bSKazu Hirata // std::optional<Expected>.
365786f64aSSam McCall template <typename T>
375786f64aSSam McCall llvm::unique_function<void(llvm::Expected<T>)>
capture(std::optional<llvm::Expected<T>> & Out)38*f71ffd3bSKazu Hirata capture(std::optional<llvm::Expected<T>> &Out) {
395786f64aSSam McCall   Out.reset();
405786f64aSSam McCall   return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
415786f64aSSam McCall }
425786f64aSSam McCall 
437b83837aSSam McCall struct OutgoingRecorder : public LSPBinder::RawOutgoing {
447b83837aSSam McCall   llvm::StringMap<std::vector<llvm::json::Value>> Received;
457b83837aSSam McCall 
callMethodclang::clangd::__anon093857260111::OutgoingRecorder467b83837aSSam McCall   void callMethod(llvm::StringRef Method, llvm::json::Value Params,
477b83837aSSam McCall                   Callback<llvm::json::Value> Reply) override {
487b83837aSSam McCall     Received[Method].push_back(Params);
497b83837aSSam McCall     if (Method == "fail")
507b83837aSSam McCall       return Reply(error("Params={0}", Params));
517b83837aSSam McCall     Reply(Params); // echo back the request
527b83837aSSam McCall   }
notifyclang::clangd::__anon093857260111::OutgoingRecorder537b83837aSSam McCall   void notify(llvm::StringRef Method, llvm::json::Value Params) override {
547b83837aSSam McCall     Received[Method].push_back(std::move(Params));
557b83837aSSam McCall   }
567b83837aSSam McCall 
takeclang::clangd::__anon093857260111::OutgoingRecorder577b83837aSSam McCall   std::vector<llvm::json::Value> take(llvm::StringRef Method) {
587b83837aSSam McCall     std::vector<llvm::json::Value> Result = Received.lookup(Method);
597b83837aSSam McCall     Received.erase(Method);
607b83837aSSam McCall     return Result;
617b83837aSSam McCall   }
627b83837aSSam McCall };
637b83837aSSam McCall 
TEST(LSPBinderTest,IncomingCalls)645786f64aSSam McCall TEST(LSPBinderTest, IncomingCalls) {
655786f64aSSam McCall   LSPBinder::RawHandlers RawHandlers;
667b83837aSSam McCall   OutgoingRecorder RawOutgoing;
677b83837aSSam McCall   LSPBinder Binder{RawHandlers, RawOutgoing};
685786f64aSSam McCall   struct Handler {
695786f64aSSam McCall     void plusOne(const Foo &Params, Callback<Foo> Reply) {
705786f64aSSam McCall       Reply(Foo{Params.X + 1});
715786f64aSSam McCall     }
725786f64aSSam McCall     void fail(const Foo &Params, Callback<Foo> Reply) {
735786f64aSSam McCall       Reply(error("X={0}", Params.X));
745786f64aSSam McCall     }
755786f64aSSam McCall     void notify(const Foo &Params) {
765786f64aSSam McCall       LastNotify = Params.X;
775786f64aSSam McCall       ++NotifyCount;
785786f64aSSam McCall     }
795786f64aSSam McCall     int LastNotify = -1;
805786f64aSSam McCall     int NotifyCount = 0;
815786f64aSSam McCall   };
825786f64aSSam McCall 
835786f64aSSam McCall   Handler H;
845786f64aSSam McCall   Binder.method("plusOne", &H, &Handler::plusOne);
855786f64aSSam McCall   Binder.method("fail", &H, &Handler::fail);
865786f64aSSam McCall   Binder.notification("notify", &H, &Handler::notify);
875786f64aSSam McCall   Binder.command("cmdPlusOne", &H, &Handler::plusOne);
885786f64aSSam McCall   ASSERT_THAT(RawHandlers.MethodHandlers.keys(),
895786f64aSSam McCall               UnorderedElementsAre("plusOne", "fail"));
905786f64aSSam McCall   ASSERT_THAT(RawHandlers.NotificationHandlers.keys(),
915786f64aSSam McCall               UnorderedElementsAre("notify"));
925786f64aSSam McCall   ASSERT_THAT(RawHandlers.CommandHandlers.keys(),
935786f64aSSam McCall               UnorderedElementsAre("cmdPlusOne"));
94*f71ffd3bSKazu Hirata   std::optional<llvm::Expected<llvm::json::Value>> Reply;
955786f64aSSam McCall 
965786f64aSSam McCall   auto &RawPlusOne = RawHandlers.MethodHandlers["plusOne"];
975786f64aSSam McCall   RawPlusOne(1, capture(Reply));
9853daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
9943fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(2));
1005786f64aSSam McCall   RawPlusOne("foo", capture(Reply));
10153daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
1025786f64aSSam McCall   EXPECT_THAT_EXPECTED(
10343fbbcbfSFangrui Song       *Reply, llvm::FailedWithMessage(HasSubstr(
10443fbbcbfSFangrui Song                   "failed to decode plusOne request: expected integer")));
1055786f64aSSam McCall 
1065786f64aSSam McCall   auto &RawFail = RawHandlers.MethodHandlers["fail"];
1075786f64aSSam McCall   RawFail(2, capture(Reply));
10853daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
10943fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*Reply, llvm::FailedWithMessage("X=2"));
1105786f64aSSam McCall 
1115786f64aSSam McCall   auto &RawNotify = RawHandlers.NotificationHandlers["notify"];
1125786f64aSSam McCall   RawNotify(42);
1135786f64aSSam McCall   EXPECT_EQ(H.LastNotify, 42);
1145786f64aSSam McCall   EXPECT_EQ(H.NotifyCount, 1);
1155786f64aSSam McCall   RawNotify("hi"); // invalid, will be logged
1165786f64aSSam McCall   EXPECT_EQ(H.LastNotify, 42);
1175786f64aSSam McCall   EXPECT_EQ(H.NotifyCount, 1);
1185786f64aSSam McCall 
1195786f64aSSam McCall   auto &RawCmdPlusOne = RawHandlers.CommandHandlers["cmdPlusOne"];
1205786f64aSSam McCall   RawCmdPlusOne(1, capture(Reply));
12153daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
12243fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(2));
1237b83837aSSam McCall 
1247b83837aSSam McCall   // None of this generated any outgoing traffic.
1257b83837aSSam McCall   EXPECT_THAT(RawOutgoing.Received, IsEmpty());
1265786f64aSSam McCall }
1277b83837aSSam McCall 
TEST(LSPBinderTest,OutgoingCalls)1287b83837aSSam McCall TEST(LSPBinderTest, OutgoingCalls) {
1297b83837aSSam McCall   LSPBinder::RawHandlers RawHandlers;
1307b83837aSSam McCall   OutgoingRecorder RawOutgoing;
1317b83837aSSam McCall   LSPBinder Binder{RawHandlers, RawOutgoing};
1327b83837aSSam McCall 
1337b83837aSSam McCall   LSPBinder::OutgoingMethod<Foo, Foo> Echo;
1347b83837aSSam McCall   Echo = Binder.outgoingMethod("echo");
1357b83837aSSam McCall   LSPBinder::OutgoingMethod<Foo, std::string> WrongSignature;
1367b83837aSSam McCall   WrongSignature = Binder.outgoingMethod("wrongSignature");
1377b83837aSSam McCall   LSPBinder::OutgoingMethod<Foo, Foo> Fail;
1387b83837aSSam McCall   Fail = Binder.outgoingMethod("fail");
1397b83837aSSam McCall 
140*f71ffd3bSKazu Hirata   std::optional<llvm::Expected<Foo>> Reply;
1417b83837aSSam McCall   Echo(Foo{2}, capture(Reply));
1427b83837aSSam McCall   EXPECT_THAT(RawOutgoing.take("echo"), ElementsAre(llvm::json::Value(2)));
14353daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
14443fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*Reply, llvm::HasValue(Foo{2}));
1457b83837aSSam McCall 
1467b83837aSSam McCall   // JSON response is integer, can't be parsed as string.
147*f71ffd3bSKazu Hirata   std::optional<llvm::Expected<std::string>> WrongTypeReply;
1487b83837aSSam McCall   WrongSignature(Foo{2}, capture(WrongTypeReply));
1497b83837aSSam McCall   EXPECT_THAT(RawOutgoing.take("wrongSignature"),
1507b83837aSSam McCall               ElementsAre(llvm::json::Value(2)));
15153daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
15243fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*WrongTypeReply,
1537b83837aSSam McCall                        llvm::FailedWithMessage(
1547b83837aSSam McCall                            HasSubstr("failed to decode wrongSignature reply")));
1557b83837aSSam McCall 
1567b83837aSSam McCall   Fail(Foo{2}, capture(Reply));
1577b83837aSSam McCall   EXPECT_THAT(RawOutgoing.take("fail"), ElementsAre(llvm::json::Value(2)));
15853daa177SKazu Hirata   ASSERT_TRUE(Reply.has_value());
15943fbbcbfSFangrui Song   EXPECT_THAT_EXPECTED(*Reply, llvm::FailedWithMessage("Params=2"));
1607b83837aSSam McCall }
1617b83837aSSam McCall 
1625786f64aSSam McCall } // namespace
1635786f64aSSam McCall } // namespace clangd
1645786f64aSSam McCall } // namespace clang
165