Split doctests between standalone and mergeable ones

This commit is contained in:
Guillaume Gomez 2024-06-08 17:32:51 +02:00
parent 7d72482431
commit 39f029a852
5 changed files with 61 additions and 29 deletions

View file

@ -164,7 +164,8 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
let args_path = temp_dir.path().join("rustdoc-cfgs");
crate::wrap_return(dcx, generate_args_file(&args_path, &options))?;
let (tests, unused_extern_reports, compiling_test_count) =
// FIXME: use mergeable tests!
let (standalone_tests, unused_extern_reports, compiling_test_count) =
interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
let collector = queries.global_ctxt()?.enter(|tcx| {
@ -192,11 +193,11 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
let unused_extern_reports = collector.unused_extern_reports.clone();
let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
Ok((collector.tests, unused_extern_reports, compiling_test_count))
Ok((collector.standalone_tests, unused_extern_reports, compiling_test_count))
})
})?;
run_tests(test_args, nocapture, tests);
run_tests(test_args, nocapture, standalone_tests);
// Collect and warn about unused externs, but only if we've gotten
// reports for each doctest
@ -617,7 +618,8 @@ pub(crate) trait DoctestVisitor {
}
struct CreateRunnableDoctests {
tests: Vec<test::TestDescAndFn>,
standalone_tests: Vec<test::TestDescAndFn>,
mergeable_tests: FxHashMap<Edition, Vec<(DocTest, ScrapedDoctest)>>,
rustdoc_options: Arc<RustdocOptions>,
opts: GlobalTestOptions,
@ -629,7 +631,8 @@ struct CreateRunnableDoctests {
impl CreateRunnableDoctests {
fn new(rustdoc_options: RustdocOptions, opts: GlobalTestOptions) -> CreateRunnableDoctests {
CreateRunnableDoctests {
tests: Vec::new(),
standalone_tests: Vec::new(),
mergeable_tests: FxHashMap::default(),
rustdoc_options: Arc::new(rustdoc_options),
opts,
visited_tests: FxHashMap::default(),
@ -647,16 +650,40 @@ impl CreateRunnableDoctests {
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly())
}
fn add_test(&mut self, test: ScrapedDoctest) {
let name = self.generate_name(&test.filename, test.line, &test.logical_path);
fn add_test(&mut self, scraped_test: ScrapedDoctest) {
let edition = scraped_test.edition(&self.rustdoc_options);
let doctest = DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition);
let is_standalone = scraped_test.langstr.compile_fail
|| scraped_test.langstr.test_harness
|| self.rustdoc_options.nocapture
|| self.rustdoc_options.test_args.iter().any(|arg| arg == "--show-output")
|| doctest.crate_attrs.contains("#![no_std]");
if is_standalone {
let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test);
self.standalone_tests.push(test_desc);
} else {
self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test));
}
}
fn generate_test_desc_and_fn(
&mut self,
test: DocTest,
scraped_test: ScrapedDoctest,
) -> test::TestDescAndFn {
let name = self.generate_name(
&scraped_test.filename,
scraped_test.line,
&scraped_test.logical_path,
);
let opts = self.opts.clone();
let target_str = self.rustdoc_options.target.to_string();
let unused_externs = self.unused_extern_reports.clone();
if !test.langstr.compile_fail {
if !scraped_test.langstr.compile_fail {
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
}
let path = match &test.filename {
let path = match &scraped_test.filename {
FileName::Real(path) => {
if let Some(local_path) = path.local_path() {
local_path.to_path_buf()
@ -669,7 +696,7 @@ impl CreateRunnableDoctests {
};
// For example `module/file.rs` would become `module_file_rs`
let file = test
let file = scraped_test
.filename
.prefer_local()
.to_string_lossy()
@ -679,12 +706,12 @@ impl CreateRunnableDoctests {
let test_id = format!(
"{file}_{line}_{number}",
file = file,
line = test.line,
line = scraped_test.line,
number = {
// Increases the current test number, if this file already
// exists or it creates a new entry with a test number of 0.
self.visited_tests
.entry((file.clone(), test.line))
.entry((file.clone(), scraped_test.line))
.and_modify(|v| *v += 1)
.or_insert(0)
},
@ -693,11 +720,11 @@ impl CreateRunnableDoctests {
let rustdoc_options = self.rustdoc_options.clone();
let rustdoc_test_options = IndividualTestOptions::new(&self.rustdoc_options, test_id, path);
debug!("creating test {name}: {}", test.text);
self.tests.push(test::TestDescAndFn {
debug!("creating test {name}: {}", scraped_test.text);
test::TestDescAndFn {
desc: test::TestDesc {
name: test::DynTestName(name),
ignore: match test.langstr.ignore {
ignore: match scraped_test.langstr.ignore {
Ignore::All => true,
Ignore::None => false,
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
@ -710,20 +737,28 @@ impl CreateRunnableDoctests {
end_col: 0,
// compiler failures are test failures
should_panic: test::ShouldPanic::No,
compile_fail: test.langstr.compile_fail,
no_run: test.no_run(&rustdoc_options),
compile_fail: scraped_test.langstr.compile_fail,
no_run: scraped_test.no_run(&rustdoc_options),
test_type: test::TestType::DocTest,
},
testfn: test::DynTestFn(Box::new(move || {
doctest_run_fn(rustdoc_test_options, opts, test, rustdoc_options, unused_externs)
doctest_run_fn(
rustdoc_test_options,
opts,
test,
scraped_test,
rustdoc_options,
unused_externs,
)
})),
});
}
}
}
fn doctest_run_fn(
test_opts: IndividualTestOptions,
global_opts: GlobalTestOptions,
doctest: DocTest,
scraped_test: ScrapedDoctest,
rustdoc_options: Arc<RustdocOptions>,
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
@ -731,9 +766,8 @@ fn doctest_run_fn(
let report_unused_externs = |uext| {
unused_externs.lock().unwrap().push(uext);
};
let edition = scraped_test.edition(&rustdoc_options);
let doctest = DocTest::new(&scraped_test.text, Some(&global_opts.crate_name), edition);
let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
&scraped_test.text,
scraped_test.langstr.test_harness,
&global_opts,
Some(&test_opts.test_id),

View file

@ -18,7 +18,6 @@ use rustc_span::{FileName, Span, DUMMY_SP};
use super::GlobalTestOptions;
pub(crate) struct DocTest {
pub(crate) test_code: String,
pub(crate) supports_color: bool,
pub(crate) already_has_extern_crate: bool,
pub(crate) main_fn_span: Option<Span>,
@ -40,7 +39,6 @@ impl DocTest {
// If the parser panicked due to a fatal error, pass the test code through unchanged.
// The error will be reported during compilation.
return DocTest {
test_code: source.to_string(),
supports_color: false,
main_fn_span: None,
crate_attrs,
@ -50,7 +48,6 @@ impl DocTest {
};
};
Self {
test_code: source.to_string(),
supports_color,
main_fn_span,
crate_attrs,
@ -64,6 +61,7 @@ impl DocTest {
/// lines before the test code begins.
pub(crate) fn generate_unique_doctest(
&self,
test_code: &str,
dont_insert_main: bool,
opts: &GlobalTestOptions,
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
@ -103,7 +101,7 @@ impl DocTest {
// NOTE: this is terribly inaccurate because it doesn't actually
// parse the source, but only has false positives, not false
// negatives.
self.test_code.contains(crate_name)
test_code.contains(crate_name)
{
// rustdoc implicitly inserts an `extern crate` item for the own crate
// which may be unused, so we need to allow the lint.

View file

@ -120,6 +120,6 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
let mut collector = CreateRunnableDoctests::new(options.clone(), opts);
md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests);
crate::doctest::run_tests(options.test_args, options.nocapture, collector.standalone_tests);
Ok(())
}

View file

@ -12,7 +12,7 @@ fn make_test(
) -> (String, usize) {
let doctest = DocTest::new(test_code, crate_name, DEFAULT_EDITION);
let (code, line_offset) =
doctest.generate_unique_doctest(dont_insert_main, opts, test_id, crate_name);
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, test_id, crate_name);
(code, line_offset)
}

View file

@ -298,10 +298,10 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
args_file: PathBuf::new(),
};
let doctest = doctest::DocTest::new(&test, krate, edition);
let (test, _) = doctest.generate_unique_doctest(false, &opts, None, krate);
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, None, krate);
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
let test_escaped = small_url_encode(doctest.test_code);
let test_escaped = small_url_encode(test);
Some(format!(
"<a class=\"test-arrow\" \
target=\"_blank\" \