]>
Commit | Line | Data |
---|---|---|
6a06907d XL |
1 | # The `#[test]` attribute |
2 | ||
3 | <!-- toc --> | |
4 | ||
a1dfa0c6 XL |
5 | Today, rust programmers rely on a built in attribute called `#[test]`. All |
6 | you have to do is mark a function as a test and include some asserts like so: | |
7 | ||
8 | ```rust,ignore | |
9 | #[test] | |
10 | fn my_test() { | |
ba9703b0 | 11 | assert!(2+2 == 4); |
a1dfa0c6 XL |
12 | } |
13 | ``` | |
14 | ||
15 | When this program is compiled using `rustc --test` or `cargo test`, it will | |
16 | produce an executable that can run this, and any other test function. This | |
17 | method of testing allows tests to live alongside code in an organic way. You | |
18 | can even put tests inside private modules: | |
19 | ||
20 | ```rust,ignore | |
21 | mod my_priv_mod { | |
ba9703b0 | 22 | fn my_priv_func() -> bool {} |
a1dfa0c6 | 23 | |
ba9703b0 XL |
24 | #[test] |
25 | fn test_priv_func() { | |
26 | assert!(my_priv_func()); | |
27 | } | |
a1dfa0c6 XL |
28 | } |
29 | ``` | |
ba9703b0 | 30 | |
a1dfa0c6 | 31 | Private items can thus be easily tested without worrying about how to expose |
60c5eb7d | 32 | them to any sort of external testing apparatus. This is key to the |
a1dfa0c6 XL |
33 | ergonomics of testing in Rust. Semantically, however, it's rather odd. |
34 | How does any sort of `main` function invoke these tests if they're not visible? | |
35 | What exactly is `rustc --test` doing? | |
36 | ||
37 | `#[test]` is implemented as a syntactic transformation inside the compiler's | |
6a06907d | 38 | [`rustc_ast` crate][rustc_ast]. Essentially, it's a fancy macro, that |
a1dfa0c6 XL |
39 | rewrites the crate in 3 steps: |
40 | ||
6a06907d | 41 | ## Step 1: Re-Exporting |
a1dfa0c6 XL |
42 | |
43 | As mentioned earlier, tests can exist inside private modules, so we need a | |
44 | way of exposing them to the main function, without breaking any existing | |
6a06907d | 45 | code. To that end, `rustc_ast` will create local modules called |
a1dfa0c6 XL |
46 | `__test_reexports` that recursively reexport tests. This expansion translates |
47 | the above example into: | |
48 | ||
49 | ```rust,ignore | |
50 | mod my_priv_mod { | |
ba9703b0 | 51 | fn my_priv_func() -> bool {} |
a1dfa0c6 | 52 | |
ba9703b0 XL |
53 | pub fn test_priv_func() { |
54 | assert!(my_priv_func()); | |
55 | } | |
a1dfa0c6 | 56 | |
ba9703b0 XL |
57 | pub mod __test_reexports { |
58 | pub use super::test_priv_func; | |
59 | } | |
a1dfa0c6 XL |
60 | } |
61 | ``` | |
62 | ||
63 | Now, our test can be accessed as | |
64 | `my_priv_mod::__test_reexports::test_priv_func`. For deeper module | |
65 | structures, `__test_reexports` will reexport modules that contain tests, so a | |
66 | test at `a::b::my_test` becomes | |
67 | `a::__test_reexports::b::__test_reexports::my_test`. While this process seems | |
68 | pretty safe, what happens if there is an existing `__test_reexports` module? | |
69 | The answer: nothing. | |
70 | ||
71 | To explain, we need to understand [how the AST represents | |
72 | identifiers][Ident]. The name of every function, variable, module, etc. is | |
73 | not stored as a string, but rather as an opaque [Symbol][Symbol] which is | |
74 | essentially an ID number for each identifier. The compiler keeps a separate | |
75 | hashtable that allows us to recover the human-readable name of a Symbol when | |
76 | necessary (such as when printing a syntax error). When the compiler generates | |
77 | the `__test_reexports` module, it generates a new Symbol for the identifier, | |
78 | so while the compiler-generated `__test_reexports` may share a name with your | |
79 | hand-written one, it will not share a Symbol. This technique prevents name | |
80 | collision during code generation and is the foundation of Rust's macro | |
81 | hygiene. | |
82 | ||
6a06907d XL |
83 | ## Step 2: Harness Generation |
84 | ||
a1dfa0c6 | 85 | Now that our tests are accessible from the root of our crate, we need to do |
6a06907d | 86 | something with them. `rustc_ast` generates a module like so: |
a1dfa0c6 XL |
87 | |
88 | ```rust,ignore | |
48663c56 XL |
89 | #[main] |
90 | pub fn main() { | |
ba9703b0 XL |
91 | extern crate test; |
92 | test::test_main_static(&[&path::to::test1, /*...*/]); | |
a1dfa0c6 XL |
93 | } |
94 | ``` | |
95 | ||
48663c56 XL |
96 | where `path::to::test1` is a constant of type `test::TestDescAndFn`. |
97 | ||
a1dfa0c6 XL |
98 | While this transformation is simple, it gives us a lot of insight into how |
99 | tests are actually run. The tests are aggregated into an array and passed to | |
48663c56 | 100 | a test runner called `test_main_static`. We'll come back to exactly what |
a1dfa0c6 XL |
101 | `TestDescAndFn` is, but for now, the key takeaway is that there is a crate |
102 | called [`test`][test] that is part of Rust core, that implements all of the | |
103 | runtime for testing. `test`'s interface is unstable, so the only stable way | |
104 | to interact with it is through the `#[test]` macro. | |
105 | ||
6a06907d XL |
106 | ## Step 3: Test Object Generation |
107 | ||
a1dfa0c6 XL |
108 | If you've written tests in Rust before, you may be familiar with some of the |
109 | optional attributes available on test functions. For example, a test can be | |
110 | annotated with `#[should_panic]` if we expect the test to cause a panic. It | |
111 | looks something like this: | |
112 | ||
113 | ```rust,ignore | |
114 | #[test] | |
115 | #[should_panic] | |
116 | fn foo() { | |
ba9703b0 | 117 | panic!("intentional"); |
a1dfa0c6 XL |
118 | } |
119 | ``` | |
120 | ||
121 | This means our tests are more than just simple functions, they have | |
122 | configuration information as well. `test` encodes this configuration data | |
123 | into a struct called [`TestDesc`][TestDesc]. For each test function in a | |
6a06907d | 124 | crate, `rustc_ast` will parse its attributes and generate a `TestDesc` |
a1dfa0c6 | 125 | instance. It then combines the `TestDesc` and test function into the |
48663c56 | 126 | predictably named `TestDescAndFn` struct, that `test_main_static` operates |
a1dfa0c6 XL |
127 | on. For a given test, the generated `TestDescAndFn` instance looks like so: |
128 | ||
129 | ```rust,ignore | |
130 | self::test::TestDescAndFn{ | |
131 | desc: self::test::TestDesc{ | |
132 | name: self::test::StaticTestName("foo"), | |
133 | ignore: false, | |
134 | should_panic: self::test::ShouldPanic::Yes, | |
135 | allow_fail: false, | |
136 | }, | |
137 | testfn: self::test::StaticTestFn(|| | |
138 | self::test::assert_test_result(::crate::__test_reexports::foo())), | |
139 | } | |
140 | ``` | |
141 | ||
142 | Once we've constructed an array of these test objects, they're passed to the | |
143 | test runner via the harness generated in step 2. | |
144 | ||
6a06907d XL |
145 | ## Inspecting the generated code |
146 | ||
a1dfa0c6 XL |
147 | On nightly rust, there's an unstable flag called `unpretty` that you can use |
148 | to print out the module source after macro expansion: | |
149 | ||
150 | ```bash | |
151 | $ rustc my_mod.rs -Z unpretty=hir | |
152 | ``` | |
153 | ||
154 | [test]: https://doc.rust-lang.org/test/index.html | |
155 | [TestDesc]: https://doc.rust-lang.org/test/struct.TestDesc.html | |
6a06907d XL |
156 | [Symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html |
157 | [Ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html | |
a1dfa0c6 | 158 | [eRFC]: https://github.com/rust-lang/rfcs/blob/master/text/2318-custom-test-frameworks.md |
6a06907d | 159 | [rustc_ast]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_ast |