// ============================================================================ // lsp_plugin_test.cpp — LSP 插件单元测试 (轻量 CHECK 宏,离线环境无 GoogleTest) // ============================================================================ // 测试 LSP 插件的可独立验证功能: // - lsp_trim: 字符串 trim 逻辑 // - lsp_frame_message: Content-Length header 构建 // - lsp_parse_content_length: Content-Length header 解析 // ============================================================================ #include "lsp_internal.hpp" #include "dstalk/dstalk_host.h" #include #include #include #include static int g_failures = 0; // Lightweight assertion macros (matches project pattern used by other tests) #define CHECK(cond, msg) do { \ if (!(cond)) { \ std::cerr << "[FAIL] " << (msg) << "\n"; \ ++g_failures; \ } \ } while (0) #define CHECK_EQ(actual, expected, msg) do { \ auto _a = (actual); \ auto _e = (expected); \ if (!(_a == _e)) { \ std::cerr << "[FAIL] " << (msg) \ << " (got=" << _a << " expected=" << _e << ")\n"; \ ++g_failures; \ } \ } while (0) // ---------------------------------------------------------------------------- // lsp_trim // ---------------------------------------------------------------------------- static void test_lsp_trim() { CHECK_EQ(lsp_trim(""), std::string(""), "trim empty string"); CHECK_EQ(lsp_trim("hello"), std::string("hello"), "trim no whitespace"); CHECK_EQ(lsp_trim(" hello"), std::string("hello"), "trim leading spaces"); CHECK_EQ(lsp_trim("hello "), std::string("hello"), "trim trailing spaces"); CHECK_EQ(lsp_trim(" hello "), std::string("hello"), "trim both sides"); CHECK_EQ(lsp_trim("\t\n\rhello\t\n\r"), std::string("hello"), "trim tabs/newlines"); CHECK_EQ(lsp_trim(" \t\n\r "), std::string(""), "trim only whitespace"); CHECK_EQ(lsp_trim("A"), std::string("A"), "trim single char"); CHECK_EQ(lsp_trim(" hello world "), std::string("hello world"), "trim preserves internal whitespace"); } // ---------------------------------------------------------------------------- // lsp_frame_message // ---------------------------------------------------------------------------- static void test_lsp_frame_message() { { std::string frame = lsp_frame_message(""); CHECK(frame.find("Content-Length: 0") != std::string::npos, "frame empty body has Content-Length: 0"); CHECK(frame.find("\r\n\r\n") != std::string::npos, "frame empty body has header separator"); CHECK(frame.find("\r\n\r\n") + 4 == frame.size(), "frame empty body ends right after separator"); } { std::string body = "{\"jsonrpc\":\"2.0\"}"; std::string frame = lsp_frame_message(body); CHECK(frame.find("Content-Length: " + std::to_string(body.size())) != std::string::npos, "frame simple body Content-Length matches"); CHECK(frame.find(body) != std::string::npos, "frame simple body present"); } { std::string body = "line1\nline2\nline3"; std::string frame = lsp_frame_message(body); CHECK(frame.find("Content-Length: " + std::to_string(body.size())) != std::string::npos, "frame multiline body Content-Length matches"); CHECK(frame.find(body) != std::string::npos, "frame multiline body present"); } { std::string body(std::string("\x00\x01\x02\x03\xFF", 5)); std::string frame = lsp_frame_message(body); CHECK(frame.find("Content-Length: 5") != std::string::npos, "frame binary body Content-Length: 5"); } } // ---------------------------------------------------------------------------- // lsp_parse_content_length // ---------------------------------------------------------------------------- static void test_lsp_parse_content_length() { CHECK_EQ(lsp_parse_content_length("Content-Length: 1234"), 1234, "parse valid header"); CHECK_EQ(lsp_parse_content_length(" Content-Length: 42"), 42, "parse leading spaces"); CHECK_EQ(lsp_parse_content_length("content-length: 99"), 99, "parse lowercase"); CHECK_EQ(lsp_parse_content_length("CONTENT-LENGTH: 77"), 77, "parse uppercase"); CHECK_EQ(lsp_parse_content_length("Content-Length: 0"), 0, "parse zero length"); CHECK_EQ(lsp_parse_content_length("Content-Length: 1048576"), 1048576, "parse large value"); CHECK_EQ(lsp_parse_content_length(""), -1, "parse empty string"); CHECK_EQ(lsp_parse_content_length("Content-Lengthh: 10"), -1, "parse misspelled (extra h)"); CHECK_EQ(lsp_parse_content_length("ContentLength: 10"), -1, "parse misspelled (no hyphen)"); CHECK_EQ(lsp_parse_content_length("Content-Length 10"), -1, "parse missing colon"); CHECK_EQ(lsp_parse_content_length("Content-Length: abc"), -1, "parse non-numeric"); CHECK_EQ(lsp_parse_content_length("Content-Length: -5"), -5, "parse negative"); CHECK_EQ(lsp_parse_content_length("Content-Length: 999999999999"), -1, "parse overflow"); CHECK_EQ(lsp_parse_content_length(std::string("\x00\x01\xFF", 3)), -1, "parse garbage input"); CHECK_EQ(lsp_parse_content_length("Content-Length: 1234abc"), 1234, "parse trailing garbage"); CHECK_EQ(lsp_parse_content_length("Content-Type: application/vscode-jsonrpc"), -1, "parse other LSP header returns -1"); } // ---------------------------------------------------------------------------- // frame + parse round-trip // ---------------------------------------------------------------------------- static void test_round_trip() { { std::string body = "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\"}"; std::string frame = lsp_frame_message(body); size_t header_end = frame.find("\r\n\r\n"); CHECK(header_end != std::string::npos, "round-trip simple finds header end"); if (header_end != std::string::npos) { std::string header_block = frame.substr(0, header_end); CHECK_EQ(lsp_parse_content_length(header_block), static_cast(body.size()), "round-trip simple Content-Length matches body size"); } } { std::string body; std::string frame = lsp_frame_message(body); size_t header_end = frame.find("\r\n\r\n"); CHECK(header_end != std::string::npos, "round-trip empty finds header end"); if (header_end != std::string::npos) { std::string header_block = frame.substr(0, header_end); CHECK_EQ(lsp_parse_content_length(header_block), 0, "round-trip empty Content-Length is 0"); } } } int main() { test_lsp_trim(); test_lsp_frame_message(); test_lsp_parse_content_length(); test_round_trip(); if (g_failures == 0) { std::cout << "lsp_plugin_test: all checks passed\n"; return 0; } std::cerr << "lsp_plugin_test: " << g_failures << " check(s) failed\n"; return 1; }