1b804eef0SSam McCall //===-- JSONTransportTests.cpp -------------------------------------------===//
2b804eef0SSam McCall //
3b804eef0SSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4b804eef0SSam McCall // See https://llvm.org/LICENSE.txt for license information.
5b804eef0SSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b804eef0SSam McCall //
7b804eef0SSam McCall //===----------------------------------------------------------------------===//
8b804eef0SSam McCall #include "Protocol.h"
9b804eef0SSam McCall #include "Transport.h"
10ad97ccf6SSam McCall #include "support/Cancellation.h"
11b804eef0SSam McCall #include "gmock/gmock.h"
12b804eef0SSam McCall #include "gtest/gtest.h"
13b804eef0SSam McCall #include <cstdio>
14b804eef0SSam McCall
15b804eef0SSam McCall namespace clang {
16b804eef0SSam McCall namespace clangd {
17b804eef0SSam McCall namespace {
18b804eef0SSam McCall
19b804eef0SSam McCall // No fmemopen on windows or on versions of MacOS X earlier than 10.13, so we
20b804eef0SSam McCall // can't easily run this test.
21e6126fabSLouis Dionne #if !(defined(_WIN32) || (defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
22e6126fabSLouis Dionne __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101300))
23b804eef0SSam McCall
24b804eef0SSam McCall // Fixture takes care of managing the input/output buffers for the transport.
25b804eef0SSam McCall class JSONTransportTest : public ::testing::Test {
26b804eef0SSam McCall std::string InBuf, OutBuf, MirrorBuf;
27b804eef0SSam McCall llvm::raw_string_ostream Out, Mirror;
28b804eef0SSam McCall std::unique_ptr<FILE, int (*)(FILE *)> In;
29b804eef0SSam McCall
30b804eef0SSam McCall protected:
JSONTransportTest()31b804eef0SSam McCall JSONTransportTest() : Out(OutBuf), Mirror(MirrorBuf), In(nullptr, nullptr) {}
32b804eef0SSam McCall
33b804eef0SSam McCall template <typename... Args>
transport(std::string InData,bool Pretty,JSONStreamStyle Style)34b804eef0SSam McCall std::unique_ptr<Transport> transport(std::string InData, bool Pretty,
35b804eef0SSam McCall JSONStreamStyle Style) {
36b804eef0SSam McCall InBuf = std::move(InData);
37b804eef0SSam McCall In = {fmemopen(&InBuf[0], InBuf.size(), "r"), &fclose};
38b804eef0SSam McCall return newJSONTransport(In.get(), Out, &Mirror, Pretty, Style);
39b804eef0SSam McCall }
40b804eef0SSam McCall
input() const41b804eef0SSam McCall std::string input() const { return InBuf; }
output()42b804eef0SSam McCall std::string output() { return Out.str(); }
inputMirror()43*8edfc2f8SChristian Kühnel std::string inputMirror() { return Mirror.str(); }
44b804eef0SSam McCall };
45b804eef0SSam McCall
46b804eef0SSam McCall // Echo is a simple server running on a transport:
47b804eef0SSam McCall // - logs each message it gets.
48b804eef0SSam McCall // - when it gets a call, replies to it
49b804eef0SSam McCall // - when it gets a notification for method "call", makes a call on Target
50b804eef0SSam McCall // Hangs up when it gets an exit notification.
51b804eef0SSam McCall class Echo : public Transport::MessageHandler {
52b804eef0SSam McCall Transport &Target;
53b804eef0SSam McCall std::string LogBuf;
54b804eef0SSam McCall llvm::raw_string_ostream Log;
55b804eef0SSam McCall
56b804eef0SSam McCall public:
Echo(Transport & Target)57b804eef0SSam McCall Echo(Transport &Target) : Target(Target), Log(LogBuf) {}
58b804eef0SSam McCall
log()59b804eef0SSam McCall std::string log() { return Log.str(); }
60b804eef0SSam McCall
onNotify(llvm::StringRef Method,llvm::json::Value Params)61b804eef0SSam McCall bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
62b804eef0SSam McCall Log << "Notification " << Method << ": " << Params << "\n";
63b804eef0SSam McCall if (Method == "call")
64b804eef0SSam McCall Target.call("echo call", std::move(Params), 42);
65b804eef0SSam McCall return Method != "exit";
66b804eef0SSam McCall }
67b804eef0SSam McCall
onCall(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)68b804eef0SSam McCall bool onCall(llvm::StringRef Method, llvm::json::Value Params,
69b804eef0SSam McCall llvm::json::Value ID) override {
70b804eef0SSam McCall Log << "Call " << Method << "(" << ID << "): " << Params << "\n";
71b804eef0SSam McCall if (Method == "err")
72b804eef0SSam McCall Target.reply(
73b804eef0SSam McCall ID, llvm::make_error<LSPError>("trouble at mill", ErrorCode(88)));
7431db1e0bSSam McCall else if (Method == "invalidated") // gone out skew on treadle
7531db1e0bSSam McCall Target.reply(ID, llvm::make_error<CancelledError>(
7631db1e0bSSam McCall static_cast<int>(ErrorCode::ContentModified)));
77b804eef0SSam McCall else
78b804eef0SSam McCall Target.reply(ID, std::move(Params));
79b804eef0SSam McCall return true;
80b804eef0SSam McCall }
81b804eef0SSam McCall
onReply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Params)82b804eef0SSam McCall bool onReply(llvm::json::Value ID,
83b804eef0SSam McCall llvm::Expected<llvm::json::Value> Params) override {
84b804eef0SSam McCall if (Params)
85b804eef0SSam McCall Log << "Reply(" << ID << "): " << *Params << "\n";
86b804eef0SSam McCall else
87b804eef0SSam McCall Log << "Reply(" << ID
88b804eef0SSam McCall << "): error = " << llvm::toString(Params.takeError()) << "\n";
89b804eef0SSam McCall return true;
90b804eef0SSam McCall }
91b804eef0SSam McCall };
92b804eef0SSam McCall
trim(llvm::StringRef S)93b804eef0SSam McCall std::string trim(llvm::StringRef S) { return S.trim().str(); }
94b804eef0SSam McCall
95b804eef0SSam McCall // Runs an Echo session using the standard JSON-RPC format we use in production.
TEST_F(JSONTransportTest,StandardDense)96b804eef0SSam McCall TEST_F(JSONTransportTest, StandardDense) {
97b804eef0SSam McCall auto T = transport(
98b804eef0SSam McCall "Content-Length: 52\r\n\r\n"
99b804eef0SSam McCall R"({"jsonrpc": "2.0", "method": "call", "params": 1234})"
100b804eef0SSam McCall "Content-Length: 46\r\n\r\n"
101b804eef0SSam McCall R"({"jsonrpc": "2.0", "id": 1234, "result": 5678})"
102b804eef0SSam McCall "Content-Length: 67\r\n\r\n"
103b804eef0SSam McCall R"({"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"})"
104b804eef0SSam McCall "Content-Length: 73\r\n\r\n"
105b804eef0SSam McCall R"({"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}})"
106b804eef0SSam McCall "Content-Length: 68\r\n\r\n"
107b804eef0SSam McCall R"({"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"})"
108b804eef0SSam McCall "Content-Length: 36\r\n\r\n"
109b804eef0SSam McCall R"({"jsonrpc": "2.0", "method": "exit"})",
110b804eef0SSam McCall /*Pretty=*/false, JSONStreamStyle::Standard);
111b804eef0SSam McCall Echo E(*T);
112b804eef0SSam McCall auto Err = T->loop(E);
113b804eef0SSam McCall EXPECT_FALSE(bool(Err)) << toString(std::move(Err));
114b804eef0SSam McCall
115b804eef0SSam McCall const char *WantLog = R"(
116b804eef0SSam McCall Notification call: 1234
117b804eef0SSam McCall Reply(1234): 5678
118b804eef0SSam McCall Call foo("abcd"): "efgh"
119b804eef0SSam McCall Reply("xyz"): error = 99: bad!
120b804eef0SSam McCall Call err("wxyz"): "boom!"
121b804eef0SSam McCall Notification exit: null
122b804eef0SSam McCall )";
123b804eef0SSam McCall EXPECT_EQ(trim(E.log()), trim(WantLog));
124b804eef0SSam McCall const char *WantOutput =
125b804eef0SSam McCall "Content-Length: 60\r\n\r\n"
126b804eef0SSam McCall R"({"id":42,"jsonrpc":"2.0","method":"echo call","params":1234})"
127b804eef0SSam McCall "Content-Length: 45\r\n\r\n"
128b804eef0SSam McCall R"({"id":"abcd","jsonrpc":"2.0","result":"efgh"})"
129b804eef0SSam McCall "Content-Length: 77\r\n\r\n"
130b804eef0SSam McCall R"({"error":{"code":88,"message":"trouble at mill"},"id":"wxyz","jsonrpc":"2.0"})";
131b804eef0SSam McCall EXPECT_EQ(output(), WantOutput);
132*8edfc2f8SChristian Kühnel EXPECT_EQ(trim(inputMirror()), trim(input()));
133b804eef0SSam McCall }
134b804eef0SSam McCall
135b804eef0SSam McCall // Runs an Echo session using the "delimited" input and pretty-printed output
136b804eef0SSam McCall // that we use in lit tests.
TEST_F(JSONTransportTest,DelimitedPretty)137b804eef0SSam McCall TEST_F(JSONTransportTest, DelimitedPretty) {
138b804eef0SSam McCall auto T = transport(R"jsonrpc(
139b804eef0SSam McCall {"jsonrpc": "2.0", "method": "call", "params": 1234}
140b804eef0SSam McCall ---
141b804eef0SSam McCall {"jsonrpc": "2.0", "id": 1234, "result": 5678}
142b804eef0SSam McCall ---
143b804eef0SSam McCall {"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"}
144b804eef0SSam McCall ---
145b804eef0SSam McCall {"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}}
146b804eef0SSam McCall ---
14731db1e0bSSam McCall {"jsonrpc": "2.0", "method": "invalidated", "id": "wxyz", "params": "boom!"}
148b804eef0SSam McCall ---
149b804eef0SSam McCall {"jsonrpc": "2.0", "method": "exit"}
150b804eef0SSam McCall )jsonrpc",
151b804eef0SSam McCall /*Pretty=*/true, JSONStreamStyle::Delimited);
152b804eef0SSam McCall Echo E(*T);
153b804eef0SSam McCall auto Err = T->loop(E);
154b804eef0SSam McCall EXPECT_FALSE(bool(Err)) << toString(std::move(Err));
155b804eef0SSam McCall
156b804eef0SSam McCall const char *WantLog = R"(
157b804eef0SSam McCall Notification call: 1234
158b804eef0SSam McCall Reply(1234): 5678
159b804eef0SSam McCall Call foo("abcd"): "efgh"
160b804eef0SSam McCall Reply("xyz"): error = 99: bad!
16131db1e0bSSam McCall Call invalidated("wxyz"): "boom!"
162b804eef0SSam McCall Notification exit: null
163b804eef0SSam McCall )";
164b804eef0SSam McCall EXPECT_EQ(trim(E.log()), trim(WantLog));
165b804eef0SSam McCall const char *WantOutput = "Content-Length: 77\r\n\r\n"
166b804eef0SSam McCall R"({
167b804eef0SSam McCall "id": 42,
168b804eef0SSam McCall "jsonrpc": "2.0",
169b804eef0SSam McCall "method": "echo call",
170b804eef0SSam McCall "params": 1234
171b804eef0SSam McCall })"
172b804eef0SSam McCall "Content-Length: 58\r\n\r\n"
173b804eef0SSam McCall R"({
174b804eef0SSam McCall "id": "abcd",
175b804eef0SSam McCall "jsonrpc": "2.0",
176b804eef0SSam McCall "result": "efgh"
177b804eef0SSam McCall })"
17831db1e0bSSam McCall "Content-Length: 145\r\n\r\n"
179b804eef0SSam McCall R"({
180b804eef0SSam McCall "error": {
18131db1e0bSSam McCall "code": -32801,
18231db1e0bSSam McCall "message": "Request cancelled because the document was modified"
183b804eef0SSam McCall },
184b804eef0SSam McCall "id": "wxyz",
185b804eef0SSam McCall "jsonrpc": "2.0"
186b804eef0SSam McCall })";
187b804eef0SSam McCall EXPECT_EQ(output(), WantOutput);
188*8edfc2f8SChristian Kühnel EXPECT_EQ(trim(inputMirror()), trim(input()));
189b804eef0SSam McCall }
190b804eef0SSam McCall
191b804eef0SSam McCall // IO errors such as EOF ane reported.
192b804eef0SSam McCall // The only successful return from loop() is if a handler returned false.
TEST_F(JSONTransportTest,EndOfFile)193b804eef0SSam McCall TEST_F(JSONTransportTest, EndOfFile) {
194b804eef0SSam McCall auto T = transport("Content-Length: 52\r\n\r\n"
195b804eef0SSam McCall R"({"jsonrpc": "2.0", "method": "call", "params": 1234})",
196b804eef0SSam McCall /*Pretty=*/false, JSONStreamStyle::Standard);
197b804eef0SSam McCall Echo E(*T);
198b804eef0SSam McCall auto Err = T->loop(E);
199b804eef0SSam McCall EXPECT_EQ(trim(E.log()), "Notification call: 1234");
200b804eef0SSam McCall EXPECT_TRUE(bool(Err)); // Ran into EOF with no handler signalling done.
201b804eef0SSam McCall consumeError(std::move(Err));
202*8edfc2f8SChristian Kühnel EXPECT_EQ(trim(inputMirror()), trim(input()));
203b804eef0SSam McCall }
204b804eef0SSam McCall
205b804eef0SSam McCall #endif
206b804eef0SSam McCall
207b804eef0SSam McCall } // namespace
208b804eef0SSam McCall } // namespace clangd
209b804eef0SSam McCall } // namespace clang
210