From 0d3ef800b9d80fde90cd50bacd1dcf08eb02f682 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Thu, 21 Mar 2024 21:11:19 +0100
Subject: [PATCH 1/6] `rustdoc --test`: Prevent reaching the maximum size of
 command-line by using files for arguments if there are too many

---
 src/librustdoc/doctest.rs  | 98 +++++++++++++++++++++++++++-----------
 src/librustdoc/lib.rs      |  4 +-
 src/librustdoc/markdown.rs | 11 ++++-
 3 files changed, 82 insertions(+), 31 deletions(-)

diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index c6eb7be08cd81..bbd54fa012e9e 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -23,6 +23,7 @@ use rustc_target::spec::{Target, TargetTriple};
 use tempfile::Builder as TempFileBuilder;
 
 use std::env;
+use std::fs::File;
 use std::io::{self, Write};
 use std::panic;
 use std::path::{Path, PathBuf};
@@ -31,6 +32,8 @@ use std::str;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 
+use tempfile::tempdir;
+
 use crate::clean::{types::AttributesExt, Attributes};
 use crate::config::Options as RustdocOptions;
 use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
@@ -48,7 +51,51 @@ pub(crate) struct GlobalTestOptions {
     pub(crate) attrs: Vec<String>,
 }
 
-pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
+pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> Result<(), String> {
+    let mut file = File::create(file_path)
+        .map_err(|error| format!("failed to create args file: {error:?}"))?;
+
+    // We now put the common arguments into the file we created.
+    let mut content = vec!["--crate-type=bin".to_string()];
+
+    for cfg in &options.cfgs {
+        content.push(format!("--cfg={cfg}"));
+    }
+    if !options.check_cfgs.is_empty() {
+        content.push("-Zunstable-options".to_string());
+        for check_cfg in &options.check_cfgs {
+            content.push(format!("--check-cfg={check_cfg}"));
+        }
+    }
+
+    if let Some(sysroot) = &options.maybe_sysroot {
+        content.push(format!("--sysroot={}", sysroot.display()));
+    }
+    for lib_str in &options.lib_strs {
+        content.push(format!("-L{lib_str}"));
+    }
+    for extern_str in &options.extern_strs {
+        content.push(format!("--extern={extern_str}"));
+    }
+    content.push("-Ccodegen-units=1".to_string());
+    for codegen_options_str in &options.codegen_options_strs {
+        content.push(format!("-C{codegen_options_str}"));
+    }
+    for unstable_option_str in &options.unstable_opts_strs {
+        content.push(format!("-Z{unstable_option_str}"));
+    }
+
+    let content = content.join("\n");
+
+    file.write(content.as_bytes())
+        .map_err(|error| format!("failed to write arguments to temporary file: {error:?}"))?;
+    Ok(())
+}
+
+pub(crate) fn run(
+    dcx: &rustc_errors::DiagCtxt,
+    options: RustdocOptions,
+) -> Result<(), ErrorGuaranteed> {
     let input = config::Input::File(options.input.clone());
 
     let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
@@ -118,6 +165,15 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
     let externs = options.externs.clone();
     let json_unused_externs = options.json_unused_externs;
 
+    let temp_dir = match tempdir()
+        .map_err(|error| format!("failed to create temporary directory: {error:?}"))
+    {
+        Ok(temp_dir) => temp_dir,
+        Err(error) => return crate::wrap_return(dcx, Err(error)),
+    };
+    let file_path = temp_dir.path().join("rustdoc-cfgs");
+    crate::wrap_return(dcx, generate_args_file(&file_path, &options))?;
+
     let (tests, unused_extern_reports, compiling_test_count) =
         interface::run_compiler(config, |compiler| {
             compiler.enter(|queries| {
@@ -134,6 +190,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
                         Some(compiler.sess.psess.clone_source_map()),
                         None,
                         enable_per_target_ignores,
+                        file_path,
                     );
 
                     let mut hir_collector = HirCollector {
@@ -334,6 +391,7 @@ fn run_test(
     path: PathBuf,
     test_id: &str,
     report_unused_externs: impl Fn(UnusedExterns),
+    arg_file: PathBuf,
 ) -> Result<(), TestFailure> {
     let (test, line_offset, supports_color) =
         make_test(test, Some(crate_name), lang_string.test_harness, opts, edition, Some(test_id));
@@ -347,19 +405,9 @@ fn run_test(
         .as_deref()
         .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
     let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary);
-    compiler.arg("--crate-type").arg("bin");
-    for cfg in &rustdoc_options.cfgs {
-        compiler.arg("--cfg").arg(&cfg);
-    }
-    if !rustdoc_options.check_cfgs.is_empty() {
-        compiler.arg("-Z").arg("unstable-options");
-        for check_cfg in &rustdoc_options.check_cfgs {
-            compiler.arg("--check-cfg").arg(&check_cfg);
-        }
-    }
-    if let Some(sysroot) = rustdoc_options.maybe_sysroot {
-        compiler.arg("--sysroot").arg(sysroot);
-    }
+
+    compiler.arg(&format!("@{}", arg_file.display()));
+
     compiler.arg("--edition").arg(&edition.to_string());
     compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
     compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
@@ -370,22 +418,10 @@ fn run_test(
     if rustdoc_options.json_unused_externs.is_enabled() && !lang_string.compile_fail {
         compiler.arg("--error-format=json");
         compiler.arg("--json").arg("unused-externs");
-        compiler.arg("-Z").arg("unstable-options");
         compiler.arg("-W").arg("unused_crate_dependencies");
+        compiler.arg("-Z").arg("unstable-options");
     }
-    for lib_str in &rustdoc_options.lib_strs {
-        compiler.arg("-L").arg(&lib_str);
-    }
-    for extern_str in &rustdoc_options.extern_strs {
-        compiler.arg("--extern").arg(&extern_str);
-    }
-    compiler.arg("-Ccodegen-units=1");
-    for codegen_options_str in &rustdoc_options.codegen_options_strs {
-        compiler.arg("-C").arg(&codegen_options_str);
-    }
-    for unstable_option_str in &rustdoc_options.unstable_opts_strs {
-        compiler.arg("-Z").arg(&unstable_option_str);
-    }
+
     if no_run && !lang_string.compile_fail && rustdoc_options.persist_doctests.is_none() {
         compiler.arg("--emit=metadata");
     }
@@ -941,6 +977,7 @@ pub(crate) struct Collector {
     visited_tests: FxHashMap<(String, usize), usize>,
     unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
     compiling_test_count: AtomicUsize,
+    arg_file: PathBuf,
 }
 
 impl Collector {
@@ -952,6 +989,7 @@ impl Collector {
         source_map: Option<Lrc<SourceMap>>,
         filename: Option<PathBuf>,
         enable_per_target_ignores: bool,
+        arg_file: PathBuf,
     ) -> Collector {
         Collector {
             tests: Vec::new(),
@@ -967,6 +1005,7 @@ impl Collector {
             visited_tests: FxHashMap::default(),
             unused_extern_reports: Default::default(),
             compiling_test_count: AtomicUsize::new(0),
+            arg_file,
         }
     }
 
@@ -1067,6 +1106,8 @@ impl Tester for Collector {
             )
         };
 
+        let arg_file = self.arg_file.clone();
+
         debug!("creating test {name}: {test}");
         self.tests.push(test::TestDescAndFn {
             desc: test::TestDesc {
@@ -1108,6 +1149,7 @@ impl Tester for Collector {
                     path,
                     &test_id,
                     report_unused_externs,
+                    arg_file,
                 );
 
                 if let Err(err) = res {
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 18651875130fc..b78fb4c4eee22 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -664,7 +664,7 @@ fn usage(argv0: &str) {
 /// A result type used by several functions under `main()`.
 type MainResult = Result<(), ErrorGuaranteed>;
 
-fn wrap_return(dcx: &rustc_errors::DiagCtxt, res: Result<(), String>) -> MainResult {
+pub(crate) fn wrap_return(dcx: &rustc_errors::DiagCtxt, res: Result<(), String>) -> MainResult {
     match res {
         Ok(()) => dcx.has_errors().map_or(Ok(()), Err),
         Err(err) => Err(dcx.err(err)),
@@ -731,7 +731,7 @@ fn main_args(
 
     match (options.should_test, options.markdown_input()) {
         (true, true) => return wrap_return(&diag, markdown::test(options)),
-        (true, false) => return doctest::run(options),
+        (true, false) => return doctest::run(&diag, options),
         (false, true) => {
             let input = options.input.clone();
             let edition = options.edition;
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index b661ced016917..dcd2cf02a30a3 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -3,11 +3,13 @@ use std::fs::{create_dir_all, read_to_string, File};
 use std::io::prelude::*;
 use std::path::Path;
 
+use tempfile::tempdir;
+
 use rustc_span::edition::Edition;
 use rustc_span::DUMMY_SP;
 
 use crate::config::{Options, RenderOptions};
-use crate::doctest::{Collector, GlobalTestOptions};
+use crate::doctest::{generate_args_file, Collector, GlobalTestOptions};
 use crate::html::escape::Escape;
 use crate::html::markdown;
 use crate::html::markdown::{
@@ -146,6 +148,12 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
         .map_err(|err| format!("{input}: {err}", input = options.input.display()))?;
     let mut opts = GlobalTestOptions::default();
     opts.no_crate_inject = true;
+
+    let temp_dir =
+        tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
+    let file_path = temp_dir.path().join("rustdoc-cfgs");
+    generate_args_file(&file_path, &options)?;
+
     let mut collector = Collector::new(
         options.input.display().to_string(),
         options.clone(),
@@ -154,6 +162,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
         None,
         Some(options.input),
         options.enable_per_target_ignores,
+        file_path,
     );
     collector.set_position(DUMMY_SP);
     let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());

From 33735d5913fbd8f92d4c5b5306a5d23030710d5c Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Fri, 22 Mar 2024 14:46:02 +0100
Subject: [PATCH 2/6] Prevent cloning more than necessary `RustdocOptions`
 fields

---
 src/librustdoc/doctest.rs | 125 +++++++++++++++++++++++---------------
 1 file changed, 77 insertions(+), 48 deletions(-)

diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index bbd54fa012e9e..1e1731cbe4a06 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -379,26 +379,26 @@ fn run_test(
     test: &str,
     crate_name: &str,
     line: usize,
-    rustdoc_options: RustdocOptions,
+    rustdoc_options: RustdocTestOptions,
     mut lang_string: LangString,
     no_run: bool,
-    runtool: Option<String>,
-    runtool_args: Vec<String>,
-    target: TargetTriple,
     opts: &GlobalTestOptions,
     edition: Edition,
-    outdir: DirState,
     path: PathBuf,
-    test_id: &str,
     report_unused_externs: impl Fn(UnusedExterns),
-    arg_file: PathBuf,
 ) -> Result<(), TestFailure> {
-    let (test, line_offset, supports_color) =
-        make_test(test, Some(crate_name), lang_string.test_harness, opts, edition, Some(test_id));
+    let (test, line_offset, supports_color) = make_test(
+        test,
+        Some(crate_name),
+        lang_string.test_harness,
+        opts,
+        edition,
+        Some(&rustdoc_options.test_id),
+    );
 
     // Make sure we emit well-formed executable names for our target.
-    let rust_out = add_exe_suffix("rust_out".to_owned(), &target);
-    let output_file = outdir.path().join(rust_out);
+    let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target);
+    let output_file = rustdoc_options.outdir.path().join(rust_out);
 
     let rustc_binary = rustdoc_options
         .test_builder
@@ -406,7 +406,7 @@ fn run_test(
         .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
     let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary);
 
-    compiler.arg(&format!("@{}", arg_file.display()));
+    compiler.arg(&format!("@{}", rustdoc_options.arg_file.display()));
 
     compiler.arg("--edition").arg(&edition.to_string());
     compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
@@ -415,17 +415,17 @@ fn run_test(
     if lang_string.test_harness {
         compiler.arg("--test");
     }
-    if rustdoc_options.json_unused_externs.is_enabled() && !lang_string.compile_fail {
+    if rustdoc_options.is_json_unused_externs_enabled && !lang_string.compile_fail {
         compiler.arg("--error-format=json");
         compiler.arg("--json").arg("unused-externs");
         compiler.arg("-W").arg("unused_crate_dependencies");
         compiler.arg("-Z").arg("unstable-options");
     }
 
-    if no_run && !lang_string.compile_fail && rustdoc_options.persist_doctests.is_none() {
+    if no_run && !lang_string.compile_fail && rustdoc_options.should_persist_doctests {
         compiler.arg("--emit=metadata");
     }
-    compiler.arg("--target").arg(match target {
+    compiler.arg("--target").arg(match rustdoc_options.target {
         TargetTriple::TargetTriple(s) => s,
         TargetTriple::TargetJson { path_for_rustdoc, .. } => {
             path_for_rustdoc.to_str().expect("target path must be valid unicode").to_string()
@@ -521,10 +521,10 @@ fn run_test(
     let mut cmd;
 
     let output_file = make_maybe_absolute_path(output_file);
-    if let Some(tool) = runtool {
+    if let Some(tool) = rustdoc_options.runtool {
         let tool = make_maybe_absolute_path(tool.into());
         cmd = Command::new(tool);
-        cmd.args(runtool_args);
+        cmd.args(rustdoc_options.runtool_args);
         cmd.arg(output_file);
     } else {
         cmd = Command::new(output_file);
@@ -933,6 +933,61 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
     (before, after, crates)
 }
 
+pub(crate) struct RustdocTestOptions {
+    test_builder: Option<PathBuf>,
+    test_builder_wrappers: Vec<PathBuf>,
+    is_json_unused_externs_enabled: bool,
+    should_persist_doctests: bool,
+    error_format: ErrorOutputType,
+    test_run_directory: Option<PathBuf>,
+    nocapture: bool,
+    arg_file: PathBuf,
+    outdir: DirState,
+    runtool: Option<String>,
+    runtool_args: Vec<String>,
+    target: TargetTriple,
+    test_id: String,
+}
+
+impl RustdocTestOptions {
+    fn new(options: &RustdocOptions, arg_file: &Path, test_id: String) -> Self {
+        let outdir = if let Some(ref path) = options.persist_doctests {
+            let mut path = path.clone();
+            path.push(&test_id);
+
+            if let Err(err) = std::fs::create_dir_all(&path) {
+                eprintln!("Couldn't create directory for doctest executables: {err}");
+                panic::resume_unwind(Box::new(()));
+            }
+
+            DirState::Perm(path)
+        } else {
+            DirState::Temp(
+                TempFileBuilder::new()
+                    .prefix("rustdoctest")
+                    .tempdir()
+                    .expect("rustdoc needs a tempdir"),
+            )
+        };
+
+        Self {
+            test_builder: options.test_builder.clone(),
+            test_builder_wrappers: options.test_builder_wrappers.clone(),
+            is_json_unused_externs_enabled: options.json_unused_externs.is_enabled(),
+            should_persist_doctests: options.persist_doctests.is_none(),
+            error_format: options.error_format,
+            test_run_directory: options.test_run_directory.clone(),
+            nocapture: options.nocapture,
+            arg_file: arg_file.into(),
+            outdir,
+            runtool: options.runtool.clone(),
+            runtool_args: options.runtool_args.clone(),
+            target: options.target.clone(),
+            test_id,
+        }
+    }
+}
+
 pub(crate) trait Tester {
     fn add_test(&mut self, test: String, config: LangString, line: usize);
     fn get_line(&self) -> usize {
@@ -1048,13 +1103,9 @@ impl Tester for Collector {
         let crate_name = self.crate_name.clone();
         let opts = self.opts.clone();
         let edition = config.edition.unwrap_or(self.rustdoc_options.edition);
-        let rustdoc_options = self.rustdoc_options.clone();
-        let runtool = self.rustdoc_options.runtool.clone();
-        let runtool_args = self.rustdoc_options.runtool_args.clone();
-        let target = self.rustdoc_options.target.clone();
-        let target_str = target.to_string();
+        let target_str = self.rustdoc_options.target.to_string();
         let unused_externs = self.unused_extern_reports.clone();
-        let no_run = config.no_run || rustdoc_options.no_run;
+        let no_run = config.no_run || self.rustdoc_options.no_run;
         if !config.compile_fail {
             self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
         }
@@ -1088,25 +1139,9 @@ impl Tester for Collector {
                 self.visited_tests.entry((file.clone(), line)).and_modify(|v| *v += 1).or_insert(0)
             },
         );
-        let outdir = if let Some(mut path) = rustdoc_options.persist_doctests.clone() {
-            path.push(&test_id);
-
-            if let Err(err) = std::fs::create_dir_all(&path) {
-                eprintln!("Couldn't create directory for doctest executables: {err}");
-                panic::resume_unwind(Box::new(()));
-            }
-
-            DirState::Perm(path)
-        } else {
-            DirState::Temp(
-                TempFileBuilder::new()
-                    .prefix("rustdoctest")
-                    .tempdir()
-                    .expect("rustdoc needs a tempdir"),
-            )
-        };
 
-        let arg_file = self.arg_file.clone();
+        let rustdoc_test_options =
+            RustdocTestOptions::new(&self.rustdoc_options, &self.arg_file, test_id);
 
         debug!("creating test {name}: {test}");
         self.tests.push(test::TestDescAndFn {
@@ -1137,19 +1172,13 @@ impl Tester for Collector {
                     &test,
                     &crate_name,
                     line,
-                    rustdoc_options,
+                    rustdoc_test_options,
                     config,
                     no_run,
-                    runtool,
-                    runtool_args,
-                    target,
                     &opts,
                     edition,
-                    outdir,
                     path,
-                    &test_id,
                     report_unused_externs,
-                    arg_file,
                 );
 
                 if let Err(err) = res {

From 86fe40021fdac50ed64e04cdae12f0156e5a8908 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Sat, 23 Mar 2024 14:52:49 +0100
Subject: [PATCH 3/6] Put temporary directory into one common function

---
 src/librustdoc/doctest.rs | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 1e1731cbe4a06..fa4b1d4fbfece 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -20,7 +20,6 @@ use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::sym;
 use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
 use rustc_target::spec::{Target, TargetTriple};
-use tempfile::Builder as TempFileBuilder;
 
 use std::env;
 use std::fs::File;
@@ -32,7 +31,7 @@ use std::str;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 
-use tempfile::tempdir;
+use tempfile::{Builder as TempFileBuilder, TempDir};
 
 use crate::clean::{types::AttributesExt, Attributes};
 use crate::config::Options as RustdocOptions;
@@ -92,6 +91,10 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) ->
     Ok(())
 }
 
+fn get_doctest_dir() -> io::Result<TempDir> {
+    TempFileBuilder::new().prefix("rustdoctest").tempdir()
+}
+
 pub(crate) fn run(
     dcx: &rustc_errors::DiagCtxt,
     options: RustdocOptions,
@@ -165,7 +168,7 @@ pub(crate) fn run(
     let externs = options.externs.clone();
     let json_unused_externs = options.json_unused_externs;
 
-    let temp_dir = match tempdir()
+    let temp_dir = match get_doctest_dir()
         .map_err(|error| format!("failed to create temporary directory: {error:?}"))
     {
         Ok(temp_dir) => temp_dir,
@@ -962,12 +965,7 @@ impl RustdocTestOptions {
 
             DirState::Perm(path)
         } else {
-            DirState::Temp(
-                TempFileBuilder::new()
-                    .prefix("rustdoctest")
-                    .tempdir()
-                    .expect("rustdoc needs a tempdir"),
-            )
+            DirState::Temp(get_doctest_dir().expect("rustdoc needs a tempdir"))
         };
 
         Self {

From 773084ff2f337774c6bca54eba1b2e88d63465d3 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Sat, 23 Mar 2024 15:00:37 +0100
Subject: [PATCH 4/6] Rename `RustdocTestOptions` into `IndividualTestOptions`

---
 src/librustdoc/doctest.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index fa4b1d4fbfece..211921715b00c 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -382,7 +382,7 @@ fn run_test(
     test: &str,
     crate_name: &str,
     line: usize,
-    rustdoc_options: RustdocTestOptions,
+    rustdoc_options: IndividualTestOptions,
     mut lang_string: LangString,
     no_run: bool,
     opts: &GlobalTestOptions,
@@ -936,7 +936,7 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
     (before, after, crates)
 }
 
-pub(crate) struct RustdocTestOptions {
+pub(crate) struct IndividualTestOptions {
     test_builder: Option<PathBuf>,
     test_builder_wrappers: Vec<PathBuf>,
     is_json_unused_externs_enabled: bool,
@@ -952,7 +952,7 @@ pub(crate) struct RustdocTestOptions {
     test_id: String,
 }
 
-impl RustdocTestOptions {
+impl IndividualTestOptions {
     fn new(options: &RustdocOptions, arg_file: &Path, test_id: String) -> Self {
         let outdir = if let Some(ref path) = options.persist_doctests {
             let mut path = path.clone();
@@ -1139,7 +1139,7 @@ impl Tester for Collector {
         );
 
         let rustdoc_test_options =
-            RustdocTestOptions::new(&self.rustdoc_options, &self.arg_file, test_id);
+            IndividualTestOptions::new(&self.rustdoc_options, &self.arg_file, test_id);
 
         debug!("creating test {name}: {test}");
         self.tests.push(test::TestDescAndFn {

From 5fab0162cca0bc55a93203f8ce127d026273a70f Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Sat, 23 Mar 2024 15:46:09 +0100
Subject: [PATCH 5/6] Add `Rustdoc` into `run-make-support`

---
 src/tools/run-make-support/src/lib.rs | 41 ++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index e5e7b559c9292..419b04231b572 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -9,8 +9,8 @@ pub fn out_dir() -> PathBuf {
     env::var_os("TMPDIR").unwrap().into()
 }
 
-fn setup_common_build_cmd() -> Command {
-    let rustc = env::var("RUSTC").unwrap();
+fn setup_common_build_cmd(command: &str) -> Command {
+    let rustc = env::var(command).unwrap();
     let mut cmd = Command::new(rustc);
     cmd.arg("--out-dir").arg(out_dir()).arg("-L").arg(out_dir());
     cmd
@@ -33,6 +33,10 @@ pub fn aux_build() -> AuxBuildInvocationBuilder {
     AuxBuildInvocationBuilder::new()
 }
 
+pub fn rustdoc() -> Rustdoc {
+    Rustdoc::new()
+}
+
 #[derive(Debug)]
 pub struct RustcInvocationBuilder {
     cmd: Command,
@@ -40,7 +44,7 @@ pub struct RustcInvocationBuilder {
 
 impl RustcInvocationBuilder {
     fn new() -> Self {
-        let cmd = setup_common_build_cmd();
+        let cmd = setup_common_build_cmd("RUSTC");
         Self { cmd }
     }
 
@@ -74,7 +78,7 @@ pub struct AuxBuildInvocationBuilder {
 
 impl AuxBuildInvocationBuilder {
     fn new() -> Self {
-        let mut cmd = setup_common_build_cmd();
+        let mut cmd = setup_common_build_cmd("RUSTC");
         cmd.arg("--crate-type=lib");
         Self { cmd }
     }
@@ -97,6 +101,35 @@ impl AuxBuildInvocationBuilder {
     }
 }
 
+#[derive(Debug)]
+pub struct Rustdoc {
+    cmd: Command,
+}
+
+impl Rustdoc {
+    fn new() -> Self {
+        let cmd = setup_common_build_cmd("RUSTDOC");
+        Self { cmd }
+    }
+
+    pub fn arg(&mut self, arg: &str) -> &mut Self {
+        self.cmd.arg(arg);
+        self
+    }
+
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+}
+
 fn run_common(bin_name: &str) -> (Command, Output) {
     let target = env::var("TARGET").unwrap();
 

From bc4f1697fad9a12d5096e63f45890ff289a3c349 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Sat, 23 Mar 2024 15:48:44 +0100
Subject: [PATCH 6/6] Add regression test for #122722

---
 tests/run-make/rustdoc-test-args/foo.rs   |  3 +++
 tests/run-make/rustdoc-test-args/rmake.rs | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+)
 create mode 100644 tests/run-make/rustdoc-test-args/foo.rs
 create mode 100644 tests/run-make/rustdoc-test-args/rmake.rs

diff --git a/tests/run-make/rustdoc-test-args/foo.rs b/tests/run-make/rustdoc-test-args/foo.rs
new file mode 100644
index 0000000000000..51d17849fd718
--- /dev/null
+++ b/tests/run-make/rustdoc-test-args/foo.rs
@@ -0,0 +1,3 @@
+//! ```
+//! let x = 12;
+//! ```
diff --git a/tests/run-make/rustdoc-test-args/rmake.rs b/tests/run-make/rustdoc-test-args/rmake.rs
new file mode 100644
index 0000000000000..808d13928ebdb
--- /dev/null
+++ b/tests/run-make/rustdoc-test-args/rmake.rs
@@ -0,0 +1,18 @@
+extern crate run_make_support;
+
+use run_make_support::{out_dir, rustdoc};
+use std::{fs, iter};
+use std::path::Path;
+
+fn generate_a_lot_of_cfgs(path: &Path) {
+    let content = iter::repeat("--cfg=a\n").take(100_000).collect::<String>();
+    fs::write(path, content.as_bytes()).expect("failed to create args file");
+}
+
+fn main() {
+    let arg_file = out_dir().join("args");
+    generate_a_lot_of_cfgs(&arg_file);
+
+    let arg_file = format!("@{}", arg_file.display());
+    rustdoc().arg("--test").arg(&arg_file).arg("foo.rs").run();
+}