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 -->
|
||||
|
||||
Today, 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:
|
||||
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]
|
||||
|
|
@ -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?
|
||||
|
||||
`#[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:
|
||||
|
||||
## 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` 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
|
||||
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?
|
||||
The answer: nothing.
|
||||
|
||||
To explain, we need to understand [how the 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
|
||||
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 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. This technique prevents name
|
||||
collision during code generation and is the foundation of Rust's macro
|
||||
hygiene.
|
||||
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. `rustc_ast` generates a module like so:
|
||||
something with them using [`rustc_ast`][ast] generates a module like so:
|
||||
|
||||
```rust,ignore
|
||||
#[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
|
||||
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` 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
|
||||
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.
|
||||
|
||||
## Step 3: Test Object Generation
|
||||
|
|
@ -119,12 +119,13 @@ fn foo() {
|
|||
```
|
||||
|
||||
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`][TestDesc]. For each test function in a
|
||||
crate, `rustc_ast` will parse its attributes and generate a `TestDesc`
|
||||
instance. It then combines the `TestDesc` and test function into the
|
||||
predictably named `TestDescAndFn` struct, that `test_main_static` operates
|
||||
on. For a given test, the generated `TestDescAndFn` instance looks like so:
|
||||
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{
|
||||
|
|
@ -140,19 +141,23 @@ self::test::TestDescAndFn{
|
|||
```
|
||||
|
||||
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
|
||||
|
||||
On nightly rust, there's an unstable flag called `unpretty` that you can use
|
||||
to print out the module source after macro expansion:
|
||||
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
|
||||
```
|
||||
|
||||
[test]: https://doc.rust-lang.org/test/index.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
|
||||
[`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
|
||||
|
|
|
|||
Loading…
Reference in New Issue