Make merged doctests run in their own process

This commit is contained in:
Guillaume Gomez 2024-06-14 00:10:30 +02:00
parent dcc77b4cbc
commit 0f0681e941
2 changed files with 87 additions and 17 deletions

View File

@ -629,9 +629,13 @@ fn drop(&mut self) {
let tool = make_maybe_absolute_path(tool.into()); let tool = make_maybe_absolute_path(tool.into());
cmd = Command::new(tool); cmd = Command::new(tool);
cmd.args(&rustdoc_options.runtool_args); cmd.args(&rustdoc_options.runtool_args);
cmd.arg(output_file); cmd.arg(&output_file);
} else { } else {
cmd = Command::new(output_file); cmd = Command::new(&output_file);
if is_multiple_tests {
cmd.arg("*doctest-bin-path");
cmd.arg(&output_file);
}
} }
if let Some(run_directory) = &rustdoc_options.test_run_directory { if let Some(run_directory) = &rustdoc_options.test_run_directory {
cmd.current_dir(run_directory); cmd.current_dir(run_directory);

View File

@ -75,7 +75,8 @@ pub(crate) fn run_merged_tests(
#![allow(internal_features)] #![allow(internal_features)]
#![feature(test)] #![feature(test)]
#![feature(rustc_attrs)] #![feature(rustc_attrs)]
#![feature(coverage_attribute)]\n" #![feature(coverage_attribute)]
"
.to_string(); .to_string();
for crate_attr in &self.crate_attrs { for crate_attr in &self.crate_attrs {
@ -104,15 +105,67 @@ pub(crate) fn run_merged_tests(
code, code,
"\ "\
{output} {output}
mod __doctest_mod {{
pub static mut BINARY_PATH: Option<std::path::PathBuf> = None;
pub const RUN_OPTION: &str = \"*doctest-inner-test\";
pub const BIN_OPTION: &str = \"*doctest-bin-path\";
#[allow(unused)]
pub fn get_doctest_path() -> Option<&'static std::path::Path> {{
unsafe {{ self::BINARY_PATH.as_deref() }}
}}
#[allow(unused)]
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> Result<(), String> {{
let out = std::process::Command::new(bin)
.arg(self::RUN_OPTION)
.arg(test_nb.to_string())
.output()
.expect(\"failed to run command\");
if !out.status.success() {{
Err(String::from_utf8_lossy(&out.stderr).to_string())
}} else {{
Ok(())
}}
}}
}}
#[rustc_main] #[rustc_main]
#[coverage(off)] #[coverage(off)]
fn main() {{ fn main() -> std::process::ExitCode {{
const TESTS: [test::TestDescAndFn; {nb_tests}] = [{ids}]; const TESTS: [test::TestDescAndFn; {nb_tests}] = [{ids}];
test::test_main( let bin_marker = std::ffi::OsStr::new(__doctest_mod::BIN_OPTION);
let test_marker = std::ffi::OsStr::new(__doctest_mod::RUN_OPTION);
let mut args = std::env::args_os().skip(1);
while let Some(arg) = args.next() {{
if arg == bin_marker {{
let Some(binary) = args.next() else {{
panic!(\"missing argument after `{{}}`\", __doctest_mod::BIN_OPTION);
}};
unsafe {{ crate::__doctest_mod::BINARY_PATH = Some(binary.into()); }}
return std::process::Termination::report(test::test_main(
&[{test_args}], &[{test_args}],
Vec::from(TESTS), Vec::from(TESTS),
None, None,
); ));
}} else if arg == test_marker {{
let Some(nb_test) = args.next() else {{
panic!(\"missing argument after `{{}}`\", __doctest_mod::RUN_OPTION);
}};
if let Some(nb_test) = nb_test.to_str().and_then(|nb| nb.parse::<usize>().ok()) {{
if let Some(test) = TESTS.get(nb_test) {{
if let test::StaticTestFn(f) = test.testfn {{
return std::process::Termination::report(f());
}}
}}
}}
panic!(\"Unexpected value after `{{}}`\", __doctest_mod::RUN_OPTION);
}}
}}
panic!(\"missing argument for merged doctest binary\");
}}", }}",
nb_tests = self.nb_tests, nb_tests = self.nb_tests,
output = self.output, output = self.output,
@ -156,6 +209,10 @@ fn generate_mergeable_doctest(
} else { } else {
writeln!(output, "mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs) writeln!(output, "mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs)
.unwrap(); .unwrap();
if scraped_test.langstr.no_run {
// To prevent having warnings about unused items since they're not called.
writeln!(output, "#![allow(unused)]").unwrap();
}
if doctest.has_main_fn { if doctest.has_main_fn {
output.push_str(&doctest.everything_else); output.push_str(&doctest.everything_else);
} else { } else {
@ -175,6 +232,7 @@ fn main() {returns_result} {{
.unwrap(); .unwrap();
} }
} }
let not_running = ignore || scraped_test.langstr.no_run;
writeln!( writeln!(
output, output,
" "
@ -196,7 +254,7 @@ fn main() {returns_result} {{
}}, }},
testfn: test::StaticTestFn( testfn: test::StaticTestFn(
#[coverage(off)] #[coverage(off)]
|| test::assert_test_result({runner}), || {{{runner}}},
) )
}}; }};
}}", }}",
@ -211,10 +269,18 @@ fn main() {returns_result} {{
}, },
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply // Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
// don't give it the function to run. // don't give it the function to run.
runner = if ignore || scraped_test.langstr.no_run { runner = if not_running {
"Ok::<(), String>(())" "test::assert_test_result(Ok::<(), String>(()))".to_string()
} else { } else {
"self::main()" format!(
"
if let Some(bin_path) = crate::__doctest_mod::get_doctest_path() {{
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}))
}} else {{
test::assert_test_result(self::main())
}}
",
)
}, },
) )
.unwrap(); .unwrap();