brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.5 KiB · 055a627 Raw
206 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Support/LSP/Transport.h"10#include "llvm/Support/FileSystem.h"11#include "llvm/Support/LSP/Logging.h"12#include "llvm/Support/LSP/Protocol.h"13#include "gmock/gmock.h"14#include "gtest/gtest.h"15 16using namespace llvm;17using namespace llvm::lsp;18using namespace testing;19 20namespace {21 22TEST(TransportTest, SendReply) {23  std::string out;24  llvm::raw_string_ostream os(out);25  JSONTransport transport(nullptr, os);26  MessageHandler handler(transport);27 28  transport.reply(1989, nullptr);29  EXPECT_THAT(out, HasSubstr("\"id\":1989"));30  EXPECT_THAT(out, HasSubstr("\"result\":null"));31}32 33class 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;40 41protected:42  TransportInputTest() : os(output) {}43 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();48 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);54  }55 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();63  }64 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();72  }73 74  StringRef getOutput() const { return output; }75  MessageHandler &getMessageHandler() { return *messageHandler; }76 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);85  }86};87 88TEST_F(TransportInputTest, RequestWithInvalidParams) {89  struct Handler {90    void onMethod(const TextDocumentItem &params,91                  llvm::lsp::Callback<TextDocumentIdentifier> callback) {}92  } handler;93  getMessageHandler().method("invalid-params-request", &handler,94                             &Handler::onMethod);95 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"));101}102 103TEST_F(TransportInputTest, NotificationWithInvalidParams) {104  // JSON parsing errors are only reported via error logging. As a result, this105  // test can't make any expectations -- but it prints the output anyway, by way106  // of demonstration.107  Logger::setLogLevel(Logger::Level::Error);108 109  struct Handler {110    void onNotification(const TextDocumentItem &params) {}111  } handler;112  getMessageHandler().notification("invalid-params-notification", &handler,113                                   &Handler::onNotification);114 115  writeInput("{\"jsonrpc\":\"2.0\",\"method\":\"invalid-params-notification\","116             "\"params\":{}}\n");117  runTransport();118}119 120TEST_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\""));126}127 128TEST_F(TransportInputTest, OutgoingNotification) {129  auto notifyFn = getMessageHandler().outgoingNotification<CompletionList>(130      "outgoing-notification");131  notifyFn(CompletionList{});132  EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-notification\""));133}134 135TEST_F(TransportInputTest, ResponseHandlerNotFound) {136  // Unhandled responses are only reported via error logging. As a result, this137  // test can't make any expectations -- but it prints the output anyway, by way138  // of demonstration.139  Logger::setLogLevel(Logger::Level::Error);140  writeInput("{\"jsonrpc\":\"2.0\",\"id\":81,\"result\":null}\n");141  runTransport();142}143 144TEST_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](const 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;157          });158  callFn({}, 82);159  callFn({}, 83);160  callFn({}, 84);161  EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-request\""));162  EXPECT_EQ(responseCallbackInvoked, 0);163 164  // One of the requests receives a response. The message handler handles this165  // response by invoking the callback from above. Subsequent responses with the166  // 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);173}174 175TEST_F(TransportInputTest, OutgoingRequestJSONParseFailure) {176  // Make an outgoing request that expects a failure response.177  unsigned responseCallbackInvoked = 0;178  auto callFn = getMessageHandler().outgoingRequest<CompletionList, Position>(179      "outgoing-request-json-parse-failure",180      [&responseCallbackInvoked](const 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;191      });192  callFn({}, 109);193  EXPECT_EQ(responseCallbackInvoked, 0u);194 195  // The request receives multiple responses, but only the first one triggers196  // the response callback. The first response has erroneous JSON that causes a197  // 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, 1u);204}205} // namespace206