| # The `#[test]` attribute |
| |
| <!-- toc --> |
| |
| |
| |
| Many Rust programmers rely on a built-in attribute called `#[test]`. All |
| you have to do is mark a function and include some asserts like so: |
| |
| |
| ```rust,ignore |
| #[test] |
| fn my_test() { |
| assert!(2+2 == 4); |
| } |
| ``` |
| |
| When this program is compiled using `rustc --test` or `cargo test`, it will |
| produce an executable that can run this, and any other test function. This |
| method of testing allows tests to live alongside code in an organic way. You |
| can even put tests inside private modules: |
| |
| ```rust,ignore |
| mod my_priv_mod { |
| fn my_priv_func() -> bool {} |
| |
| #[test] |
| fn test_priv_func() { |
| assert!(my_priv_func()); |
| } |
| } |
| ``` |
| |
| Private items can thus be easily tested without worrying about how to expose |
| them to any sort of external testing apparatus. This is key to the |
| ergonomics of testing in Rust. Semantically, however, it's rather odd. |
| How does any sort of `main` function invoke these tests if they're not visible? |
| What exactly is `rustc --test` doing? |
| |
| `#[test]` is implemented as a syntactic transformation inside the compiler's |
| [`rustc_ast`][rustc_ast]. Essentially, it's a fancy [`macro`] that |
| rewrites the crate in 3 steps: |
| |
| ## Step 1: Re-Exporting |
| |
| As mentioned earlier, tests can exist inside private modules, so we need a |
| way of exposing them to the main function, without breaking any existing |
| code. To that end, [`rustc_ast`][rustc_ast] will create local modules called |
| `__test_reexports` that recursively reexport tests. This expansion translates |
| the above example into: |
| |
| ```rust,ignore |
| mod my_priv_mod { |
| fn my_priv_func() -> bool {} |
| |
| pub fn test_priv_func() { |
| assert!(my_priv_func()); |
| } |
| |
| pub mod __test_reexports { |
| pub use super::test_priv_func; |
| } |
| } |
| ``` |
| |
| Now, our test can be accessed as |
| `my_priv_mod::__test_reexports::test_priv_func`. For deeper module |
| structures, `__test_reexports` will reexport modules that contain tests, so a |
| test at `a::b::my_test` becomes |
| `a::__test_reexports::b::__test_reexports::my_test`. While this process seems |
| pretty safe, what happens if there is an existing `__test_reexports` module? |
| The answer: nothing. |
| |
| To explain, we need to understand how Rust's [Abstract Syntax Tree][ast] |
| represents [identifiers][Ident]. The name of every function, variable, module, |
| etc. is not stored as a string, but rather as an opaque [Symbol][Symbol] which |
| is essentially an ID number for each identifier. The compiler keeps a separate |
| hashtable that allows us to recover the human-readable name of a Symbol when |
| necessary (such as when printing a syntax error). When the compiler generates |
| the `__test_reexports` module, it generates a new [Symbol][Symbol] for the |
| identifier, so while the compiler-generated `__test_reexports` may share a name |
| with your hand-written one, it will not share a [Symbol][Symbol]. This |
| technique prevents name collision during code generation and is the foundation |
| of Rust's [`macro`] hygiene. |
| |
| ## Step 2: Harness generation |
| |
| Now that our tests are accessible from the root of our crate, we need to do |
| something with them using [`rustc_ast`][ast] generates a module like so: |
| |
| ```rust,ignore |
| #[main] |
| pub fn main() { |
| extern crate test; |
| test::test_main_static(&[&path::to::test1, /*...*/]); |
| } |
| ``` |
| |
| Here `path::to::test1` is a constant of type [`test::TestDescAndFn`][tdaf]. |
| |
| While this transformation is simple, it gives us a lot of insight into how |
| tests are actually run. The tests are aggregated into an array and passed to |
| a test runner called `test_main_static`. We'll come back to exactly what |
| [`TestDescAndFn`][tdaf] is, but for now, the key takeaway is that there is a crate |
| called [`test`][test] that is part of Rust core, that implements all of the |
| runtime for testing. [`test`][test]'s interface is unstable, so the only stable way |
| to interact with it is through the `#[test]` macro. |
| |
| ## Step 3: Test object generation |
| |
| If you've written tests in Rust before, you may be familiar with some of the |
| optional attributes available on test functions. For example, a test can be |
| annotated with `#[should_panic]` if we expect the test to cause a panic. It |
| looks something like this: |
| |
| ```rust,ignore |
| #[test] |
| #[should_panic] |
| fn foo() { |
| panic!("intentional"); |
| } |
| ``` |
| |
| This means our tests are more than just simple functions, they have |
| configuration information as well. `test` encodes this configuration data into |
| a `struct` called [`TestDesc`]. For each test function in a crate, |
| [`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`] |
| instance. It then combines the [`TestDesc`] and test function into the |
| predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_static`] |
| operates on. |
| For a given test, the generated [`TestDescAndFn`][tdaf] instance looks like so: |
| |
| ```rust,ignore |
| self::test::TestDescAndFn{ |
| desc: self::test::TestDesc{ |
| name: self::test::StaticTestName("foo"), |
| ignore: false, |
| should_panic: self::test::ShouldPanic::Yes, |
| allow_fail: false, |
| }, |
| testfn: self::test::StaticTestFn(|| |
| self::test::assert_test_result(::crate::__test_reexports::foo())), |
| } |
| ``` |
| |
| Once we've constructed an array of these test objects, they're passed to the |
| test runner via the harness generated in Step 2. |
| |
| ## Inspecting the generated code |
| |
| On `nightly` `rustc`, there's an unstable flag called `unpretty` that you can use |
| to print out the module source after [`macro`] expansion: |
| |
| ```bash |
| $ rustc my_mod.rs -Z unpretty=hir |
| ``` |
| |
| [`macro`]: ./macro-expansion.md |
| [`TestDesc`]: https://doc.rust-lang.org/test/struct.TestDesc.html |
| [ast]: ./ast-validation.md |
| [Ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html |
| [rustc_ast]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_ast |
| [Symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html |
| [test]: https://doc.rust-lang.org/test/index.html |
| [tdaf]: https://doc.rust-lang.org/test/struct.TestDescAndFn.html |
| [`test_main_static`]: https://doc.rust-lang.org/test/fn.test_main_static.html |