summaryrefslogtreecommitdiffstats
path: root/clangd/unittests/JSONTransportTests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/unittests/JSONTransportTests.cpp')
-rw-r--r--clangd/unittests/JSONTransportTests.cpp205
1 files changed, 205 insertions, 0 deletions
diff --git a/clangd/unittests/JSONTransportTests.cpp b/clangd/unittests/JSONTransportTests.cpp
new file mode 100644
index 00000000..3f71a10c
--- /dev/null
+++ b/clangd/unittests/JSONTransportTests.cpp
@@ -0,0 +1,205 @@
+//===-- JSONTransportTests.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "Protocol.h"
+#include "Transport.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cstdio>
+
+namespace clang {
+namespace clangd {
+namespace {
+
+// No fmemopen on windows or on versions of MacOS X earlier than 10.13, so we
+// can't easily run this test.
+#if !(defined(_WIN32) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
+ __MAC_OS_X_VERSION_MIN_REQUIRED < 101300))
+
+// Fixture takes care of managing the input/output buffers for the transport.
+class JSONTransportTest : public ::testing::Test {
+ std::string InBuf, OutBuf, MirrorBuf;
+ llvm::raw_string_ostream Out, Mirror;
+ std::unique_ptr<FILE, int (*)(FILE *)> In;
+
+protected:
+ JSONTransportTest() : Out(OutBuf), Mirror(MirrorBuf), In(nullptr, nullptr) {}
+
+ template <typename... Args>
+ std::unique_ptr<Transport> transport(std::string InData, bool Pretty,
+ JSONStreamStyle Style) {
+ InBuf = std::move(InData);
+ In = {fmemopen(&InBuf[0], InBuf.size(), "r"), &fclose};
+ return newJSONTransport(In.get(), Out, &Mirror, Pretty, Style);
+ }
+
+ std::string input() const { return InBuf; }
+ std::string output() { return Out.str(); }
+ std::string input_mirror() { return Mirror.str(); }
+};
+
+// Echo is a simple server running on a transport:
+// - logs each message it gets.
+// - when it gets a call, replies to it
+// - when it gets a notification for method "call", makes a call on Target
+// Hangs up when it gets an exit notification.
+class Echo : public Transport::MessageHandler {
+ Transport &Target;
+ std::string LogBuf;
+ llvm::raw_string_ostream Log;
+
+public:
+ Echo(Transport &Target) : Target(Target), Log(LogBuf) {}
+
+ std::string log() { return Log.str(); }
+
+ bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
+ Log << "Notification " << Method << ": " << Params << "\n";
+ if (Method == "call")
+ Target.call("echo call", std::move(Params), 42);
+ return Method != "exit";
+ }
+
+ bool onCall(llvm::StringRef Method, llvm::json::Value Params,
+ llvm::json::Value ID) override {
+ Log << "Call " << Method << "(" << ID << "): " << Params << "\n";
+ if (Method == "err")
+ Target.reply(
+ ID, llvm::make_error<LSPError>("trouble at mill", ErrorCode(88)));
+ else
+ Target.reply(ID, std::move(Params));
+ return true;
+ }
+
+ bool onReply(llvm::json::Value ID,
+ llvm::Expected<llvm::json::Value> Params) override {
+ if (Params)
+ Log << "Reply(" << ID << "): " << *Params << "\n";
+ else
+ Log << "Reply(" << ID
+ << "): error = " << llvm::toString(Params.takeError()) << "\n";
+ return true;
+ }
+};
+
+std::string trim(llvm::StringRef S) { return S.trim().str(); }
+
+// Runs an Echo session using the standard JSON-RPC format we use in production.
+TEST_F(JSONTransportTest, StandardDense) {
+ auto T = transport(
+ "Content-Length: 52\r\n\r\n"
+ R"({"jsonrpc": "2.0", "method": "call", "params": 1234})"
+ "Content-Length: 46\r\n\r\n"
+ R"({"jsonrpc": "2.0", "id": 1234, "result": 5678})"
+ "Content-Length: 67\r\n\r\n"
+ R"({"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"})"
+ "Content-Length: 73\r\n\r\n"
+ R"({"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}})"
+ "Content-Length: 68\r\n\r\n"
+ R"({"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"})"
+ "Content-Length: 36\r\n\r\n"
+ R"({"jsonrpc": "2.0", "method": "exit"})",
+ /*Pretty=*/false, JSONStreamStyle::Standard);
+ Echo E(*T);
+ auto Err = T->loop(E);
+ EXPECT_FALSE(bool(Err)) << toString(std::move(Err));
+
+ const char *WantLog = R"(
+Notification call: 1234
+Reply(1234): 5678
+Call foo("abcd"): "efgh"
+Reply("xyz"): error = 99: bad!
+Call err("wxyz"): "boom!"
+Notification exit: null
+ )";
+ EXPECT_EQ(trim(E.log()), trim(WantLog));
+ const char *WantOutput =
+ "Content-Length: 60\r\n\r\n"
+ R"({"id":42,"jsonrpc":"2.0","method":"echo call","params":1234})"
+ "Content-Length: 45\r\n\r\n"
+ R"({"id":"abcd","jsonrpc":"2.0","result":"efgh"})"
+ "Content-Length: 77\r\n\r\n"
+ R"({"error":{"code":88,"message":"trouble at mill"},"id":"wxyz","jsonrpc":"2.0"})";
+ EXPECT_EQ(output(), WantOutput);
+ EXPECT_EQ(trim(input_mirror()), trim(input()));
+}
+
+// Runs an Echo session using the "delimited" input and pretty-printed output
+// that we use in lit tests.
+TEST_F(JSONTransportTest, DelimitedPretty) {
+ auto T = transport(R"jsonrpc(
+{"jsonrpc": "2.0", "method": "call", "params": 1234}
+---
+{"jsonrpc": "2.0", "id": 1234, "result": 5678}
+---
+{"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"}
+---
+{"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}}
+---
+{"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"}
+---
+{"jsonrpc": "2.0", "method": "exit"}
+ )jsonrpc",
+ /*Pretty=*/true, JSONStreamStyle::Delimited);
+ Echo E(*T);
+ auto Err = T->loop(E);
+ EXPECT_FALSE(bool(Err)) << toString(std::move(Err));
+
+ const char *WantLog = R"(
+Notification call: 1234
+Reply(1234): 5678
+Call foo("abcd"): "efgh"
+Reply("xyz"): error = 99: bad!
+Call err("wxyz"): "boom!"
+Notification exit: null
+ )";
+ EXPECT_EQ(trim(E.log()), trim(WantLog));
+ const char *WantOutput = "Content-Length: 77\r\n\r\n"
+ R"({
+ "id": 42,
+ "jsonrpc": "2.0",
+ "method": "echo call",
+ "params": 1234
+})"
+ "Content-Length: 58\r\n\r\n"
+ R"({
+ "id": "abcd",
+ "jsonrpc": "2.0",
+ "result": "efgh"
+})"
+ "Content-Length: 105\r\n\r\n"
+ R"({
+ "error": {
+ "code": 88,
+ "message": "trouble at mill"
+ },
+ "id": "wxyz",
+ "jsonrpc": "2.0"
+})";
+ EXPECT_EQ(output(), WantOutput);
+ EXPECT_EQ(trim(input_mirror()), trim(input()));
+}
+
+// IO errors such as EOF ane reported.
+// The only successful return from loop() is if a handler returned false.
+TEST_F(JSONTransportTest, EndOfFile) {
+ auto T = transport("Content-Length: 52\r\n\r\n"
+ R"({"jsonrpc": "2.0", "method": "call", "params": 1234})",
+ /*Pretty=*/false, JSONStreamStyle::Standard);
+ Echo E(*T);
+ auto Err = T->loop(E);
+ EXPECT_EQ(trim(E.log()), "Notification call: 1234");
+ EXPECT_TRUE(bool(Err)); // Ran into EOF with no handler signalling done.
+ consumeError(std::move(Err));
+ EXPECT_EQ(trim(input_mirror()), trim(input()));
+}
+
+#endif
+
+} // namespace
+} // namespace clangd
+} // namespace clang