rustdoc: Extract actual doctest running logic into function

This commit is contained in:
Noah Lev 2024-05-31 00:50:41 -07:00 committed by Guillaume Gomez
parent 85499ebf13
commit b7dd401a78
2 changed files with 127 additions and 76 deletions

View file

@ -179,7 +179,7 @@ pub(crate) fn run(
let opts = scrape_test_config(crate_attrs);
let enable_per_target_ignores = options.enable_per_target_ignores;
let mut collector = Collector::new(
let mut collector = CreateRunnableDoctests::new(
tcx.crate_name(LOCAL_CRATE).to_string(),
options,
opts,
@ -989,7 +989,7 @@ pub(crate) trait DoctestVisitor {
fn visit_header(&mut self, _name: &str, _level: u32) {}
}
pub(crate) struct Collector {
pub(crate) struct CreateRunnableDoctests {
pub(crate) tests: Vec<test::TestDescAndFn>,
rustdoc_options: RustdocOptions,
@ -1001,14 +1001,14 @@ pub(crate) struct Collector {
arg_file: PathBuf,
}
impl Collector {
impl CreateRunnableDoctests {
pub(crate) fn new(
crate_name: String,
rustdoc_options: RustdocOptions,
opts: GlobalTestOptions,
arg_file: PathBuf,
) -> Collector {
Collector {
) -> CreateRunnableDoctests {
CreateRunnableDoctests {
tests: Vec::new(),
rustdoc_options,
crate_name,
@ -1105,79 +1105,124 @@ impl Collector {
test_type: test::TestType::DocTest,
},
testfn: test::DynTestFn(Box::new(move || {
let report_unused_externs = |uext| {
unused_externs.lock().unwrap().push(uext);
};
let res = run_test(
&text,
&crate_name,
line,
rustdoc_test_options,
langstr,
no_run,
&opts,
edition,
path,
report_unused_externs,
);
if let Err(err) = res {
match err {
TestFailure::CompileError => {
eprint!("Couldn't compile the test.");
}
TestFailure::UnexpectedCompilePass => {
eprint!("Test compiled successfully, but it's marked `compile_fail`.");
}
TestFailure::UnexpectedRunPass => {
eprint!("Test executable succeeded, but it's marked `should_panic`.");
}
TestFailure::MissingErrorCodes(codes) => {
eprint!("Some expected error codes were not found: {codes:?}");
}
TestFailure::ExecutionError(err) => {
eprint!("Couldn't run the test: {err}");
if err.kind() == io::ErrorKind::PermissionDenied {
eprint!(" - maybe your tempdir is mounted with noexec?");
}
}
TestFailure::ExecutionFailure(out) => {
eprintln!("Test executable failed ({reason}).", reason = out.status);
// FIXME(#12309): An unfortunate side-effect of capturing the test
// executable's output is that the relative ordering between the test's
// stdout and stderr is lost. However, this is better than the
// alternative: if the test executable inherited the parent's I/O
// handles the output wouldn't be captured at all, even on success.
//
// The ordering could be preserved if the test process' stderr was
// redirected to stdout, but that functionality does not exist in the
// standard library, so it may not be portable enough.
let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
if !stdout.is_empty() || !stderr.is_empty() {
eprintln!();
if !stdout.is_empty() {
eprintln!("stdout:\n{stdout}");
}
if !stderr.is_empty() {
eprintln!("stderr:\n{stderr}");
}
}
}
}
panic::resume_unwind(Box::new(()));
}
Ok(())
doctest_run_fn(
RunnableDoctest {
crate_name,
line,
rustdoc_test_options,
langstr,
no_run,
opts,
edition,
path,
text,
},
unused_externs,
)
})),
});
}
}
/// A doctest that is ready to run.
struct RunnableDoctest {
crate_name: String,
line: usize,
rustdoc_test_options: IndividualTestOptions,
langstr: LangString,
no_run: bool,
opts: GlobalTestOptions,
edition: Edition,
path: PathBuf,
text: String,
}
fn doctest_run_fn(
test: RunnableDoctest,
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
) -> Result<(), String> {
let RunnableDoctest {
crate_name,
line,
rustdoc_test_options,
langstr,
no_run,
opts,
edition,
path,
text,
} = test;
let report_unused_externs = |uext| {
unused_externs.lock().unwrap().push(uext);
};
let res = run_test(
&text,
&crate_name,
line,
rustdoc_test_options,
langstr,
no_run,
&opts,
edition,
path,
report_unused_externs,
);
if let Err(err) = res {
match err {
TestFailure::CompileError => {
eprint!("Couldn't compile the test.");
}
TestFailure::UnexpectedCompilePass => {
eprint!("Test compiled successfully, but it's marked `compile_fail`.");
}
TestFailure::UnexpectedRunPass => {
eprint!("Test executable succeeded, but it's marked `should_panic`.");
}
TestFailure::MissingErrorCodes(codes) => {
eprint!("Some expected error codes were not found: {codes:?}");
}
TestFailure::ExecutionError(err) => {
eprint!("Couldn't run the test: {err}");
if err.kind() == io::ErrorKind::PermissionDenied {
eprint!(" - maybe your tempdir is mounted with noexec?");
}
}
TestFailure::ExecutionFailure(out) => {
eprintln!("Test executable failed ({reason}).", reason = out.status);
// FIXME(#12309): An unfortunate side-effect of capturing the test
// executable's output is that the relative ordering between the test's
// stdout and stderr is lost. However, this is better than the
// alternative: if the test executable inherited the parent's I/O
// handles the output wouldn't be captured at all, even on success.
//
// The ordering could be preserved if the test process' stderr was
// redirected to stdout, but that functionality does not exist in the
// standard library, so it may not be portable enough.
let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
if !stdout.is_empty() || !stderr.is_empty() {
eprintln!();
if !stdout.is_empty() {
eprintln!("stdout:\n{stdout}");
}
if !stderr.is_empty() {
eprintln!("stderr:\n{stderr}");
}
}
}
}
panic::resume_unwind(Box::new(()));
}
Ok(())
}
#[cfg(test)] // used in tests
impl DoctestVisitor for Vec<usize> {
fn visit_test(&mut self, _test: String, _config: LangString, line: usize) {

View file

@ -5,7 +5,9 @@ use std::fs::read_to_string;
use rustc_span::FileName;
use tempfile::tempdir;
use super::{generate_args_file, Collector, DoctestVisitor, GlobalTestOptions, ScrapedDoctest};
use super::{
generate_args_file, CreateRunnableDoctests, DoctestVisitor, GlobalTestOptions, ScrapedDoctest,
};
use crate::config::Options;
use crate::html::markdown::{find_testable_code, ErrorCodes, LangString};
@ -119,8 +121,12 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
None,
);
let mut collector =
Collector::new(options.input.filestem().to_string(), options.clone(), opts, file_path);
let mut collector = CreateRunnableDoctests::new(
options.input.filestem().to_string(),
options.clone(),
opts,
file_path,
);
md_collector.tests.into_iter().for_each(|t| collector.add_test(ScrapedDoctest::Markdown(t)));
crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests);
Ok(())