]> git.proxmox.com Git - rustc.git/blame - src/doc/rustc-dev-guide/src/test-implementation.md
New upstream version 1.52.0~beta.3+dfsg1
[rustc.git] / src / doc / rustc-dev-guide / src / test-implementation.md
CommitLineData
6a06907d
XL
1# The `#[test]` attribute
2
3<!-- toc -->
4
a1dfa0c6
XL
5Today, rust programmers rely on a built in attribute called `#[test]`. All
6you have to do is mark a function as a test and include some asserts like so:
7
8```rust,ignore
9#[test]
10fn my_test() {
ba9703b0 11 assert!(2+2 == 4);
a1dfa0c6
XL
12}
13```
14
15When this program is compiled using `rustc --test` or `cargo test`, it will
16produce an executable that can run this, and any other test function. This
17method of testing allows tests to live alongside code in an organic way. You
18can even put tests inside private modules:
19
20```rust,ignore
21mod 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 31Private items can thus be easily tested without worrying about how to expose
60c5eb7d 32them to any sort of external testing apparatus. This is key to the
a1dfa0c6
XL
33ergonomics of testing in Rust. Semantically, however, it's rather odd.
34How does any sort of `main` function invoke these tests if they're not visible?
35What 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
39rewrites the crate in 3 steps:
40
6a06907d 41## Step 1: Re-Exporting
a1dfa0c6
XL
42
43As mentioned earlier, tests can exist inside private modules, so we need a
44way of exposing them to the main function, without breaking any existing
6a06907d 45code. To that end, `rustc_ast` will create local modules called
a1dfa0c6
XL
46`__test_reexports` that recursively reexport tests. This expansion translates
47the above example into:
48
49```rust,ignore
50mod 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
63Now, our test can be accessed as
64`my_priv_mod::__test_reexports::test_priv_func`. For deeper module
65structures, `__test_reexports` will reexport modules that contain tests, so a
66test at `a::b::my_test` becomes
67`a::__test_reexports::b::__test_reexports::my_test`. While this process seems
68pretty safe, what happens if there is an existing `__test_reexports` module?
69The answer: nothing.
70
71To explain, we need to understand [how the AST represents
72identifiers][Ident]. The name of every function, variable, module, etc. is
73not stored as a string, but rather as an opaque [Symbol][Symbol] which is
74essentially an ID number for each identifier. The compiler keeps a separate
75hashtable that allows us to recover the human-readable name of a Symbol when
76necessary (such as when printing a syntax error). When the compiler generates
77the `__test_reexports` module, it generates a new Symbol for the identifier,
78so while the compiler-generated `__test_reexports` may share a name with your
79hand-written one, it will not share a Symbol. This technique prevents name
80collision during code generation and is the foundation of Rust's macro
81hygiene.
82
6a06907d
XL
83## Step 2: Harness Generation
84
a1dfa0c6 85Now that our tests are accessible from the root of our crate, we need to do
6a06907d 86something with them. `rustc_ast` generates a module like so:
a1dfa0c6
XL
87
88```rust,ignore
48663c56
XL
89#[main]
90pub fn main() {
ba9703b0
XL
91 extern crate test;
92 test::test_main_static(&[&path::to::test1, /*...*/]);
a1dfa0c6
XL
93}
94```
95
48663c56
XL
96where `path::to::test1` is a constant of type `test::TestDescAndFn`.
97
a1dfa0c6
XL
98While this transformation is simple, it gives us a lot of insight into how
99tests are actually run. The tests are aggregated into an array and passed to
48663c56 100a 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
102called [`test`][test] that is part of Rust core, that implements all of the
103runtime for testing. `test`'s interface is unstable, so the only stable way
104to interact with it is through the `#[test]` macro.
105
6a06907d
XL
106## Step 3: Test Object Generation
107
a1dfa0c6
XL
108If you've written tests in Rust before, you may be familiar with some of the
109optional attributes available on test functions. For example, a test can be
110annotated with `#[should_panic]` if we expect the test to cause a panic. It
111looks something like this:
112
113```rust,ignore
114#[test]
115#[should_panic]
116fn foo() {
ba9703b0 117 panic!("intentional");
a1dfa0c6
XL
118}
119```
120
121This means our tests are more than just simple functions, they have
122configuration information as well. `test` encodes this configuration data
123into a struct called [`TestDesc`][TestDesc]. For each test function in a
6a06907d 124crate, `rustc_ast` will parse its attributes and generate a `TestDesc`
a1dfa0c6 125instance. It then combines the `TestDesc` and test function into the
48663c56 126predictably named `TestDescAndFn` struct, that `test_main_static` operates
a1dfa0c6
XL
127on. For a given test, the generated `TestDescAndFn` instance looks like so:
128
129```rust,ignore
130self::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
142Once we've constructed an array of these test objects, they're passed to the
143test runner via the harness generated in step 2.
144
6a06907d
XL
145## Inspecting the generated code
146
a1dfa0c6
XL
147On nightly rust, there's an unstable flag called `unpretty` that you can use
148to 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