Skip to content

Commit eab1d61

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

File tree

4 files changed

+193
-18
lines changed

4 files changed

+193
-18
lines changed

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

Lines changed: 68 additions & 14 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,11 +385,10 @@ used to demonstrate code snippets that can cause Undefined Behavior.
361385
# fn foo() {}
362386
```
363387

364-
`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.
388+
### `compile_fail`
368389

390+
`compile_fail` tells `rustdoc` that the compilation should fail. If it
391+
compiles, then the test will fail.
369392
```rust
370393
/// ```compile_fail
371394
/// let x = 5;
@@ -374,6 +397,13 @@ are added.
374397
# fn foo() {}
375398
```
376399

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

@@ -390,6 +420,8 @@ that the code sample should be compiled using the respective edition of Rust.
390420
# fn foo() {}
391421
```
392422

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

444-
### Ignoring targets
476+
### `ignore-…`: Ignoring targets
445477

446478
Attributes starting with `ignore-` can be used to ignore doctests for specific
447479
targets. For example, `ignore-x86_64` will avoid building doctests when the
@@ -478,7 +510,7 @@ struct Foo;
478510
In older versions, this will be ignored on all targets, but starting with
479511
version 1.88.0, `ignore-x86_64` will override `ignore`.
480512

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

483515
```rust
484516
/// ```custom,{class=language-c}
@@ -504,8 +536,9 @@ To be noted that you can replace `class=` with `.` to achieve the same result:
504536
pub struct Bar;
505537
```
506538

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.
539+
To be noted, `rust` and `{.rust}` / `{class=rust}` have different effects:
540+
`rust` indicates that this is a Rust code block whereas
541+
the two others add a "rust" CSS class on the code block.
509542

510543
You can also use double quotes:
511544

@@ -516,6 +549,27 @@ You can also use double quotes:
516549
pub struct Bar;
517550
```
518551

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

521575
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)