fold/tests/integration/test_lsp_framed.cpp

131 lines
4.6 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <deque>
#include <filesystem>
#include <fstream>
#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
#include "fold/lsp/diagnostic.hpp"
#include "fold/lsp/server.hpp"
#include "fold/lsp/transport.hpp"
namespace {
// In-memory LSP transport. The queue holds entire bodies (no Content-Length
// framing) so tests can describe messages declaratively; the LSP server
// only sees parsed bodies via readMessage anyway.
class ScriptedTransport final : public fold::lsp::Transport {
public:
void enqueue(const nlohmann::json& msg) { in_.push_back(msg.dump()); }
std::optional<std::string> readMessage() override {
if (in_.empty())
return std::nullopt;
std::string m = std::move(in_.front());
in_.pop_front();
return m;
}
void writeMessage(const nlohmann::json& m) override { out_.push_back(m); }
const std::vector<nlohmann::json>& out() const { return out_; }
private:
std::deque<std::string> in_;
std::vector<nlohmann::json> out_;
};
std::string locateFixtures() {
const char* env = std::getenv("FOLD_FIXTURES_DIR");
std::string fixtures = env ? env : "../fixtures";
if (!std::filesystem::exists(fixtures + "/clones/exact.cpp")) {
fixtures = std::filesystem::current_path().string() + "/../fixtures";
}
REQUIRE(std::filesystem::exists(fixtures + "/clones/exact.cpp"));
return fixtures;
}
void writeCompileDb(const std::string& fixtures) {
namespace fs = std::filesystem;
nlohmann::json db = nlohmann::json::array();
for (const auto& f : {"clones/exact.cpp", "clones/renamed.cpp"}) {
fs::path file = fs::path(fixtures) / f;
db.push_back({
{"directory", fixtures},
{"command", std::string("clang++ -std=c++20 -c ") + file.string()},
{"file", file.string()},
});
}
std::ofstream(fs::path(fixtures) / "compile_commands.json") << db.dump();
}
} // namespace
TEST_CASE("LSP: initialize returns server capabilities", "[lsp][handshake]") {
auto transport = std::make_unique<ScriptedTransport>();
auto* t = transport.get();
t->enqueue({{"jsonrpc", "2.0"},
{"id", 1},
{"method", "initialize"},
{"params", {{"rootUri", "file:///tmp"}}}});
t->enqueue({{"jsonrpc", "2.0"}, {"id", 2}, {"method", "shutdown"}});
t->enqueue({{"jsonrpc", "2.0"}, {"method", "exit"}});
fold::lsp::Server server(std::move(transport));
REQUIRE(server.run() == 0);
REQUIRE(t->out().size() == 2);
const auto& init = t->out()[0];
REQUIRE(init["id"] == 1);
REQUIRE(init["result"]["serverInfo"]["name"] == "fold");
REQUIRE(init["result"]["capabilities"]["textDocumentSync"]["save"].get<bool>() == true);
REQUIRE(t->out()[1]["id"] == 2);
}
TEST_CASE("LSP: didSave triggers publishDiagnostics with fold findings", "[lsp][diagnostics]") {
std::string fixtures = locateFixtures();
writeCompileDb(fixtures);
std::string clone_file = fixtures + "/clones/exact.cpp";
auto transport = std::make_unique<ScriptedTransport>();
auto* t = transport.get();
t->enqueue({{"jsonrpc", "2.0"},
{"id", 1},
{"method", "initialize"},
{"params",
{
{"rootUri", fold::lsp::uriFromAbsolutePath(fixtures)},
}}});
t->enqueue({{"jsonrpc", "2.0"}, {"method", "initialized"}});
t->enqueue({{"jsonrpc", "2.0"},
{"method", "textDocument/didSave"},
{"params",
{
{"textDocument", {{"uri", fold::lsp::uriFromAbsolutePath(clone_file)}}},
}}});
t->enqueue({{"jsonrpc", "2.0"}, {"id", 2}, {"method", "shutdown"}});
t->enqueue({{"jsonrpc", "2.0"}, {"method", "exit"}});
fold::lsp::Server server(std::move(transport));
REQUIRE(server.run() == 0);
bool saw_diagnostics = false;
for (const auto& msg : t->out()) {
if (msg.value("method", "") == "textDocument/publishDiagnostics") {
auto diags = msg["params"]["diagnostics"];
if (diags.is_array() && !diags.empty()) {
REQUIRE(diags[0]["source"] == "fold");
REQUIRE(diags[0].contains("range"));
saw_diagnostics = true;
}
}
}
REQUIRE(saw_diagnostics);
}
TEST_CASE("LSP URI helpers round-trip POSIX paths", "[lsp][uri]") {
std::string p = "/tmp/has space/file#hash.cpp";
std::string uri = fold::lsp::uriFromAbsolutePath(p);
REQUIRE(uri.rfind("file://", 0) == 0);
REQUIRE(fold::lsp::absolutePathFromUri(uri) == p);
}