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