// 脾神(hj_transform)测试:确定性解析、提取、未闭合源码态、限额与校验。 #include #include #include #include "hunjing/lingtai.h" namespace { struct Engine { HjEngine *ptr = nullptr; Engine() { REQUIRE(hj_engine_new(HJ_ABI_VERSION, &ptr) == HJ_OK); } ~Engine() { hj_engine_free(ptr); } }; std::string transform(const Engine &e, std::string_view input, HjStatus *status = nullptr) { HjBuffer *buf = nullptr; HjStatus st = hj_transform_markdown(e.ptr, input.data(), input.size(), &buf); if (status != nullptr) *status = st; if (st != HJ_OK) return {}; std::string out(static_cast(hj_buffer_data(buf)), hj_buffer_len(buf)); hj_buffer_free(buf); return out; } } // namespace TEST_CASE("版本与空输入") { Engine e; CHECK(std::string(hj_transform_version()) == "hunjing-markdown-v1"); const std::string out = transform(e, ""); CHECK(out == R"jx({"parser_version":"hunjing-markdown-v1","blocks":[]})jx"); } TEST_CASE("纯文本归并为 markdown block 并裁剪空白") { Engine e; const std::string out = transform(e, "\n你好,魂镜\n\n第二段\n\n"); CHECK(out.find(R"jx("type":"markdown")jx") != std::string::npos); CHECK(out.find("你好,魂镜\\n\\n第二段") != std::string::npos); CHECK(out.find(R"jx("flavor":"hunjing-gfm-v1")jx") != std::string::npos); } TEST_CASE("fenced code 与 mermaid 提取") { Engine e; const std::string input = "前置说明\n```python\nprint(1)\n```\n中间\n```mermaid\nflowchart LR\nA-->B\n```\n结尾"; const std::string out = transform(e, input); CHECK(out.find(R"jx("type":"code","source":"print(1)","language":"python")jx") != std::string::npos); CHECK(out.find(R"jx("type":"mermaid","source":"flowchart LR\nA-->B")jx") != std::string::npos); CHECK(out.find("前置说明") != std::string::npos); CHECK(out.find("结尾") != std::string::npos); } TEST_CASE("块级数学定界提取") { Engine e; const std::string input = "前文\n$$\nE=mc^2\n$$\n后文"; const std::string out = transform(e, input); CHECK(out.find(R"jx("type":"math","notation":"latex","display":"block","source":"E=mc^2")jx") != std::string::npos); } TEST_CASE("未闭合 fenced 保持源码态") { Engine e; const std::string input = "开头\n```js\nunterminated("; const std::string out = transform(e, input); CHECK(out.find(R"jx("type":"markdown")jx") != std::string::npos); CHECK(out.find("```js") != std::string::npos); CHECK(out.find("unterminated(") != std::string::npos); CHECK(out.find(R"jx("type":"code")jx") == std::string::npos); } TEST_CASE("未闭合数学定界保持源码态") { Engine e; const std::string out = transform(e, "a\n$$\nunclosed math"); CHECK(out.find(R"jx("type":"math")jx") == std::string::npos); CHECK(out.find("$$") != std::string::npos); } TEST_CASE("确定性:同输入两次输出字节一致") { Engine e; const std::string input = "x\n```mermaid\nA-->B\n```\n$$\nz\n$$\n"; const std::string a = transform(e, input); const std::string b = transform(e, input); CHECK(a == b); } TEST_CASE("非法 UTF-8 拒绝") { Engine e; HjStatus st = HJ_OK; transform(e, std::string_view("ab\xff\xfe", 5), &st); CHECK(st == HJ_ERR_INVALID_ARGUMENT); } TEST_CASE("超长输入拒绝") { Engine e; const std::string big((1 << 20) + 1, 'a'); HjStatus st = HJ_OK; transform(e, big, &st); CHECK(st == HJ_ERR_LIMIT_EXCEEDED); } TEST_CASE("参数与句柄校验") { Engine e; HjBuffer *buf = nullptr; CHECK(hj_transform_markdown(e.ptr, "x", 1, nullptr) == HJ_ERR_INVALID_ARGUMENT); CHECK(hj_transform_markdown(nullptr, "x", 1, &buf) == HJ_ERR_HANDLE_INVALID); CHECK(hj_transform_markdown(e.ptr, nullptr, 5, &buf) == HJ_ERR_INVALID_ARGUMENT); } TEST_CASE("随机鲁棒性:任意字节输入不崩溃且返回已知状态码") { // 本地等价冒烟:正式 libFuzzer 目标(fuzz/fuzz_transform.cpp)在 Linux CI 运行。 Engine e; uint64_t rng = 0x9E3779B97F4A7C15ULL; // 固定种子,可复现 auto next = [&rng](uint64_t bound) { rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; return static_cast(rng % bound); }; static const char *kPieces[] = { "text ", "\n", "```", "mermaid\n", "python\n", "$$\n", "`", "$", "hello", "\xE4\xB8\xAD", "\"", "\\", "flowchart LR\nA-->B\n", "\x7f", }; for (int iter = 0; iter < 10000; ++iter) { std::string input; if (iter % 2 == 0) { const size_t len = next(512); input.reserve(len); for (size_t k = 0; k < len; ++k) input.push_back(static_cast(next(256))); } else { for (size_t k = 0, m = next(24); k < m; ++k) input += kPieces[next(14)]; } HjBuffer *buf = nullptr; const HjStatus st = hj_transform_markdown(e.ptr, input.data(), input.size(), &buf); CHECK((st == HJ_OK || st == HJ_ERR_INVALID_ARGUMENT || st == HJ_ERR_LIMIT_EXCEEDED || st == HJ_ERR_PARSE_FAILED)); if (buf != nullptr) { CHECK(hj_buffer_data(buf) != nullptr); CHECK(hj_buffer_free(buf) == HJ_OK); } } }