131 lines
4.6 KiB
C++
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);
|
|
}
|