Skip to content

Commit f71d897

Browse files
committed
rustdoc: Test & document test_harness code block attribute
1 parent 04ff05c commit f71d897

File tree

4 files changed

+193
-17
lines changed

4 files changed

+193
-17
lines changed

src/doc/rustdoc/src/write-documentation/documentation-tests.md

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -321,21 +321,43 @@ we can add the `#[macro_use]` attribute. Second, we’ll need to add our own
321321

322322
## Attributes
323323

324-
Code blocks can be annotated with attributes that help `rustdoc` do the right
325-
thing when testing your code:
324+
Code blocks can be annotated with attributes that tell `rustdoc` how to build and interpret the test.
325+
They follow the triple backtick in the opening line.
326+
As such, they share the place with language strings like `rust` or `text`.
327+
Multiple attributes can be provided by separating them with commas, spaces or tabs.
328+
You can also write comments which are enclosed in parentheses `(…)`.
326329

327-
The `ignore` attribute tells Rust to ignore your code. This is almost never
328-
what you want as it's the most generic. Instead, consider annotating it
329-
with `text` if it's not code or using `#`s to get a working example that
330-
only shows the part you care about.
330+
As alluded to in the introduction at the very top,
331+
unless you specify `rust` or something that isn't an attribute (except for `custom`),
332+
the code block is assumed to be Rust source code (and is syntax highlighted as such).
333+
334+
You can of course add `rust` explicitly (like `rust,ignore`) if the Markdown is also consumed by
335+
other tools (e.g., if it's contained inside of a `README.md` that's included via `include_str`).
336+
337+
### `ignore`
338+
339+
The `ignore` attribute tells `rustdoc` to ignore your code. This is useful if you would like to
340+
have Rust syntax highlighting but the snippet is incomplete or pseudocode.
341+
It is customary to add the reason why it should be ignored in a `(…)` comment.
331342

332343
```rust
333344
/// ```ignore
334345
/// fn foo() {
335346
/// ```
347+
///
348+
/// ```ignore (needs extra dependency)
349+
/// use dependency::functionality;
350+
/// functionality();
351+
/// ```
336352
# fn foo() {}
337353
```
338354

355+
Do note that this is almost never what you want as it's the most generic.
356+
Instead, consider annotating it with `text` if it's not code or
357+
using `#`s to get a working example that only shows the part you care about.
358+
359+
### `should_panic`
360+
339361
`should_panic` tells `rustdoc` that the code should compile correctly but
340362
panic during execution. If the code doesn't panic, the test will fail.
341363

@@ -346,6 +368,8 @@ panic during execution. If the code doesn't panic, the test will fail.
346368
# fn foo() {}
347369
```
348370

371+
### `no_run`
372+
349373
The `no_run` attribute will compile your code but not run it. This is
350374
important for examples such as "Here's how to retrieve a web page,"
351375
which you would want to ensure compiles, but might be run in a test
@@ -361,10 +385,10 @@ used to demonstrate code snippets that can cause Undefined Behavior.
361385
# fn foo() {}
362386
```
363387

388+
### `compile_fail`
389+
364390
`compile_fail` tells `rustdoc` that the compilation should fail. If it
365-
compiles, then the test will fail. However, please note that code failing
366-
with the current Rust release may work in a future release, as new features
367-
are added.
391+
compiles, then the test will fail.
368392

369393
```rust
370394
/// ```compile_fail
@@ -374,6 +398,13 @@ are added.
374398
# fn foo() {}
375399
```
376400

401+
<div class="warning">
402+
However, please note that code failing with the current Rust release may work in a future release,
403+
as new features are added!
404+
</div>
405+
406+
### `edition…`
407+
377408
`edition2015`, `edition2018`, `edition2021`, and `edition2024` tell `rustdoc`
378409
that the code sample should be compiled using the respective edition of Rust.
379410

@@ -390,6 +421,8 @@ that the code sample should be compiled using the respective edition of Rust.
390421
# fn foo() {}
391422
```
392423

424+
### `standalone_crate`
425+
393426
Starting in the 2024 edition[^edition-note], compatible doctests are merged as one before being
394427
run. We combine doctests for performance reasons: the slowest part of doctests is to compile them.
395428
Merging all of them into one file and compiling this new file, then running the doctests is much
@@ -441,7 +474,7 @@ should not be merged with the others. So the previous code should use it:
441474
In this case, it means that the line information will not change if you add/remove other
442475
doctests.
443476

444-
### Ignoring targets
477+
### `ignore-…`: Ignoring targets
445478

446479
Attributes starting with `ignore-` can be used to ignore doctests for specific
447480
targets. For example, `ignore-x86_64` will avoid building doctests when the
@@ -478,7 +511,7 @@ struct Foo;
478511
In older versions, this will be ignored on all targets, but starting with
479512
version 1.88.0, `ignore-x86_64` will override `ignore`.
480513

481-
### Custom CSS classes for code blocks
514+
### `{…}` & `custom`: Custom CSS classes for code blocks
482515

483516
```rust
484517
/// ```custom,{class=language-c}
@@ -504,8 +537,9 @@ To be noted that you can replace `class=` with `.` to achieve the same result:
504537
pub struct Bar;
505538
```
506539

507-
To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is
508-
a Rust code block whereas the two others add a "rust" CSS class on the code block.
540+
To be noted, `rust` and `{.rust}` / `{class=rust}` have different effects:
541+
`rust` indicates that this is a Rust code block whereas
542+
the two others add a "rust" CSS class on the code block.
509543

510544
You can also use double quotes:
511545

@@ -516,6 +550,27 @@ You can also use double quotes:
516550
pub struct Bar;
517551
```
518552

