clang-tools  16.0.0git
JSONTransportTests.cpp
Go to the documentation of this file.
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-exception
6 //
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 
15 namespace clang {
16 namespace clangd {
17 namespace {
18 
19 // No fmemopen on windows or on versions of MacOS X earlier than 10.13, so we
20 // 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.
25 class 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 
30 protected:
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 it
49 // - when it gets a notification for method "call", makes a call on Target
50 // Hangs up when it gets an exit notification.
51 class Echo : public Transport::MessageHandler {
52  Transport &Target;
53  std::string LogBuf;
54  llvm::raw_string_ostream Log;
55 
56 public:
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 treadle
75  Target.reply(ID, llvm::make_error<CancelledError>(
76  static_cast<int>(ErrorCode::ContentModified)));
77  else
78  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  else
87  Log << "Reply(" << ID
88  << "): error = " << llvm::toString(Params.takeError()) << "\n";
89  return true;
90  }
91 };
92 
93 std::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.
96 TEST_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"(
116 Notification call: 1234
117 Reply(1234): 5678
118 Call foo("abcd"): "efgh"
119 Reply("xyz"): error = 99: bad!
120 Call err("wxyz"): "boom!"
121 Notification exit: null
122  )";
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 output
136 // that we use in lit tests.
137 TEST_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"(
157 Notification call: 1234
158 Reply(1234): 5678
159 Call foo("abcd"): "efgh"
160 Reply("xyz"): error = 99: bad!
161 Call invalidated("wxyz"): "boom!"
162 Notification exit: null
163  )";
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": 1234
171 })"
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.
193 TEST_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 #endif
206 
207 } // namespace
208 } // namespace clangd
209 } // namespace clang
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::clangd::TEST_F
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
Definition: BackgroundIndexTests.cpp:94
clang::clangd::ErrorCode
ErrorCode
Definition: Protocol.h:47
Target
std::string Target
Definition: QueryDriverDatabase.cpp:64
Cancellation.h
Protocol.h
clang::clangd::JSONStreamStyle
JSONStreamStyle
Definition: Transport.h:66
Args
llvm::json::Object Args
Definition: Trace.cpp:138
clang::clangd::toString
static const char * toString(OffsetEncoding OE)
Definition: Protocol.cpp:1393
clang::clangd::Standard
@ Standard
Definition: Transport.h:68
clang::clangd::CompletionItemKind::Method
@ Method
clang::clangd::log
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:67
Transport.h
ID
static char ID
Definition: Logger.cpp:74
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::newJSONTransport
std::unique_ptr< Transport > newJSONTransport(std::FILE *In, llvm::raw_ostream &Out, llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)
Definition: JSONTransport.cpp:327
clang::tidy::cppcoreguidelines::toString
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Definition: SpecialMemberFunctionsCheck.cpp:55
clang::clangd::Delimited
@ Delimited
Definition: Transport.h:70
Out
CompiledFragmentImpl & Out
Definition: ConfigCompile.cpp:99
clang::clangd::ErrorCode::ContentModified
@ ContentModified
Value
static constexpr bool Value
Definition: SuspiciousCallArgumentCheck.cpp:72