feat: Add LSP plugin unit tests and frontend common initialization library
Some checks failed
Some checks failed
- Introduced `dstalk_lsp_plugin_test` for testing LSP plugin functionalities including `lsp_trim`, `lsp_frame_message`, and `lsp_parse_content_length`. - Created `dstalk_frontend_common` static library to encapsulate shared initialization logic for frontend components (CLI, GUI, Web). - Implemented configuration file discovery and service querying in `dstalk_frontend_init`. - Added internal headers for LSP and Anthropic plugins to facilitate unit testing. - Established a mailroom system for asynchronous message passing between stateless agents, enhancing coordination and context management.
This commit is contained in:
153
tests/lsp_plugin_test.cpp
Normal file
153
tests/lsp_plugin_test.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
// ============================================================================
|
||||
// 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 <cstdarg>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
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<int>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user