#testing #compiler #suite #test-suite #harness #ui #trybuild

已删除 trybuild-test-suite

trybuild crate 的测试套件

0.0.0 2020年4月23日

#39 in #harness

MIT/Apache

2KB

Trybuild

github crates.io docs.rs build status

Trybuild 是一个测试工具,用于调用 rustc 对一组测试用例进行测试,并断言任何生成的错误信息是预期中的。

这种测试通常用于测试涉及过程宏的错误报告。我们会编写测试用例来触发宏检测到的错误或 Rust 编译器在展开后的代码中检测到的错误,并与预期错误进行比较,以确保它们对用户友好。

这种测试风格有时被称为 ui 测试,因为它们测试了用户与库的交互方面,而不仅仅是普通 API 测试所涵盖的内容。

这里没有任何内容与宏特定相关;trybuild 同样适用于测试非宏 API 的误用。

[dev-dependencies]
trybuild = "1.0"

编译失败测试

一个最小化的 trybuild 配置看起来像这样

#[test]
fn ui() {
    let t = trybuild::TestCases::new();
    t.compile_fail("tests/ui/*.rs");
}

可以使用 cargo test 来运行测试。它将逐个编译与 glob 模式匹配的源文件,预期它们无法编译,并断言编译器的错误信息与相邻命名的 *.stderr 文件(与测试文件名相同,但扩展名不同)中包含的预期输出相匹配。如果匹配,则认为测试用例成功。

在项目的 Cargo.toml 中的 [dev-dependencies] 下列出的依赖项可以在测试用例中访问。

失败的测试会显示预期的与实际的编译器输出。

如果编译失败测试没有失败,则也视为失败。

要测试单个源文件,使用

cargo test -- ui trybuild=example.rs

其中 ui 是调用 trybuild#[test] 函数的名称,而 example.rs 是要测试的文件名称。


通过测试

相同的测试工具也适用于运行预期会通过的测试。通常,您可以直接使用 Cargo 运行此类测试,但能够结合这种模式可能对研讨会很有用,在研讨会上,参与者一次一个地通过测试用例。Trybuild 最初是为我的 Rust Latam 的过程宏研讨会 开发的。

#[test]
fn ui() {
    let t = trybuild::TestCases::new();
    t.pass("tests/01-parse-header.rs");
    t.pass("tests/02-parse-body.rs");
    t.compile_fail("tests/03-expand-four-errors.rs");
    t.pass("tests/04-paste-ident.rs");
    t.pass("tests/05-repeat-section.rs");
    //t.pass("tests/06-make-work-in-function.rs");
    //t.pass("tests/07-init-array.rs");
    //t.compile_fail("tests/08-ident-span.rs");
}

通过测试如果编译成功并且编译后的二进制文件执行时没有恐慌,则视为成功。


详细信息

这是整个API。


工作流程

在迭代测试用例或库时,有两种方式来更新*.stderr文件;不建议手动编写。

首先,如果测试用例以compile_fail运行但没有相应的*.stderr文件,测试运行器将实际编译器的输出以正确的文件名保存到Cargo.toml所在目录下的wip目录中。因此,您可以通过删除这些文件,运行cargo test,并将所有文件从wip移动到您的测试用例目录中来更新这些文件。

或者,使用环境变量TRYBUILD=overwrite运行cargo test,以跳过wip目录并直接在原地写入所有编译器输出。之后,您可能需要检查git diff,以确保编译器的输出符合您的预期。


要测试什么

对于compile-fail测试,为任何您希望了解用户界面编译器输出变化的情况编写测试。作为一个负面例子,请不要仅仅调用所有公共API,并使用错误类型的参数来编写compile-fail测试;这样做没有任何好处。

常见用法是测试过程宏发出的特定目标错误消息。例如,来自ref-cast的derive宏要求放置在具有#[repr(C)]#[repr(transparent)]的类型上,以便扩展不会出现未定义行为,它在编译时强制执行

error: RefCast trait requires #[repr(C)] or #[repr(transparent)]
 --> $DIR/missing-repr.rs:3:10
  |
3 | #[derive(RefCast)]
  |          ^^^^^^^

消耗辅助属性的宏希望检查那些属性中的未识别内容是否正确地指示给调用者。错误消息是否放置在错误的标记下,而不是在无用的call_site范围中?

error: unknown serde field attribute `qqq`
 --> $DIR/unknown-attribute.rs:5:13
  |
5 |     #[serde(qqq = "...")]
  |             ^^^

声明性宏也可以从compile-fail测试中受益。来自serde_json的json!宏是一个非常大的macro_rules宏,但它努力确保来自输入中损坏的JSON的错误消息总是出现在最合适的标记上

error: no rules expected the token `,`
 --> $DIR/double-comma.rs:4:38
  |
4 |     println!("{}", json!({ "k": null,, }));
  |                                      ^ no rules expected this token in macro call

有时我们可能有一个宏,它可以在宏扩展之后成功展开,但我们依赖于它在某个点上触发特定的编译器错误。例如,来自readonly的crate引入了公共但只读的结构体字段,即使调用者有对周围结构体的&t;mut引用也不例外。如果有人写入只读字段,我们需要确保它无法编译

error[E0594]: cannot assign to data in a `&` reference
  --> $DIR/write-a-readonly.rs:17:26
   |
17 |     println!("{}", s.n); s.n += 1;
   |                          ^^^^^^^^ cannot assign

在所有这些情况下,编译器的输出可能会改变,因为我们的crate或我们的依赖项破坏了某些东西,或者由于Rust编译器的变化所致。这两者都是拥有良好构思的编译失败测试的好理由。如果我们重构并错误地导致了一个以前是正确的错误现在不再被发出或者发出在错误的位置,这对于测试套件来说是很重要的。如果编译器改变了一些东西,使得我们关心的错误消息大大变差,那么捕捉和报告为编译器问题也很重要。


许可证

根据您的选择,本软件受Apache许可证,版本2.0MIT许可证的许可。
除非您明确表示,否则根据Apache-2.0许可证定义,您有意提交给此crate的贡献应如上所述双重许可,不附加任何额外条款或条件。

无运行时依赖