553+
### `test_harness`
554+
555+
With `test_harness` applied, `rustdoc` will run any contained *test functions*
556+
instead of the (potentially implicit) `main` function.
557+
558+
```rust
559+
//! ```test_harness
560+
//! #[test]
561+
//! #[should_panic]
562+
//! fn abc() { assert!(false); }
563+
//!
564+
//! #[test]
565+
//! fn xyz() { assert!(true); }
566+
//! ```
567+
```
568+
569+
You can read more about *test functions* in [the Book][testing-book] or in [the Rust Reference][testing-ref].
570+
571+
[testing-book]: ../../book/book/ch11-01-writing-tests.html
572+
[testing-ref]: ../../reference/attributes/testing.html
573+
519574
## Syntax reference
520575

521576
The *exact* syntax for code blocks, including the edge cases, can be found

src/librustdoc/html/markdown.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -847,11 +847,12 @@ pub(crate) enum Ignore {
847847
Some(Vec<String>),
848848
}
849849

850-
/// This is the parser for fenced codeblocks attributes. It implements the following eBNF:
850+
/// This is the parser for fenced codeblocks attributes.
851851
///
852-
/// ```eBNF
853-
/// lang-string = *(token-list / delimited-attribute-list / comment)
852+
/// It implements the following grammar as expressed in ABNF:
854853
///
854+
/// ```ABNF
855+
/// lang-string = *(token-list / delimited-attribute-list / comment)
855856
/// bareword = LEADINGCHAR *(CHAR)
856857
/// bareword-without-leading-char = CHAR *(CHAR)
857858
/// quoted-string = QUOTE *(NONQUOTE) QUOTE
@@ -862,7 +863,7 @@ pub(crate) enum Ignore {
862863
/// attribute-list = [sep] attribute *(sep attribute) [sep]
863864
/// delimited-attribute-list = OPEN-CURLY-BRACKET attribute-list CLOSE-CURLY-BRACKET
864865
/// token-list = [sep] token *(sep token) [sep]
865-
/// comment = OPEN_PAREN *(all characters) CLOSE_PAREN
866+
/// comment = OPEN_PAREN *<all characters except closing parentheses> CLOSE_PAREN
866867
///
867868
/// OPEN_PAREN = "("
868869
/// CLOSE_PARENT = ")"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Ensure that the code block attr `test_harness` works as expected.
2+
//@ compile-flags: --test --test-args --test-threads=1
3+
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
4+
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
5+
//@ rustc-env: RUST_BACKTRACE=0
6+
//@ failure-status: 101
7+
8+
// The `main` fn won't be run under `test_harness`, so this test should pass.
9+
//! ```test_harness
10+
//! fn main() {
11+
//! assert!(false);
12+
//! }
13+
//! ```
14+
15+
// Check that we run both `bad` and `good` even if `bad` fails.
16+
//! ```test_harness
17+
//! #[test]
18+
//! fn bad() {
19+
//! assert!(false);
20+
//! }
21+
//!
22+
//! #[test]
23+
//! fn good() {
24+
//! assert!(true);
25+
//! }
26+
//! ```
27+
28+
// Contrary to normal doctests, cfg `test` is active under `test_harness`.
29+
//! ```test_harness
30+
//! #[cfg(test)]
31+
//! mod group {
32+
//! #[test]
33+
//! fn element() {
34+
//! assert!(false);
35+
//! }
36+
//! }
37+
//! ```
38+
39+
// `test_harness` doctests aren't implicitly wrapped in a `main` fn even if they contain stmts.
40+
//! ```test_harness
41+
//! assert!(true);
42+
//!
43+
//! #[test] fn extra() {}
44+
//! ```
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
running 4 tests
3+
test $DIR/test-harness.rs - (line 14) ... FAILED
4+
test $DIR/test-harness.rs - (line 25) ... FAILED
5+
test $DIR/test-harness.rs - (line 34) ... FAILED
6+
test $DIR/test-harness.rs - (line 9) ... ok
7+
8+
failures:
9+
10+
---- $DIR/test-harness.rs - (line 14) stdout ----
11+
Test executable failed (exit status: 101).
12+
13+
stdout:
14+
15+
running 2 tests
16+
test good ... ok
17+
test bad ... FAILED
18+
19+
failures:
20+
21+
---- bad stdout ----
22+
23+
thread 'bad' ($TID) panicked at $DIR/test-harness.rs:4:5:
24+
assertion failed: false
25+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
26+
27+
28+
failures:
29+
bad
30+
31+
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
32+
33+
34+
35+
---- $DIR/test-harness.rs - (line 25) stdout ----
36+
Test executable failed (exit status: 101).
37+
38+
stdout:
39+
40+
running 1 test
41+
test group::element ... FAILED
42+
43+
failures:
44+
45+
---- group::element stdout ----
46+
47+
thread 'group::element' ($TID) panicked at $DIR/test-harness.rs:6:9:
48+
assertion failed: false
49+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
50+
51+
52+
failures:
53+
group::element
54+
55+
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
56+
57+
58+
59+
---- $DIR/test-harness.rs - (line 34) stdout ----
60+
error: non-item macro in item position: assert
61+
--> $DIR/test-harness.rs:35:1
62+
|
63+
LL | assert!(true);
64+
| ^^^^^^^^^^^^^
65+
66+
error: aborting due to 1 previous error
67+
68+
Couldn't compile the test.
69+
70+
failures:
71+
$DIR/test-harness.rs - (line 14)
72+
$DIR/test-harness.rs - (line 25)
73+
$DIR/test-harness.rs - (line 34)
74+
75+
test result: FAILED. 1 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
76+

0 commit comments

Comments
 (0)