diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 2ab4052fedff9..33911a585bd2a 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -501,7 +501,10 @@ fn add_exe_suffix(input: String, target: &TargetTuple) -> String { input + &exe_suffix } -fn wrapped_rustc_command(rustc_wrappers: &[PathBuf], rustc_binary: &Path) -> Command { +fn wrapped_rustc_command<'a>( + rustc_wrappers: &'a [PathBuf], + rustc_binary: &'a Path, +) -> (Command, &'a Path) { let mut args = rustc_wrappers.iter().map(PathBuf::as_path).chain([rustc_binary]); let exe = args.next().expect("unable to create rustc command"); @@ -510,7 +513,7 @@ fn wrapped_rustc_command(rustc_wrappers: &[PathBuf], rustc_binary: &Path) -> Com command.arg(arg); } - command + (command, rustc_wrappers.first().map(|p| &**p).unwrap_or(rustc_binary)) } /// Information needed for running a bundle of doctests. @@ -630,7 +633,8 @@ fn run_test( .test_builder .as_deref() .unwrap_or_else(|| rustc_interface::util::rustc_path(sysroot).expect("found rustc")); - let mut compiler = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); + let (mut compiler, binary_path) = + wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); compiler.args(&compiler_args); @@ -671,7 +675,13 @@ fn run_test( debug!("compiler invocation for doctest: {compiler:?}"); - let mut child = compiler.spawn().expect("Failed to spawn rustc process"); + let mut child = match compiler.spawn() { + Ok(child) => child, + Err(error) => { + eprintln!("Failed to spawn {binary_path:?}: {error:?}"); + return (Duration::default(), Err(TestFailure::CompileError)); + } + }; let output = if let Some(merged_test_code) = &doctest.merged_test_code { // compile-fail tests never get merged, so this should always pass let status = child.wait().expect("Failed to wait"); @@ -680,7 +690,7 @@ fn run_test( // build it now let runner_input_file = doctest.path_for_merged_doctest_runner(); - let mut runner_compiler = + let (mut runner_compiler, binary_path) = wrapped_rustc_command(&rustdoc_options.test_builder_wrappers, rustc_binary); // the test runner does not contain any user-written code, so this doesn't allow // the user to exploit nightly-only features on stable @@ -733,7 +743,13 @@ fn run_test( let status = if !status.success() { status } else { - let mut child_runner = runner_compiler.spawn().expect("Failed to spawn rustc process"); + let mut child_runner = match runner_compiler.spawn() { + Ok(child) => child, + Err(error) => { + eprintln!("Failed to spawn {binary_path:?}: {error:?}"); + return (Duration::default(), Err(TestFailure::CompileError)); + } + }; child_runner.wait().expect("Failed to wait") }; diff --git a/tests/run-make/rustdoc-test-builder/foo.rs b/tests/run-make/rustdoc-test-builder/foo.rs new file mode 100644 index 0000000000000..51d17849fd718 --- /dev/null +++ b/tests/run-make/rustdoc-test-builder/foo.rs @@ -0,0 +1,3 @@ +//! ``` +//! let x = 12; +//! ``` diff --git a/tests/run-make/rustdoc-test-builder/rmake.rs b/tests/run-make/rustdoc-test-builder/rmake.rs new file mode 100644 index 0000000000000..818cfb243fa6b --- /dev/null +++ b/tests/run-make/rustdoc-test-builder/rmake.rs @@ -0,0 +1,22 @@ +// This test ensures that if the rustdoc test binary is not executable, it will +// gracefully fail and not panic. + +//@ needs-target-std + +use run_make_support::{path, rfs, rustdoc}; + +fn main() { + let absolute_path = path("foo.rs").canonicalize().expect("failed to get absolute path"); + let output = rustdoc() + .input("foo.rs") + .arg("--test") + .arg("-Zunstable-options") + .arg("--test-builder") + .arg(&absolute_path) + .run_fail(); + + output.assert_stdout_contains("Failed to spawn "); + output.assert_stderr_not_contains("the compiler unexpectedly panicked. this is a bug."); + // Just in case... + output.assert_stdout_not_contains("the compiler unexpectedly panicked. this is a bug."); +}