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: skipdescription: ...(推荐)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枚举(Yes、No、Unknown)以指示修改 - 测试框架捕获并在快照中包含
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