Skip to content

Commit

Permalink
Update test-implementation.md (#1937)
Browse files Browse the repository at this point in the history
* Update test-implementation.md

* Update test-implementation.md
  • Loading branch information
Tbkhi committed Mar 13, 2024
1 parent dbee504 commit 8587c9b
Showing 1 changed file with 34 additions and 29 deletions.
63 changes: 34 additions & 29 deletions src/test-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:

Expand All @@ -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]
Expand All @@ -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
Expand All @@ -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{
Expand All @@ -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

0 comments on commit 8587c9b

Please sign in to comment.