HashQL测试策略Skill testing-hashql

HashQL测试策略是用于测试HashQL代码的一套方法,包括编译测试(UI测试)、单元测试和快照测试,主要用于验证编译器行为、错误诊断、内部逻辑和MIR/HIR/AST传递,确保代码质量和错误报告准确。关键词包括HashQL、测试、编译测试、单元测试、快照测试、MIR、HIR、AST、诊断、错误消息、Rust、编译器、量化交易。

测试 0 次安装 0 次浏览 更新于 3/11/2026

name: testing-hashql description: HashQL测试策略,包括编译测试(UI测试)、单元测试和快照测试。在编写HashQL代码测试、使用//~注解、运行–bless、调试测试失败或选择合适测试方法时使用。 license: AGPL-3.0 metadata: triggers: type: domain enforcement: suggest priority: high keywords: - hashql测试 - 编译测试 - ui测试 - 快照测试 - insta测试 - //~注解 - --bless intent-patterns: - “\b(编写|创建|运行|调试|添加|修复)\b.?\b(hashql|编译测试)\b.?\b测试\b” - “\b(测试|验证)\b.*?\b(诊断|错误消息|mir|hir|ast)\b”

HashQL测试策略

HashQL使用三种测试方法。编译测试是默认方法,用于测试编译器行为。

快速参考

场景 测试类型 位置
诊断/错误消息 编译测试 tests/ui/
编译器流水线阶段 编译测试 tests/ui/
MIR/HIR/AST传递集成 编译测试 tests/ui/
MIR/HIR/AST传递边界情况 insta tests/ui/<category>/
MIR传递单元测试 MIR构建器 src/**/tests.rs
核心库(如需要) insta src/**/snapshots/
解析器片段(syntax-jexpr) insta src/*/snapshots/
内部函数/逻辑 单元测试 src/*.rs

编译测试(UI测试)

使用J-Expr文件和诊断注解测试解析、类型检查和错误报告。

结构:

package/tests/ui/
  category/
    .spec.toml        # 套件规范(必需)
    test.jsonc        # 测试输入
    test.stdout       # 预期输出(运行:通过)
    test.stderr       # 预期错误(运行:失败)
    test.aux.svg      # 辅助输出(某些套件)

命令:

cargo run -p hashql-compiletest run                           # 运行所有
cargo run -p hashql-compiletest run --filter "test(name)"     # 过滤
cargo run -p hashql-compiletest run --bless                   # 更新预期

测试文件示例:

//@ run: fail
//@ description: 测试重复字段检测
["type", "Bad", {"#struct": {"x": "Int", "x": "String"}}, "_"]
//~^ ERROR 字段 `x` 首次在此定义

指令//@ 在文件开头):

  • run: pass / run: fail(默认) / run: skip
  • description: ...(推荐)
  • name: custom_name

注解//~ 用于预期诊断):

  • //~ ERROR msg - 当前行
  • //~^ ERROR msg - 上一行
  • //~v ERROR msg - 下一行
  • //~| ERROR msg - 与前一注解相同

📖 完整指南: references/compiletest-guide.md

单元测试

标准Rust #[test] 函数,用于测试内部逻辑。

位置: #[cfg(test)] 模块在源文件中

示例来自 hashql-syntax-jexpr/src/parser/state.rs

#[test]
fn peek_returns_token_without_consuming() {
    bind_context!(let context = "42");
    bind_state!(let mut state from context);

    let token = state.peek().expect("不应失败").expect("应有令牌");
    assert_eq!(token.kind, number("42"));
}

命令:

cargo nextest run --package hashql-<package>
cargo test --package hashql-<package> --doc    # 文档测试

insta快照测试

当编译测试(首选方法)不可行时,使用 insta 库进行快照输出测试。分为三类:

类别 快照位置 理由
流水线库 mir, hir, ast tests/ui/<category>/*.snap 与编译测试测试一起定位
核心 hashql-core 默认insta(src/**/snapshots/ 与流水线分离;优先单元测试
语法 syntax-jexpr src/*/snapshots/ 基于宏的解析器片段测试

流水线库(mir, hir, ast)

快照与编译测试UI测试一起定位。测试代码在 src/**/tests.rs,快照在相应的 tests/ui/<category>/ 目录。

// 示例:hashql-mir/src/pass/transform/ssa_repair/tests.rs
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut settings = Settings::clone_current();
settings.set_snapshot_path(dir.join("tests/ui/pass/ssa_repair")); // 匹配测试类别
settings.set_prepend_module_to_snapshot(false);

let _drop = settings.bind_to_scope();
assert_snapshot!(name, value);

类别多样:reify/lower/pass/ssa_repair/ 等。

核心

hashql-core 与编译流水线分离,因此使用默认insta目录。优先单元测试;仅在必要时使用快照。

语法(syntax-jexpr)

语法库早于编译测试,使用基于宏的测试框架直接测试解析器片段。

// hashql-syntax-jexpr/src/parser/string/test.rs
pub(crate) macro test_cases($parser:ident; $($name:ident($source:expr) => $description:expr,)*) {
    $(
        #[test]
        fn $name() {
            assert_parse!($parser, $source, $description);
        }
    )*
}

快照:hashql-syntax-jexpr/src/parser/*/snapshots/*.snap

命令

cargo insta test --package hashql-<package>
cargo insta review     # 交互式审查
cargo insta accept     # 接受所有待定

MIR构建器测试

通过编程方式构建的MIR体直接测试MIR转换和分析传递。

位置: hashql-mir/src/pass/**/tests.rs

何时使用:

  • 隔离测试MIR传递,具有精确的CFG控制
  • 边界情况需要从源代码难以生成的特定MIR结构
  • 基准测试传递性能

关键特性:

  • 转换传递返回 Changed 枚举(YesNoUnknown)以指示修改
  • 测试框架捕获并在快照中包含 Changed 值以供验证
  • 快照格式:MIR前 → Changed: Yes/No/Unknown 分隔符 → MIR后

重要:缺少宏功能

body! 宏不支持所有MIR构造。如果您需要一个不支持的功能,不要手动绕过 - 而是停止并请求将该功能添加到宏中。

快速示例(使用 body! 宏)

use hashql_core::{heap::Heap, r#type::environment::Environment};
use hashql_mir::{builder::body, intern::Interner};

let heap = Heap::new();
let interner = Interner::new(&heap);
let env = Environment::new(&heap);

let body = body!(interner, env; fn@0/1 -> Int {
    decl x: Int, cond: Bool;

    bb0() {
        cond = load true;
        if cond then bb1() else bb2();
    },
    bb1() {
        goto bb3(1);
    },
    bb2() {
        goto bb3(2);
    },
    bb3(x) {
        return x;
    }
});

📖 完整指南: references/mir-builder-guide.md

参考资料