渲染zh-Hans

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 文件中,因此没有独立的测试目录结构,没有测试模块声明,构建脚本在测试构建与生产构建之间也没有区别。编译器通过所用关键字来区分哪些块是测试代码、哪些是生产代码 —— probandumproba 会被解析,但从生产构建中排除。

实战示例#

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 块可以嵌套,镜像被测代码的结构。

参考#

  1. examples/corpus/probandum/ —— probandum 示例文件
  2. examples/corpus/proba/ —— proba 示例文件
  3. examples/corpus/adfirma/ —— adfirma 示例文件
  4. examples/coreutils/packages/echo/src/main.fab —— 带标签的实战用法