about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-04-25 00:53:58 +0200
committerGitHub <noreply@github.com>2025-04-25 00:53:58 +0200
commit610ed826e30c8f89d56275b5a1033de4b3fc4a38 (patch)
treea8a5ef3a5f841c860b2bc14b1ad9660331a293d0
parent3c877f6a477380ed61155d3bf816df09c9e05b9e (diff)
parent115341f767741ab1584b5daeafc2709a74612f59 (diff)
downloadrust-610ed826e30c8f89d56275b5a1033de4b3fc4a38.tar.gz
rust-610ed826e30c8f89d56275b5a1033de4b3fc4a38.zip
Rollup merge of #137096 - ehuss:stabilize-doctest-xcompile, r=fmease
Stabilize flags for doctest cross compilation

This makes the following changes in preparation for supporting doctest cross-compiling in cargo:

- Renames `--runtool` and `--runtool-arg` to `--test-runtool` and `--test-runtool-arg` to maintain consistency with other `--test-*` arguments.
- Stabilizes the `--test-runtool` and `--test-runtool-arg`. These are needed in order to support cargo's `target.runner` option which specifies a runner to execute a cross-compiled doctest (for example, qemu).
- Stabilizes the `--enable-per-target-ignores` flag by removing it and making it unconditionally enabled. This makes it possible to disable a doctest on a per-target basis, which I think will be helpful for rolling out this feature.

These changes were suggested in https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/stabilizing.20doctest.20xcompile/near/409281127

The intent is to stabilize the doctest-xcompile feature in cargo. This will help ensure that for projects that do cross-compile testing that their doctests are also covered. Currently there is a somewhat surprising behavior that they are ignored.

Closes https://github.com/rust-lang/rust/issues/64245

