Some checks failed
- Refresh agents STATUS to W22.6 and exclude mailroom from metadata scans - Add mailroom dispatch checklist and defensive rules - Register F-23.D-1 and tag network input validation defense-in-depth - Update network plugin tests for header length limits - Fix LSP test metadata and remove orphan anthropic_internal.hpp Verification: - cmake --build build --config Release: 0 error, 0 warning - ctest --test-dir build --output-on-failure: 10/10 passed - ctest --test-dir build -R dstalk_smoke_test --output-on-failure: passed - python scripts/check_agents_metadata.py --strict: passed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
156 lines
7.6 KiB
C++
156 lines
7.6 KiB
C++
// ============================================================================
|
|
// lsp_plugin_test.cpp — LSP 插件单元测试 (轻量 CHECK 宏,离线环境无 GoogleTest)
|
|
// ============================================================================
|
|
// 测试 LSP 插件的可独立验证功能:
|
|
// - lsp_trim: 字符串 trim 逻辑
|
|
// - lsp_frame_message: Content-Length header 构建
|
|
// - lsp_parse_content_length: Content-Length header 解析
|
|
// 说明: 本测试仅覆盖纯函数路径;不覆盖 reader_loop 并发、进程生命周期或
|
|
// Server->Client Request 集成路径,这些需由 smoke/集成测试覆盖。
|
|
// ============================================================================
|
|
|
|
#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;
|
|
}
|