Errors and testing
Error handling#
Faber 将许多语言合并为同一种形状的三个相关概念区分开来:
| 构造 | 含义 |
|---|---|
→ T | 正常的成功返回通道 |
T ∪ nihil | 成功值域中的缺失 |
⇥ E | 用于错误的可恢复备用退出通道 |
正常返回#
函数 porta(整数 x) → 整数 {
如果 x ≺ 0 则 返回 0
返回 x * 2
}可失败函数#
当函数可以通过错误通道退出时,使用 ⇥:
函数 divide(整数 a, 整数 b) → 整数 ⇥ 文本 {
如果 b ≡ 0 {
抛错 "division by zero"
}
返回 a / b
}抛出异常 — iace#
iace 在错误通道上发送一个值:
函数 exigePositivum(整数 value) → 无值 ⇥ 文本 {
如果 value ≺ 0 {
抛错 "negative value"
}
}恢复 — fac / cape#
调用者使用 执行 块和 捕获 处理程序进行局部恢复:
函数 divide(整数 a, 整数 b) → 整数 {
如果 b ≡ 0 则 返回 0
返回 a / b
}
函数 tutum(整数 a, 整数 b) → 整数 {
执行 {
返回 divide(a, b)
}
捕获 err {
警告 err
返回 0
}
}直接的可失败调用不是普通表达式。请将 → T ⇥ E 函数的调用放在活动的 执行 / 捕获 边界内。
内联转换恢复#
⇥ 也可以在 ↦ 转换上指定内联恢复值:
常量 文本 raw ← "42"
常量 整数 n ← raw ↦ 整数 ⇥ 0仅效果可失败#
对于会出错但不返回成功值的函数,省略 → T:
函数 exigePositivum(整数 value) → 无值 ⇥ 文本 {
如果 value ≺ 0 {
抛错 "negative value"
}
}当前状态#
→、返回、⇥、iace 以及 执行 / 捕获 是现行的语法和检查器接口。针对完整 ⇥ / iace / 捕获 运行时行为的 Rust 和 Go 降阶(lowering)仍存在后端缺口——这些能通过类型检查,但尚未向所有目标生成可失败的运行时代码。
Inline testing#
Faber 在语言层面内置了一流测试框架,包含三个关键字:probandum 声明测试套件,proba 声明单个测试用例,adfirma 断言某个条件。测试与被测代码位于同一文件中,通过 faber test 运行,并支持与生产代码相同的编译流水线 —— 具备区域感知能力、类型检查和多目标支持。
三个关键字#
| 关键字 | 角色 | 近似等价物 |
|---|---|---|
probandum | 声明具名测试套件 | describe, #[cfg(test)] mod |
proba | 声明单个测试用例 | it, #[test] |
adfirma | 在运行时断言某个条件 | assert!, assert_eq! |
probandum —— 测试套件#
probandum 块对相关测试用例进行分组。套件可以嵌套,以层级方式组织测试:
测试 "unum plus unum" {
断言 1 + 1 ≡ 2
}
测试 "multiplicatio" {
断言 3 * 4 ≡ 12
}
测试 "comparatio" {
常量 整数 x ← 10
断言 x ≥ 10
}proba —— 测试用例#
proba 块包含测试逻辑。它可以使用任意 Faber 代码 —— 变量绑定、函数调用、控制流 —— 并以一个或多个 adfirma 断言结束。测试可以通过可选的 tag 标记打标签,以便选择性执行:
proba "echo formats operands with one space" tag "coreutils" {
adfirma echo_textus(["hello", "world"]) ≡ "hello world"
}adfirma —— 断言#
adfirma 对一个布尔表达式求值,若为假则报告失败。可选的消息字符串在失败时提供上下文:
入口 {
常量 整数 x ← 10
# Simple assertion
断言 x ≻ 0
# With custom message
断言 x ≡ 10 secus "x decem esse debet"
# Multiple assertions in sequence
常量 文本 nomen ← "Marcus"
断言 nomen ≡ "Marcus"
断言 nomen ≠ "" secus "nomen vacuum non sit"
}工作流#
测试通过 faber test 命令运行:
faber test # run all tests in the current package
faber test examples/coreutils/packages/echo # run tests for a specific package由于测试与源代码位于同一个 .fab 文件中,因此没有独立的测试目录结构,没有测试模块声明,构建脚本在测试构建与生产构建之间也没有区别。编译器通过所用关键字来区分哪些块是测试代码、哪些是生产代码 —— probandum 和 proba 会被解析,但从生产构建中排除。
实战示例#
coreutils 的 echo 包在实践中展示了该测试框架的用法。测试与实现位于同一文件中,涵盖选项解析、转义扩展和边界情况:
probandum "echo formatting" tag "coreutils" {
proba "empty operands format as empty text" {
fixum lista<textus> words ← vacua
adfirma echo_textus(words) ≡ ""
}
proba "single operand is unchanged" {
adfirma echo_textus(["hello"]) ≡ "hello"
}
proba "-E is a leading no-op option" {
adfirma echo_textus(["-E", "hello", "world"]) ≡ "hello world"
}
proba "-n suppresses the trailing newline flag" {
adfirma echo_novam_lineam(["-n", "hello"]) ≡ falsum
}
proba "-e expands the declared escape subset" {
adfirma echo_textus(["-e", "a\\nb"]) ≡ "a\nb"
adfirma echo_textus(["-e", "a\\tb"]) ≡ "a\tb"
}
}设计说明#
若干设计选择使 Faber 的测试框架区别于传统方法:
- 无独立测试二进制文件。 测试是同一源文件中的声明,而非独立的编译目标。编译器将测试块从生产输出中过滤掉。
- 以标签代替目录。 测试通过
tag标记而非目录结构来组织。一个测试可以属于多个组织维度,而无需被移动。 - 完整编译流水线。 测试经过类型检查、分析,并具备区域感知能力 —— 同一个
--reader-locale标志也适用于测试输出。 - 多目标。 测试通过包所面向的任意后端运行 ——
faber test --interpret使用 MIR 步进器,faber test使用编译后的 Rust。 - 嵌套套件。
probandum块可以嵌套,镜像被测代码的结构。
参考#
examples/corpus/probandum/—— probandum 示例文件examples/corpus/proba/—— proba 示例文件examples/corpus/adfirma/—— adfirma 示例文件examples/coreutils/packages/echo/src/main.fab—— 带标签的实战用法