try-job: x86_64-msvc-1
-rw-r--r--src/doc/rustdoc/src/command-line-arguments.md22
-rw-r--r--src/doc/rustdoc/src/unstable-features.md54
-rw-r--r--src/doc/rustdoc/src/write-documentation/documentation-tests.md37
-rw-r--r--src/librustdoc/config.rs23
-rw-r--r--src/librustdoc/doctest.rs6
-rw-r--r--src/librustdoc/doctest/markdown.rs8
-rw-r--r--src/librustdoc/doctest/rust.rs6
-rw-r--r--src/librustdoc/html/markdown.rs30
-rw-r--r--src/librustdoc/html/markdown/tests.rs4
-rw-r--r--src/librustdoc/lib.rs18
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs2
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs2
-rw-r--r--src/tools/miri/cargo-miri/src/main.rs2
-rw-r--r--src/tools/miri/cargo-miri/src/phases.rs10
-rw-r--r--tests/run-make/doctests-runtool/rmake.rs6
-rw-r--r--tests/run-make/rustdoc-default-output/output-default.stdout7
-rw-r--r--tests/rustdoc/doctest/auxiliary/doctest-runtool.rs21
-rw-r--r--tests/rustdoc/doctest/doctest-runtool.rs13
18 files changed, 134 insertions, 137 deletions
diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md
index 872592d669d..b55ddf6e0e1 100644
--- a/src/doc/rustdoc/src/command-line-arguments.md
+++ b/src/doc/rustdoc/src/command-line-arguments.md
@@ -222,6 +222,28 @@ For more, see [the chapter on documentation tests](write-documentation/documenta
 
 See also `--test`.
 
+## `--test-runtool`, `--test-runtool-arg`: program to run tests with; args to pass to it
+
+A doctest wrapper program can be specified with the `--test-runtool` flag.
+Rustdoc will execute that wrapper instead of the doctest executable when
+running tests. The first arguments to the wrapper will be any arguments
+specified with the `--test-runtool-arg` flag, followed by the path to the
+doctest executable to run.
+
+Using these options looks like this:
+
+```bash
+$ rustdoc src/lib.rs --test-runtool path/to/runner --test-runtool-arg --do-thing --test-runtool-arg --do-other-thing
+```
+
+For example, if you want to run your doctests under valgrind you might run:
+
+```bash
+$ rustdoc src/lib.rs --test-runtool valgrind
+```
+
+Another use case would be to run a test inside an emulator, or through a Virtual Machine.
+
 ## `--target`: generate documentation for the specified target triple
 
 Using this flag looks like this:
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index d4ff69a9933..69e5a5adbec 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -631,60 +631,6 @@ The generated output (formatted) will look like this:
 `--output-format html` has no effect, as the default output is HTML. This is
 accepted on stable, even though the other options for this flag aren't.
 
-## `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
-
- * Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245)
-
-Using this flag looks like this:
-
-```bash
-$ rustdoc src/lib.rs -Z unstable-options --enable-per-target-ignores
-```
-
-This flag allows you to tag doctests with compiletest style `ignore-foo` filters that prevent
-rustdoc from running that test if the target triple string contains foo. For example:
-
-```rust
-///```ignore-foo,ignore-bar
-///assert!(2 == 2);
-///```
-struct Foo;
-```
-
-This will not be run when the build target is `super-awesome-foo` or `less-bar-awesome`.
-If the flag is not enabled, then rustdoc will consume the filter, but do nothing with it, and
-the above example will be run for all targets.
-If you want to preserve backwards compatibility for older versions of rustdoc, you can use
-
-```rust
-///```ignore,ignore-foo
-///assert!(2 == 2);
-///```
-struct Foo;
-```
-
-In older versions, this will be ignored on all targets, but on newer versions `ignore-gnu` will
-override `ignore`.
-
-## `--runtool`, `--runtool-arg`: program to run tests with; args to pass to it
-
- * Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245)
-
-Using these options looks like this:
-
-```bash
-$ rustdoc src/lib.rs -Z unstable-options --runtool runner --runtool-arg --do-thing --runtool-arg --do-other-thing
-```
-
-These options can be used to run the doctest under a program, and also pass arguments to
-that program. For example, if you want to run your doctests under valgrind you might run
-
-```bash
-$ rustdoc src/lib.rs -Z unstable-options --runtool valgrind
-```
-
-Another use case would be to run a test inside an emulator, or through a Virtual Machine.
-
 ## `--with-examples`: include examples of uses of items as documentation
 
  * Tracking issue: [#88791](https://github.com/rust-lang/rust/issues/88791)
diff --git a/src/doc/rustdoc/src/write-documentation/documentation-tests.md b/src/doc/rustdoc/src/write-documentation/documentation-tests.md
index b921f677857..077b02d603d 100644
--- a/src/doc/rustdoc/src/write-documentation/documentation-tests.md
+++ b/src/doc/rustdoc/src/write-documentation/documentation-tests.md
@@ -427,6 +427,43 @@ 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
+
+Attributes starting with `ignore-` can be used to ignore doctests for specific
+targets. For example, `ignore-x86_64` will avoid building doctests when the
+target name contains `x86_64`.
+
+```rust
+/// ```ignore-x86_64
+/// assert!(2 == 2);
+/// ```
+struct Foo;
+```
+
+This doctest will not be built for targets such as `x86_64-unknown-linux-gnu`.
+
+Multiple ignore attributes can be specified to ignore multiple targets:
+
+```rust
+/// ```ignore-x86_64,ignore-windows
+/// assert!(2 == 2);
+/// ```
+struct Foo;
+```
+
+If you want to preserve backwards compatibility for older versions of rustdoc,
+you can specify both `ignore` and `ignore-`, such as:
+
+```rust
+/// ```ignore,ignore-x86_64
+/// assert!(2 == 2);
+/// ```
+struct Foo;
+```
+
+In older versions, this will be ignored on all targets, but starting with
+version CURRENT_RUSTC_VERSION, `ignore-x86_64` will override `ignore`.
+
 ### Custom CSS classes for code blocks
 
 ```rust
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 23a2bcd9011..4ef73ff48ed 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -124,13 +124,9 @@ pub(crate) struct Options {
     /// temporary directory if not set.
     pub(crate) persist_doctests: Option<PathBuf>,
     /// Runtool to run doctests with
-    pub(crate) runtool: Option<String>,
+    pub(crate) test_runtool: Option<String>,
     /// Arguments to pass to the runtool
-    pub(crate) runtool_args: Vec<String>,
-    /// Whether to allow ignoring doctests on a per-target basis
-    /// For example, using ignore-foo to ignore running the doctest on any target that
-    /// contains "foo" as a substring
-    pub(crate) enable_per_target_ignores: bool,
+    pub(crate) test_runtool_args: Vec<String>,
     /// Do not run doctests, compile them if should_test is active.
     pub(crate) no_run: bool,
     /// What sources are being mapped.
@@ -215,9 +211,8 @@ impl fmt::Debug for Options {
             .field("persist_doctests", &self.persist_doctests)
             .field("show_coverage", &self.show_coverage)
             .field("crate_version", &self.crate_version)
-            .field("runtool", &self.runtool)
-            .field("runtool_args", &self.runtool_args)
-            .field("enable-per-target-ignores", &self.enable_per_target_ignores)
+            .field("test_runtool", &self.test_runtool)
+            .field("test_runtool_args", &self.test_runtool_args)
             .field("run_check", &self.run_check)
             .field("no_run", &self.no_run)
             .field("test_builder_wrappers", &self.test_builder_wrappers)
@@ -779,9 +774,8 @@ impl Options {
         let unstable_opts_strs = matches.opt_strs("Z");
         let lib_strs = matches.opt_strs("L");
         let extern_strs = matches.opt_strs("extern");
-        let runtool = matches.opt_str("runtool");
-        let runtool_args = matches.opt_strs("runtool-arg");
-        let enable_per_target_ignores = matches.opt_present("enable-per-target-ignores");
+        let test_runtool = matches.opt_str("test-runtool");
+        let test_runtool_args = matches.opt_strs("test-runtool-arg");
         let document_private = matches.opt_present("document-private-items");
         let document_hidden = matches.opt_present("document-hidden-items");
         let run_check = matches.opt_present("check");
@@ -843,9 +837,8 @@ impl Options {
             crate_version,
             test_run_directory,
             persist_doctests,
-            runtool,
-            runtool_args,
-            enable_per_target_ignores,
+            test_runtool,
+            test_runtool_args,
             test_builder,
             run_check,
             no_run,
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 88eaa52c6de..829a9ca6e7d 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -219,11 +219,9 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
             let crate_name = tcx.crate_name(LOCAL_CRATE).to_string();
             let crate_attrs = tcx.hir_attrs(CRATE_HIR_ID);
             let opts = scrape_test_config(crate_name, crate_attrs, args_path);
-            let enable_per_target_ignores = options.enable_per_target_ignores;
 
             let hir_collector = HirCollector::new(
                 ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
-                enable_per_target_ignores,
                 tcx,
             );
             let tests = hir_collector.collect_crate();
@@ -782,10 +780,10 @@ fn run_test(
     let mut cmd;
 
     let output_file = make_maybe_absolute_path(output_file);
-    if let Some(tool) = &rustdoc_options.runtool {
+    if let Some(tool) = &rustdoc_options.test_runtool {
         let tool = make_maybe_absolute_path(tool.into());
         cmd = Command::new(tool);
-        cmd.args(&rustdoc_options.runtool_args);
+        cmd.args(&rustdoc_options.test_runtool_args);
         cmd.arg(&output_file);
     } else {
         cmd = Command::new(&output_file);
diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs
index a0d39ce749d..497a8d7c4a7 100644
--- a/src/librustdoc/doctest/markdown.rs
+++ b/src/librustdoc/doctest/markdown.rs
@@ -104,13 +104,7 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
     };
     let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
 
-    find_testable_code(
-        &input_str,
-        &mut md_collector,
-        codes,
-        options.enable_per_target_ignores,
-        None,
-    );
+    find_testable_code(&input_str, &mut md_collector, codes, None);
 
     let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
     md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs
index be2dea641d7..43dcfab880b 100644
--- a/src/librustdoc/doctest/rust.rs
+++ b/src/librustdoc/doctest/rust.rs
@@ -63,19 +63,18 @@ impl DocTestVisitor for RustCollector {
 pub(super) struct HirCollector<'tcx> {
     codes: ErrorCodes,
     tcx: TyCtxt<'tcx>,
-    enable_per_target_ignores: bool,
     collector: RustCollector,
 }
 
 impl<'tcx> HirCollector<'tcx> {
-    pub fn new(codes: ErrorCodes, enable_per_target_ignores: bool, tcx: TyCtxt<'tcx>) -> Self {
+    pub fn new(codes: ErrorCodes, tcx: TyCtxt<'tcx>) -> Self {
         let collector = RustCollector {
             source_map: tcx.sess.psess.clone_source_map(),
             cur_path: vec![],
             position: DUMMY_SP,
             tests: vec![],
         };
-        Self { codes, enable_per_target_ignores, tcx, collector }
+        Self { codes, tcx, collector }
     }
 
     pub fn collect_crate(mut self) -> Vec<ScrapedDocTest> {
@@ -131,7 +130,6 @@ impl HirCollector<'_> {
                 &doc,
                 &mut self.collector,
                 self.codes,
-                self.enable_per_target_ignores,
                 Some(&crate::html::markdown::ExtraInfo::new(self.tcx, def_id, span)),
             );
         }
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 44134bda5ea..fc46293e7ea 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -246,7 +246,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
             match kind {
                 CodeBlockKind::Fenced(ref lang) => {
                     let parse_result =
-                        LangString::parse_without_check(lang, self.check_error_codes, false);
+                        LangString::parse_without_check(lang, self.check_error_codes);
                     if !parse_result.rust {
                         let added_classes = parse_result.added_classes;
                         let lang_string = if let Some(lang) = parse_result.unknown.first() {
@@ -707,17 +707,15 @@ pub(crate) fn find_testable_code<T: doctest::DocTestVisitor>(
     doc: &str,
     tests: &mut T,
     error_codes: ErrorCodes,
-    enable_per_target_ignores: bool,
     extra_info: Option<&ExtraInfo<'_>>,
 ) {
-    find_codes(doc, tests, error_codes, enable_per_target_ignores, extra_info, false)
+    find_codes(doc, tests, error_codes, extra_info, false)
 }
 
 pub(crate) fn find_codes<T: doctest::DocTestVisitor>(
     doc: &str,
     tests: &mut T,
     error_codes: ErrorCodes,
-    enable_per_target_ignores: bool,
     extra_info: Option<&ExtraInfo<'_>>,
     include_non_rust: bool,
 ) {
@@ -733,12 +731,7 @@ pub(crate) fn find_codes<T: doctest::DocTestVisitor>(
                         if lang.is_empty() {
                             Default::default()
                         } else {
-                            LangString::parse(
-                                lang,
-                                error_codes,
-                                enable_per_target_ignores,
-                                extra_info,
-                            )
+                            LangString::parse(lang, error_codes, extra_info)
                         }
                     }
                     CodeBlockKind::Indented => Default::default(),
@@ -1162,18 +1155,13 @@ impl Default for LangString {
 }
 
 impl LangString {
-    fn parse_without_check(
-        string: &str,
-        allow_error_code_check: ErrorCodes,
-        enable_per_target_ignores: bool,
-    ) -> Self {
-        Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
+    fn parse_without_check(string: &str, allow_error_code_check: ErrorCodes) -> Self {
+        Self::parse(string, allow_error_code_check, None)
     }
 
     fn parse(
         string: &str,
         allow_error_code_check: ErrorCodes,
-        enable_per_target_ignores: bool,
         extra: Option<&ExtraInfo<'_>>,
     ) -> Self {
         let allow_error_code_check = allow_error_code_check.as_bool();
@@ -1203,10 +1191,8 @@ impl LangString {
                     LangStringToken::LangToken(x)
                         if let Some(ignore) = x.strip_prefix("ignore-") =>
                     {
-                        if enable_per_target_ignores {
-                            ignores.push(ignore.to_owned());
-                            seen_rust_tags = !seen_other_tags;
-                        }
+                        ignores.push(ignore.to_owned());
+                        seen_rust_tags = !seen_other_tags;
                     }
                     LangStringToken::LangToken("rust") => {
                         data.rust = true;
@@ -1968,7 +1954,7 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<Rust
                     let lang_string = if syntax.is_empty() {
                         Default::default()
                     } else {
-                        LangString::parse(syntax, ErrorCodes::Yes, false, Some(extra_info))
+                        LangString::parse(syntax, ErrorCodes::Yes, Some(extra_info))
                     };
                     if !lang_string.rust {
                         continue;
diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs
index bb42b877a2c..784d0c5d21e 100644
--- a/src/librustdoc/html/markdown/tests.rs
+++ b/src/librustdoc/html/markdown/tests.rs
@@ -49,7 +49,7 @@ fn test_unique_id() {
 fn test_lang_string_parse() {
     fn t(lg: LangString) {
         let s = &lg.original;
-        assert_eq!(LangString::parse(s, ErrorCodes::Yes, true, None), lg)
+        assert_eq!(LangString::parse(s, ErrorCodes::Yes, None), lg)
     }
 
     t(Default::default());
@@ -479,7 +479,7 @@ fn test_markdown_html_escape() {
 fn test_find_testable_code_line() {
     fn t(input: &str, expect: &[usize]) {
         let mut lines = Vec::<usize>::new();
-        find_testable_code(input, &mut lines, ErrorCodes::No, false, None);
+        find_testable_code(input, &mut lines, ErrorCodes::No, None);
         assert_eq!(lines, expect);
     }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 4fe5e13c3af..44bd96a7e45 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -507,28 +507,20 @@ fn opts() -> Vec<RustcOptGroup> {
             "",
         ),
         opt(
-            Unstable,
-            FlagMulti,
-            "",
-            "enable-per-target-ignores",
-            "parse ignore-foo for ignoring doctests on a per-target basis",
-            "",
-        ),
-        opt(
-            Unstable,
+            Stable,
             Opt,
             "",
-            "runtool",
+            "test-runtool",
             "",
             "The tool to run tests with when building for a different target than host",
         ),
         opt(
-            Unstable,
+            Stable,
             Multi,
             "",
-            "runtool-arg",
+            "test-runtool-arg",
             "",
-            "One (of possibly many) arguments to pass to the runtool",
+            "One argument (of possibly many) to pass to the runtool",
         ),
         opt(
             Unstable,
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 761282bde7c..c9f0baaaa4c 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -212,7 +212,7 @@ impl DocVisitor<'_> for CoverageCalculator<'_, '_> {
                 let has_docs = !i.attrs.doc_strings.is_empty();
                 let mut tests = Tests { found_tests: 0 };
 
-                find_testable_code(&i.doc_value(), &mut tests, ErrorCodes::No, false, None);
+                find_testable_code(&i.doc_value(), &mut tests, ErrorCodes::No, None);
 
                 let has_doc_example = tests.found_tests != 0;
                 let hir_id = DocContext::as_local_hir_id(self.ctx.tcx, i.item_id).unwrap();
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index 70dbb944d4c..8028afea363 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -122,7 +122,7 @@ pub(crate) fn look_for_tests(cx: &DocContext<'_>, dox: &str, item: &Item) {
 
     let mut tests = Tests { found_tests: 0 };
 
-    find_testable_code(dox, &mut tests, ErrorCodes::No, false, None);
+    find_testable_code(dox, &mut tests, ErrorCodes::No, None);
 
     if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples() {
         if should_have_doc_example(cx, item) {
diff --git a/src/tools/miri/cargo-miri/src/main.rs b/src/tools/miri/cargo-miri/src/main.rs
index 7d9f77f3752..322ef0a6c2a 100644
--- a/src/tools/miri/cargo-miri/src/main.rs
+++ b/src/tools/miri/cargo-miri/src/main.rs
@@ -53,7 +53,7 @@ fn main() {
     //     with `RustcPhase::Rustdoc`. There we perform a check-build (needed to get the expected
     //     build failures for `compile_fail` doctests) and then store a JSON file with the
     //     information needed to run this test.
-    //   - We also set `--runtool` to ourselves, which ends up in `phase_runner` with
+    //   - We also set `--test-runtool` to ourselves, which ends up in `phase_runner` with
     //     `RunnerPhase::Rustdoc`. There we parse the JSON file written in `phase_rustc` and invoke
     //     the Miri driver for interpretation.
 
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index 71ea07f3463..cb62e12413c 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -666,8 +666,8 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
         if arg == "--extern" {
             // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
             forward_patched_extern_arg(&mut args, &mut cmd);
-        } else if arg == "--runtool" {
-            // An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
+        } else if arg == "--test-runtool" {
+            // An existing --test-runtool flag indicates cargo is running in cross-target mode, which we don't support.
             // Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
             // otherwise, we won't be called as rustdoc at all.
             show_error!("cross-interpreting doctests is not currently supported by Miri.");
@@ -693,8 +693,8 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
     // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
     cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
 
-    // The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
-    // which are disabled by default. We first need to enable them explicitly:
+    // The `--test-builder` is an unstable rustdoc features,
+    // which is disabled by default. We first need to enable them explicitly:
     cmd.arg("-Zunstable-options");
 
     // rustdoc needs to know the right sysroot.
@@ -705,7 +705,7 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
     // Make rustdoc call us back.
     let cargo_miri_path = env::current_exe().expect("current executable path invalid");
     cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
-    cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
+    cmd.arg("--test-runtool").arg(&cargo_miri_path); // invoked with just a single path argument
 
     debug_cmd("[cargo-miri rustdoc]", verbose, &cmd);
     exec(cmd)
diff --git a/tests/run-make/doctests-runtool/rmake.rs b/tests/run-make/doctests-runtool/rmake.rs
index c7be829c215..817001c514b 100644
--- a/tests/run-make/doctests-runtool/rmake.rs
+++ b/tests/run-make/doctests-runtool/rmake.rs
@@ -1,4 +1,4 @@
-// Tests behavior of rustdoc `--runtool`.
+// Tests behavior of rustdoc `--test-runtool`.
 
 use std::path::PathBuf;
 
@@ -11,7 +11,7 @@ fn mkdir(name: &str) -> PathBuf {
     dir
 }
 
-// Behavior with --runtool with relative paths and --test-run-directory.
+// Behavior with --test-runtool with relative paths and --test-run-directory.
 fn main() {
     let run_dir_name = "rundir";
     let run_dir = mkdir(run_dir_name);
@@ -27,7 +27,7 @@ fn main() {
         .arg("--test")
         .arg("--test-run-directory")
         .arg(run_dir_name)
-        .arg("--runtool")
+        .arg("--test-runtool")
         .arg(&run_tool_binary)
         .extern_("t", "libt.rlib")
         .run();
diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout
index 01f470f6e16..563f8ec50cd 100644
--- a/tests/run-make/rustdoc-default-output/output-default.stdout
+++ b/tests/run-make/rustdoc-default-output/output-default.stdout
@@ -138,12 +138,9 @@ Options:
         --show-coverage 
                         calculate percentage of public items with
                         documentation
-        --enable-per-target-ignores 
-                        parse ignore-foo for ignoring doctests on a per-target
-                        basis
-        --runtool The tool to run tests with when building for a different target than host
+        --test-runtool The tool to run tests with when building for a different target than host
                         
-        --runtool-arg One (of possibly many) arguments to pass to the runtool
+        --test-runtool-arg One argument (of possibly many) to pass to the runtool
                         
         --test-builder PATH
                         The rustc-like binary to use as the test builder
diff --git a/tests/rustdoc/doctest/auxiliary/doctest-runtool.rs b/tests/rustdoc/doctest/auxiliary/doctest-runtool.rs
new file mode 100644
index 00000000000..a21ae0664fe
--- /dev/null
+++ b/tests/rustdoc/doctest/auxiliary/doctest-runtool.rs
@@ -0,0 +1,21 @@
+// For some reason on Windows, the PATH to the libstd dylib doesn't seem to
+// carry over to running the runtool.
+//@ no-prefer-dynamic
+
+use std::path::Path;
+use std::process::Command;
+
+fn main() {
+    let args: Vec<_> = std::env::args().collect();
+    eprintln!("{args:#?}");
+    assert_eq!(args.len(), 4);
+    assert_eq!(args[1], "arg1");
+    assert_eq!(args[2], "arg2 with space");
+    let path = Path::new(&args[3]);
+    let output = Command::new(path).output().unwrap();
+    // Should fail without env var.
+    assert!(!output.status.success());
+    let output = Command::new(path).env("DOCTEST_RUNTOOL_CHECK", "xyz").output().unwrap();
+    // Should pass with env var.
+    assert!(output.status.success());
+}
diff --git a/tests/rustdoc/doctest/doctest-runtool.rs b/tests/rustdoc/doctest/doctest-runtool.rs
new file mode 100644
index 00000000000..c4fb02e5228
--- /dev/null
+++ b/tests/rustdoc/doctest/doctest-runtool.rs
@@ -0,0 +1,13 @@
+// Tests that the --test-runtool argument works.
+
+//@ ignore-cross-compile
+//@ aux-bin: doctest-runtool.rs
+//@ compile-flags: --test
+//@ compile-flags: --test-runtool=auxiliary/bin/doctest-runtool
+//@ compile-flags: --test-runtool-arg=arg1 --test-runtool-arg
+//@ compile-flags: 'arg2 with space'
+
+/// ```
+/// assert_eq!(std::env::var("DOCTEST_RUNTOOL_CHECK"), Ok("xyz".to_string()));
+/// ```
+pub fn main() {}