diff --git a/src/doc/rustdoc/src/write-documentation/documentation-tests.md b/src/doc/rustdoc/src/write-documentation/documentation-tests.md index 4084c1d962a22..bb51cd7f4ea99 100644 --- a/src/doc/rustdoc/src/write-documentation/documentation-tests.md +++ b/src/doc/rustdoc/src/write-documentation/documentation-tests.md @@ -321,21 +321,43 @@ we can add the `#[macro_use]` attribute. Second, we’ll need to add our own ## Attributes -Code blocks can be annotated with attributes that help `rustdoc` do the right -thing when testing your code: +Code blocks can be annotated with attributes that tell `rustdoc` how to build and interpret the test. +They follow the triple backtick in the opening line. +As such, they share the place with language strings like `rust` or `text`. +Multiple attributes can be provided by separating them with commas, spaces or tabs. +You can also write comments which are enclosed in parentheses `(…)`. -The `ignore` attribute tells Rust to ignore your code. This is almost never -what you want as it's the most generic. Instead, consider annotating it -with `text` if it's not code or using `#`s to get a working example that -only shows the part you care about. +As alluded to in the introduction at the very top, +unless you specify `rust` or something that isn't an attribute (except for `custom`), +the code block is assumed to be Rust source code (and is syntax highlighted as such). + +You can of course add `rust` explicitly (like `rust,ignore`) if the Markdown is also consumed by +other tools (e.g., if it's contained inside of a `README.md` that's included via `include_str`). + +### `ignore` + +The `ignore` attribute tells `rustdoc` to ignore your code. This is useful if you would like to +have Rust syntax highlighting but the snippet is incomplete or pseudocode. +It is customary to add the reason why it should be ignored in a `(…)` comment. ```rust /// ```ignore /// fn foo() { /// ``` +/// +/// ```ignore (needs extra dependency) +/// use dependency::functionality; +/// functionality(); +/// ``` # fn foo() {} ``` +Do note that this is almost never what you want as it's the most generic. +Instead, consider annotating it with `text` if it's not code or +using `#`s to get a working example that only shows the part you care about. + +### `should_panic` + `should_panic` tells `rustdoc` that the code should compile correctly but panic during execution. If the code doesn't panic, the test will fail. @@ -346,6 +368,8 @@ panic during execution. If the code doesn't panic, the test will fail. # fn foo() {} ``` +### `no_run` + The `no_run` attribute will compile your code but not run it. This is important for examples such as "Here's how to retrieve a web page," 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. # fn foo() {} ``` +### `compile_fail` + `compile_fail` tells `rustdoc` that the compilation should fail. If it -compiles, then the test will fail. However, please note that code failing -with the current Rust release may work in a future release, as new features -are added. +compiles, then the test will fail. ```rust /// ```compile_fail @@ -374,6 +398,13 @@ are added. # fn foo() {} ``` +
+However, please note that code failing with the current Rust release may work in a future release, +as new features are added! +
+ +### `edition…` + `edition2015`, `edition2018`, `edition2021`, and `edition2024` tell `rustdoc` that the code sample should be compiled using the respective edition of Rust. @@ -390,6 +421,8 @@ that the code sample should be compiled using the respective edition of Rust. # fn foo() {} ``` +### `standalone_crate` + Starting in the 2024 edition[^edition-note], compatible doctests are merged as one before being run. We combine doctests for performance reasons: the slowest part of doctests is to compile them. 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: In this case, it means that the line information will not change if you add/remove other doctests. -### Ignoring targets +### `ignore-…`: Ignoring targets Attributes starting with `ignore-` can be used to ignore doctests for specific targets. For example, `ignore-x86_64` will avoid building doctests when the @@ -478,7 +511,7 @@ struct Foo; In older versions, this will be ignored on all targets, but starting with version 1.88.0, `ignore-x86_64` will override `ignore`. -### Custom CSS classes for code blocks +### `{…}` & `custom`: Custom CSS classes for code blocks ```rust /// ```custom,{class=language-c} @@ -504,8 +537,9 @@ To be noted that you can replace `class=` with `.` to achieve the same result: pub struct Bar; ``` -To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is -a Rust code block whereas the two others add a "rust" CSS class on the code block. +To be noted, `rust` and `{.rust}` / `{class=rust}` have different effects: +`rust` indicates that this is a Rust code block whereas +the two others add a "rust" CSS class on the code block. You can also use double quotes: @@ -516,6 +550,27 @@ You can also use double quotes: pub struct Bar; ``` +### `test_harness` + +With `test_harness` applied, `rustdoc` will run any contained *test functions* +instead of the (potentially implicit) `main` function. + +```rust +//! ```test_harness +//! #[test] +//! #[should_panic] +//! fn abc() { assert!(false); } +//! +//! #[test] +//! fn xyz() { assert!(true); } +//! ``` +``` + +You can read more about *test functions* in [the Book][testing-book] or in [the Rust Reference][testing-ref]. + +[testing-book]: ../../book/ch11-01-writing-tests.html +[testing-ref]: ../../reference/attributes/testing.html + ## Syntax reference The *exact* syntax for code blocks, including the edge cases, can be found diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 752cd2e610a54..c4e684f8db14e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -847,11 +847,12 @@ pub(crate) enum Ignore { Some(Vec), } -/// This is the parser for fenced codeblocks attributes. It implements the following eBNF: +/// This is the parser for fenced codeblocks attributes. /// -/// ```eBNF -/// lang-string = *(token-list / delimited-attribute-list / comment) +/// It implements the following grammar as expressed in ABNF: /// +/// ```ABNF +/// lang-string = *(token-list / delimited-attribute-list / comment) /// bareword = LEADINGCHAR *(CHAR) /// bareword-without-leading-char = CHAR *(CHAR) /// quoted-string = QUOTE *(NONQUOTE) QUOTE @@ -862,7 +863,7 @@ pub(crate) enum Ignore { /// attribute-list = [sep] attribute *(sep attribute) [sep] /// delimited-attribute-list = OPEN-CURLY-BRACKET attribute-list CLOSE-CURLY-BRACKET /// token-list = [sep] token *(sep token) [sep] -/// comment = OPEN_PAREN *(all characters) CLOSE_PAREN +/// comment = OPEN_PAREN * CLOSE_PAREN /// /// OPEN_PAREN = "(" /// CLOSE_PARENT = ")" diff --git a/tests/rustdoc-ui/doctest/test-harness.rs b/tests/rustdoc-ui/doctest/test-harness.rs new file mode 100644 index 0000000000000..067391b3b7d1f --- /dev/null +++ b/tests/rustdoc-ui/doctest/test-harness.rs @@ -0,0 +1,44 @@ +// Ensure that the code block attr `test_harness` works as expected. +//@ compile-flags: --test --test-args --test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ rustc-env: RUST_BACKTRACE=0 +//@ failure-status: 101 + +// The `main` fn won't be run under `test_harness`, so this test should pass. +//! ```test_harness +//! fn main() { +//! assert!(false); +//! } +//! ``` + +// Check that we run both `bad` and `good` even if `bad` fails. +//! ```test_harness +//! #[test] +//! fn bad() { +//! assert!(false); +//! } +//! +//! #[test] +//! fn good() { +//! assert!(true); +//! } +//! ``` + +// Contrary to normal doctests, cfg `test` is active under `test_harness`. +//! ```test_harness +//! #[cfg(test)] +//! mod group { +//! #[test] +//! fn element() { +//! assert!(false); +//! } +//! } +//! ``` + +// `test_harness` doctests aren't implicitly wrapped in a `main` fn even if they contain stmts. +//! ```test_harness +//! assert!(true); +//! +//! #[test] fn extra() {} +//! ``` diff --git a/tests/rustdoc-ui/doctest/test-harness.stdout b/tests/rustdoc-ui/doctest/test-harness.stdout new file mode 100644 index 0000000000000..94fc943511f87 --- /dev/null +++ b/tests/rustdoc-ui/doctest/test-harness.stdout @@ -0,0 +1,76 @@ + +running 4 tests +test $DIR/test-harness.rs - (line 14) ... FAILED +test $DIR/test-harness.rs - (line 25) ... FAILED +test $DIR/test-harness.rs - (line 34) ... FAILED +test $DIR/test-harness.rs - (line 9) ... ok + +failures: + +---- $DIR/test-harness.rs - (line 14) stdout ---- +Test executable failed (exit status: 101). + +stdout: + +running 2 tests +test bad ... FAILED +test good ... ok + +failures: + +---- bad stdout ---- + +thread 'bad' ($TID) panicked at $DIR/test-harness.rs:4:5: +assertion failed: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + bad + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + + +---- $DIR/test-harness.rs - (line 25) stdout ---- +Test executable failed (exit status: 101). + +stdout: + +running 1 test +test group::element ... FAILED + +failures: + +---- group::element stdout ---- + +thread 'group::element' ($TID) panicked at $DIR/test-harness.rs:6:9: +assertion failed: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + group::element + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + + +---- $DIR/test-harness.rs - (line 34) stdout ---- +error: non-item macro in item position: assert + --> $DIR/test-harness.rs:35:1 + | +LL | assert!(true); + | ^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/test-harness.rs - (line 14) + $DIR/test-harness.rs - (line 25) + $DIR/test-harness.rs - (line 34) + +test result: FAILED. 1 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +