[LLVM] Fix Maintainers.md formatting (NFC)
[llvm-project.git] / mlir / unittests / Tools / lsp-server-support / Transport.cpp
blob0303c1cba8bc87a4cddab19fbe033f3b73efd4d7
1 //===- Transport.cpp - LSP JSON transport unit tests ----------------------===//
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 //===----------------------------------------------------------------------===//
9 #include "mlir/Tools/lsp-server-support/Transport.h"
10 #include "mlir/Tools/lsp-server-support/Logging.h"
11 #include "mlir/Tools/lsp-server-support/Protocol.h"
12 #include "llvm/Support/FileSystem.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
16 using namespace mlir;
17 using namespace mlir::lsp;
18 using namespace testing;
20 namespace {
22 TEST(TransportTest, SendReply) {
23 std::string out;
24 llvm::raw_string_ostream os(out);
25 JSONTransport transport(nullptr, os);
26 MessageHandler handler(transport);
28 transport.reply(1989, nullptr);
29 EXPECT_THAT(out, HasSubstr("\"id\":1989"));
30 EXPECT_THAT(out, HasSubstr("\"result\":null"));
33 class TransportInputTest : public Test {
34 llvm::SmallVector<char> inputPath;
35 std::FILE *in = nullptr;
36 std::string output = "";
37 llvm::raw_string_ostream os;
38 std::optional<JSONTransport> transport = std::nullopt;
39 std::optional<MessageHandler> messageHandler = std::nullopt;
41 protected:
42 TransportInputTest() : os(output) {}
44 void SetUp() override {
45 std::error_code ec =
46 llvm::sys::fs::createTemporaryFile("lsp-unittest", "json", inputPath);
47 ASSERT_FALSE(ec) << "Could not create temporary file: " << ec.message();
49 in = std::fopen(inputPath.data(), "r");
50 ASSERT_TRUE(in) << "Could not open temporary file: "
51 << std::strerror(errno);
52 transport.emplace(in, os, JSONStreamStyle::Delimited);
53 messageHandler.emplace(*transport);
56 void TearDown() override {
57 EXPECT_EQ(std::fclose(in), 0)
58 << "Could not close temporary file FD: " << std::strerror(errno);
59 std::error_code ec =
60 llvm::sys::fs::remove(inputPath, /*IgnoreNonExisting=*/false);
61 EXPECT_FALSE(ec) << "Could not remove temporary file '" << inputPath.data()
62 << "': " << ec.message();
65 void writeInput(StringRef buffer) {
66 std::error_code ec;
67 llvm::raw_fd_ostream os(inputPath.data(), ec);
68 ASSERT_FALSE(ec) << "Could not write to '" << inputPath.data()
69 << "': " << ec.message();
70 os << buffer;
71 os.close();
74 StringRef getOutput() const { return output; }
75 MessageHandler &getMessageHandler() { return *messageHandler; }
77 void runTransport() {
78 bool gotEOF = false;
79 llvm::Error err = llvm::handleErrors(
80 transport->run(*messageHandler), [&](const llvm::ECError &ecErr) {
81 gotEOF = ecErr.convertToErrorCode() == std::errc::io_error;
82 });
83 llvm::consumeError(std::move(err));
84 EXPECT_TRUE(gotEOF);
88 TEST_F(TransportInputTest, RequestWithInvalidParams) {
89 struct Handler {
90 void onMethod(const TextDocumentItem &params,
91 mlir::lsp::Callback<TextDocumentIdentifier> callback) {}
92 } handler;
93 getMessageHandler().method("invalid-params-request", &handler,
94 &Handler::onMethod);
96 writeInput("{\"jsonrpc\":\"2.0\",\"id\":92,"
97 "\"method\":\"invalid-params-request\",\"params\":{}}\n");
98 runTransport();
99 EXPECT_THAT(getOutput(), HasSubstr("error"));
100 EXPECT_THAT(getOutput(), HasSubstr("missing value at (root).uri"));
103 TEST_F(TransportInputTest, NotificationWithInvalidParams) {
104 // JSON parsing errors are only reported via error logging. As a result, this
105 // test can't make any expectations -- but it prints the output anyway, by way
106 // of demonstration.
107 Logger::setLogLevel(Logger::Level::Error);
109 struct Handler {
110 void onNotification(const TextDocumentItem &params) {}
111 } handler;
112 getMessageHandler().notification("invalid-params-notification", &handler,
113 &Handler::onNotification);
115 writeInput("{\"jsonrpc\":\"2.0\",\"method\":\"invalid-params-notification\","
116 "\"params\":{}}\n");
117 runTransport();
120 TEST_F(TransportInputTest, MethodNotFound) {
121 writeInput("{\"jsonrpc\":\"2.0\",\"id\":29,\"method\":\"ack\"}\n");
122 runTransport();
123 EXPECT_THAT(getOutput(), HasSubstr("\"id\":29"));
124 EXPECT_THAT(getOutput(), HasSubstr("\"error\""));
125 EXPECT_THAT(getOutput(), HasSubstr("\"message\":\"method not found: ack\""));
128 TEST_F(TransportInputTest, OutgoingNotification) {
129 auto notifyFn = getMessageHandler().outgoingNotification<CompletionList>(
130 "outgoing-notification");
131 notifyFn(CompletionList{});
132 EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-notification\""));
135 TEST_F(TransportInputTest, ResponseHandlerNotFound) {
136 // Unhandled responses are only reported via error logging. As a result, this
137 // test can't make any expectations -- but it prints the output anyway, by way
138 // of demonstration.
139 Logger::setLogLevel(Logger::Level::Error);
140 writeInput("{\"jsonrpc\":\"2.0\",\"id\":81,\"result\":null}\n");
141 runTransport();
144 TEST_F(TransportInputTest, OutgoingRequest) {
145 // Make some outgoing requests.
146 int responseCallbackInvoked = 0;
147 auto callFn =
148 getMessageHandler().outgoingRequest<CompletionList, CompletionContext>(
149 "outgoing-request",
150 [&responseCallbackInvoked](llvm::json::Value id,
151 llvm::Expected<CompletionContext> result) {
152 // Make expectations on the expected response.
153 EXPECT_EQ(id, 83);
154 ASSERT_TRUE((bool)result);
155 EXPECT_EQ(result->triggerKind, CompletionTriggerKind::Invoked);
156 responseCallbackInvoked += 1;
158 callFn({}, 82);
159 callFn({}, 83);
160 callFn({}, 84);
161 EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-request\""));
162 EXPECT_EQ(responseCallbackInvoked, 0);
164 // One of the requests receives a response. The message handler handles this
165 // response by invoking the callback from above. Subsequent responses with the
166 // same ID are ignored.
167 writeInput(
168 "{\"jsonrpc\":\"2.0\",\"id\":83,\"result\":{\"triggerKind\":1}}\n"
169 "// -----\n"
170 "{\"jsonrpc\":\"2.0\",\"id\":83,\"result\":{\"triggerKind\":3}}\n");
171 runTransport();
172 EXPECT_EQ(responseCallbackInvoked, 1);
175 TEST_F(TransportInputTest, OutgoingRequestJSONParseFailure) {
176 // Make an outgoing request that expects a failure response.
177 bool responseCallbackInvoked = 0;
178 auto callFn = getMessageHandler().outgoingRequest<CompletionList, Position>(
179 "outgoing-request-json-parse-failure",
180 [&responseCallbackInvoked](llvm::json::Value id,
181 llvm::Expected<Position> result) {
182 llvm::Error err = result.takeError();
183 EXPECT_EQ(id, 109);
184 ASSERT_TRUE((bool)err);
185 EXPECT_THAT(debugString(err),
186 HasSubstr("failed to decode "
187 "reply:outgoing-request-json-parse-failure(109) "
188 "response: missing value at (root).character"));
189 llvm::consumeError(std::move(err));
190 responseCallbackInvoked += 1;
192 callFn({}, 109);
193 EXPECT_EQ(responseCallbackInvoked, 0);
195 // The request receives multiple responses, but only the first one triggers
196 // the response callback. The first response has erroneous JSON that causes a
197 // parse failure.
198 writeInput("{\"jsonrpc\":\"2.0\",\"id\":109,\"result\":{\"line\":7}}\n"
199 "// -----\n"
200 "{\"jsonrpc\":\"2.0\",\"id\":109,\"result\":{\"line\":3,"
201 "\"character\":2}}\n");
202 runTransport();
203 EXPECT_EQ(responseCallbackInvoked, 1);
205 } // namespace