Update test-implementation.md (#1937)
* Update test-implementation.md * Update test-implementation.md
This commit is contained in:
parent
1d2ddcf7d1
commit
6df2f59951
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
Today, Rust programmers rely on a built in attribute called `#[test]`. All
|
Many Rust programmers rely on a built-in attribute called `#[test]`. All
|
||||||
you have to do is mark a function as a test and include some asserts like so:
|
you have to do is mark a function and include some asserts like so:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -35,14 +35,14 @@ How does any sort of `main` function invoke these tests if they're not visible?
|
||||||
What exactly is `rustc --test` doing?
|
What exactly is `rustc --test` doing?
|
||||||
|
|
||||||
`#[test]` is implemented as a syntactic transformation inside the compiler's
|
`#[test]` is implemented as a syntactic transformation inside the compiler's
|
||||||
[`rustc_ast` crate][rustc_ast]. Essentially, it's a fancy macro, that
|
[`rustc_ast`][rustc_ast]. Essentially, it's a fancy [`macro`] that
|
||||||
rewrites the crate in 3 steps:
|
rewrites the crate in 3 steps:
|
||||||
|
|
||||||
## Step 1: Re-Exporting
|
## Step 1: Re-Exporting
|
||||||
|
|
||||||
As mentioned earlier, tests can exist inside private modules, so we need a
|
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
|
way of exposing them to the main function, without breaking any existing
|
||||||
code. To that end, `rustc_ast` will create local modules called
|
code. To that end, [`rustc_ast`][rustc_ast] will create local modules called
|
||||||
`__test_reexports` that recursively reexport tests. This expansion translates
|
`__test_reexports` that recursively reexport tests. This expansion translates
|
||||||
the above example into:
|
the above example into:
|
||||||
|
|
||||||
|
|
@ -68,22 +68,22 @@ test at `a::b::my_test` becomes
|
||||||
pretty safe, what happens if there is an existing `__test_reexports` module?
|
pretty safe, what happens if there is an existing `__test_reexports` module?
|
||||||
The answer: nothing.
|
The answer: nothing.
|
||||||
|
|
||||||
To explain, we need to understand [how the AST represents
|
To explain, we need to understand how Rust's [Abstract Syntax Tree][ast]
|
||||||
identifiers][Ident]. The name of every function, variable, module, etc. is
|
represents [identifiers][Ident]. The name of every function, variable, module,
|
||||||
not stored as a string, but rather as an opaque [Symbol][Symbol] which is
|
etc. is not stored as a string, but rather as an opaque [Symbol][Symbol] which
|
||||||
essentially an ID number for each identifier. The compiler keeps a separate
|
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
|
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
|
necessary (such as when printing a syntax error). When the compiler generates
|
||||||
the `__test_reexports` module, it generates a new Symbol for the identifier,
|
the `__test_reexports` module, it generates a new [Symbol][Symbol] for the
|
||||||
so while the compiler-generated `__test_reexports` may share a name with your
|
identifier, so while the compiler-generated `__test_reexports` may share a name
|
||||||
hand-written one, it will not share a Symbol. This technique prevents name
|
with your hand-written one, it will not share a [Symbol][Symbol]. This
|
||||||
collision during code generation and is the foundation of Rust's macro
|
technique prevents name collision during code generation and is the foundation
|
||||||
hygiene.
|
of Rust's [`macro`] hygiene.
|
||||||
|
|
||||||
## Step 2: Harness Generation
|
## Step 2: Harness Generation
|
||||||
|
|
||||||
Now that our tests are accessible from the root of our crate, we need to do
|
Now that our tests are accessible from the root of our crate, we need to do
|
||||||
something with them. `rustc_ast` generates a module like so:
|
something with them using [`rustc_ast`][ast] generates a module like so:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
#[main]
|
#[main]
|
||||||
|
|
@ -93,14 +93,14 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
where `path::to::test1` is a constant of type `test::TestDescAndFn`.
|
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
|
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
|
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
|
a test runner called `test_main_static`. We'll come back to exactly what
|
||||||
`TestDescAndFn` is, but for now, the key takeaway is that there is a crate
|
[`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
|
called [`test`][test] that is part of Rust core, that implements all of the
|
||||||
runtime for testing. `test`'s interface is unstable, so the only stable way
|
runtime for testing. [`test`][test]'s interface is unstable, so the only stable way
|
||||||
to interact with it is through the `#[test]` macro.
|
to interact with it is through the `#[test]` macro.
|
||||||
|
|
||||||
## Step 3: Test Object Generation
|
## Step 3: Test Object Generation
|
||||||
|
|
@ -119,12 +119,13 @@ fn foo() {
|
||||||
```
|
```
|
||||||
|
|
||||||
This means our tests are more than just simple functions, they have
|
This means our tests are more than just simple functions, they have
|
||||||
configuration information as well. `test` encodes this configuration data
|
configuration information as well. `test` encodes this configuration data into
|
||||||
into a struct called [`TestDesc`][TestDesc]. For each test function in a
|
a `struct` called [`TestDesc`]. For each test function in a crate,
|
||||||
crate, `rustc_ast` will parse its attributes and generate a `TestDesc`
|
[`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`]
|
||||||
instance. It then combines the `TestDesc` and test function into the
|
instance. It then combines the [`TestDesc`] and test function into the
|
||||||
predictably named `TestDescAndFn` struct, that `test_main_static` operates
|
predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_static`]
|
||||||
on. For a given test, the generated `TestDescAndFn` instance looks like so:
|
operates on.
|
||||||
|
For a given test, the generated [`TestDescAndFn`][tdaf] instance looks like so:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
self::test::TestDescAndFn{
|
self::test::TestDescAndFn{
|
||||||
|
|
@ -140,19 +141,23 @@ self::test::TestDescAndFn{
|
||||||
```
|
```
|
||||||
|
|
||||||
Once we've constructed an array of these test objects, they're passed to the
|
Once we've constructed an array of these test objects, they're passed to the
|
||||||
test runner via the harness generated in step 2.
|
test runner via the harness generated in Step 2.
|
||||||
|
|
||||||
## Inspecting the generated code
|
## Inspecting the generated code
|
||||||
|
|
||||||
On nightly rust, there's an unstable flag called `unpretty` that you can use
|
On `nightly` `rustc`, there's an unstable flag called `unpretty` that you can use
|
||||||
to print out the module source after macro expansion:
|
to print out the module source after [`macro`] expansion:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ rustc my_mod.rs -Z unpretty=hir
|
$ rustc my_mod.rs -Z unpretty=hir
|
||||||
```
|
```
|
||||||
|
|
||||||
[test]: https://doc.rust-lang.org/test/index.html
|
[`macro`]: ./macro-expansion.md
|
||||||
[TestDesc]: https://doc.rust-lang.org/test/struct.TestDesc.html
|
[`TestDesc`]: https://doc.rust-lang.org/test/struct.TestDesc.html
|
||||||
[Symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html
|
[ast]: ./ast-validation.md
|
||||||
[Ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html
|
[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
|
[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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue