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());
cmd = Command::new(tool);
cmd.args(&rustdoc_options.runtool_args);
cmd.arg(output_file);
cmd.arg(&output_file);
} 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 {
cmd.current_dir(run_directory);

View File

@ -75,8 +75,9 @@ pub(crate) fn run_merged_tests(
#![allow(internal_features)]
#![feature(test)]
#![feature(rustc_attrs)]
#![feature(coverage_attribute)]\n"
.to_string();
#![feature(coverage_attribute)]
"
.to_string();
for crate_attr in &self.crate_attrs {
code.push_str(crate_attr);
@ -104,15 +105,67 @@ pub(crate) fn run_merged_tests(
code,
"\
{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]
#[coverage(off)]
fn main() {{
fn main() -> std::process::ExitCode {{
const TESTS: [test::TestDescAndFn; {nb_tests}] = [{ids}];
test::test_main(
&[{test_args}],
Vec::from(TESTS),
None,
);
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}],
Vec::from(TESTS),
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,
output = self.output,
@ -156,6 +209,10 @@ fn generate_mergeable_doctest(
} else {
writeln!(output, "mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs)
.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 {
output.push_str(&doctest.everything_else);
} else {
@ -167,14 +224,15 @@ fn generate_mergeable_doctest(
write!(
output,
"\
fn main() {returns_result} {{
{}
}}",
fn main() {returns_result} {{
{}
}}",
doctest.everything_else
)
.unwrap();
}
}
let not_running = ignore || scraped_test.langstr.no_run;
writeln!(
output,
"
@ -196,7 +254,7 @@ fn main() {returns_result} {{
}},
testfn: test::StaticTestFn(
#[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
// don't give it the function to run.
runner = if ignore || scraped_test.langstr.no_run {
"Ok::<(), String>(())"
runner = if not_running {
"test::assert_test_result(Ok::<(), String>(()))".to_string()
} 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();