diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 65ddaedf26c..f00aef491f5 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -1,5 +1,6 @@ mod make; mod markdown; +mod runner; mod rust; use std::fs::File; @@ -164,40 +165,54 @@ 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))?; - // 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| { - let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); - let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID); - let opts = scrape_test_config(crate_name, crate_attrs, args_path); - let enable_per_target_ignores = options.enable_per_target_ignores; + let CreateRunnableDoctests { + standalone_tests, + mergeable_tests, + rustdoc_options, + opts, + unused_extern_reports, + compiling_test_count, + .. + } = interface::run_compiler(config, |compiler| { + compiler.enter(|queries| { + let collector = queries.global_ctxt()?.enter(|tcx| { + let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); + let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID); + let opts = scrape_test_config(crate_name, crate_attrs, args_path); + let enable_per_target_ignores = options.enable_per_target_ignores; - let mut collector = CreateRunnableDoctests::new(options, opts); - let hir_collector = HirCollector::new( - &compiler.sess, - tcx.hir(), - ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()), - enable_per_target_ignores, - tcx, - ); - let tests = hir_collector.collect_crate(); - tests.into_iter().for_each(|t| collector.add_test(t)); + let mut collector = CreateRunnableDoctests::new(options, opts); + let hir_collector = HirCollector::new( + &compiler.sess, + tcx.hir(), + ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()), + enable_per_target_ignores, + tcx, + ); + let tests = hir_collector.collect_crate(); + tests.into_iter().for_each(|t| collector.add_test(t)); - collector - }); - if compiler.sess.dcx().has_errors().is_some() { - FatalError.raise(); - } + collector + }); + if compiler.sess.dcx().has_errors().is_some() { + FatalError.raise(); + } - let unused_extern_reports = collector.unused_extern_reports.clone(); - let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst); - Ok((collector.standalone_tests, unused_extern_reports, compiling_test_count)) - }) - })?; + Ok(collector) + }) + })?; - run_tests(test_args, nocapture, standalone_tests); + run_tests( + test_args, + nocapture, + opts, + rustdoc_options, + &unused_extern_reports, + standalone_tests, + mergeable_tests, + ); + + let compiling_test_count = compiling_test_count.load(Ordering::SeqCst); // Collect and warn about unused externs, but only if we've gotten // reports for each doctest @@ -243,14 +258,74 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<() pub(crate) fn run_tests( mut test_args: Vec, nocapture: bool, - mut tests: Vec, + opts: GlobalTestOptions, + rustdoc_options: RustdocOptions, + unused_extern_reports: &Arc>>, + mut standalone_tests: Vec, + mut mergeable_tests: FxHashMap>, ) { test_args.insert(0, "rustdoctest".to_string()); if nocapture { test_args.push("--nocapture".to_string()); } - tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice())); - test::test_main(&test_args, tests, None); + + let mut nb_errors = 0; + + for (edition, mut doctests) in mergeable_tests { + if doctests.is_empty() { + continue; + } + doctests.sort_by(|(_, a), (_, b)| a.name.cmp(&b.name)); + let outdir = Arc::clone(&doctests[0].outdir); + + let mut tests_runner = runner::DocTestRunner::new(); + + let rustdoc_test_options = IndividualTestOptions::new( + &rustdoc_options, + format!("merged_doctest"), + PathBuf::from(r"doctest.rs"), + ); + + for (doctest, scraped_test) in &doctests { + tests_runner.add_test(doctest, scraped_test); + } + if let Ok(success) = + tests_runner.run_tests(rustdoc_test_options, edition, &opts, &test_args, &outdir) + { + if !success { + nb_errors += 1; + } + continue; + } else { + // We failed to compile all compatible tests as one so we push them into the + // `standalone_tests` doctests. + debug!("Failed to compile compatible doctests for edition {} all at once", edition); + for (doctest, scraped_test) in doctests { + doctest.generate_unique_doctest( + &scraped_test.text, + scraped_test.langstr.test_harness, + &opts, + Some(&opts.crate_name), + ); + standalone_tests.push(generate_test_desc_and_fn( + doctest, + scraped_test, + opts.clone(), + rustdoc_test_options.clone(), + unused_extern_reports.clone(), + )); + } + } + } + + if !standalone_tests.is_empty() { + standalone_tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice())); + test::test_main(&test_args, standalone_tests, None); + } + if nb_errors != 0 { + // libtest::ERROR_EXIT_CODE is not public but it's the same value. + std::process::exit(101); + } } // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade. @@ -365,7 +440,10 @@ struct RunnableDoctest { full_test_line_offset: usize, test_opts: IndividualTestOptions, global_opts: GlobalTestOptions, - scraped_test: ScrapedDoctest, + langstr: LangString, + line: usize, + edition: Edition, + no_run: bool, } fn run_test( @@ -374,8 +452,7 @@ fn run_test( supports_color: bool, report_unused_externs: impl Fn(UnusedExterns), ) -> Result<(), TestFailure> { - let scraped_test = &doctest.scraped_test; - let langstr = &scraped_test.langstr; + let langstr = &doctest.langstr; // Make sure we emit well-formed executable names for our target. let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target); let output_file = doctest.test_opts.outdir.path().join(rust_out); @@ -392,11 +469,11 @@ fn run_test( compiler.arg(format!("--sysroot={}", sysroot.display())); } - compiler.arg("--edition").arg(&scraped_test.edition(rustdoc_options).to_string()); + compiler.arg("--edition").arg(&doctest.edition.to_string()); compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path); compiler.env( "UNSTABLE_RUSTDOC_TEST_LINE", - format!("{}", scraped_test.line as isize - doctest.full_test_line_offset as isize), + format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize), ); compiler.arg("-o").arg(&output_file); if langstr.test_harness { @@ -409,10 +486,7 @@ fn run_test( compiler.arg("-Z").arg("unstable-options"); } - if scraped_test.no_run(rustdoc_options) - && !langstr.compile_fail - && rustdoc_options.persist_doctests.is_none() - { + if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() { // FIXME: why does this code check if it *shouldn't* persist doctests // -- shouldn't it be the negation? compiler.arg("--emit=metadata"); @@ -493,8 +567,7 @@ fn drop(&mut self) { // We used to check if the output contained "error[{}]: " but since we added the // colored output, we can't anymore because of the color escape characters before // the ":". - let missing_codes: Vec = scraped_test - .langstr + let missing_codes: Vec = langstr .error_codes .iter() .filter(|err| !out.contains(&format!("error[{err}]"))) @@ -511,7 +584,7 @@ fn drop(&mut self) { } } - if scraped_test.no_run(rustdoc_options) { + if doctest.no_run { return Ok(()); } @@ -600,9 +673,27 @@ struct ScrapedDoctest { logical_path: Vec, langstr: LangString, text: String, + name: String, } impl ScrapedDoctest { + fn new( + filename: FileName, + line: usize, + logical_path: Vec, + langstr: LangString, + text: String, + ) -> Self { + let mut item_path = logical_path.join("::"); + item_path.retain(|c| c != ' '); + if !item_path.is_empty() { + item_path.push(' '); + } + let name = + format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly()); + + Self { filename, line, logical_path, langstr, text, name } + } fn edition(&self, opts: &RustdocOptions) -> Edition { self.langstr.edition.unwrap_or(opts.edition) } @@ -641,60 +732,7 @@ fn new(rustdoc_options: RustdocOptions, opts: GlobalTestOptions) -> CreateRunnab } } - fn generate_name(&self, filename: &FileName, line: usize, logical_path: &[String]) -> String { - let mut item_path = logical_path.join("::"); - item_path.retain(|c| c != ' '); - if !item_path.is_empty() { - item_path.push(' '); - } - format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly()) - } - 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 !scraped_test.langstr.compile_fail { - self.compiling_test_count.fetch_add(1, Ordering::SeqCst); - } - - let path = match &scraped_test.filename { - FileName::Real(path) => { - if let Some(local_path) = path.local_path() { - local_path.to_path_buf() - } else { - // Somehow we got the filename from the metadata of another crate, should never happen - unreachable!("doctest from a different crate"); - } - } - _ => PathBuf::from(r"doctest.rs"), - }; - // For example `module/file.rs` would become `module_file_rs` let file = scraped_test .filename @@ -717,42 +755,99 @@ fn generate_test_desc_and_fn( }, ); - let rustdoc_options = self.rustdoc_options.clone(); - let rustdoc_test_options = IndividualTestOptions::new(&self.rustdoc_options, test_id, path); - - debug!("creating test {name}: {}", scraped_test.text); - test::TestDescAndFn { - desc: test::TestDesc { - name: test::DynTestName(name), - ignore: match scraped_test.langstr.ignore { - Ignore::All => true, - Ignore::None => false, - Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)), - }, - ignore_message: None, - source_file: "", - start_line: 0, - start_col: 0, - end_line: 0, - end_col: 0, - // compiler failures are test failures - should_panic: test::ShouldPanic::No, - 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, - scraped_test, - rustdoc_options, - unused_externs, - ) - })), + let edition = scraped_test.edition(&self.rustdoc_options); + let doctest = + DocTest::new(&scraped_test.text, Some(&self.opts.crate_name), edition, test_id); + 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 { + if !scraped_test.langstr.compile_fail { + self.compiling_test_count.fetch_add(1, Ordering::SeqCst); + } + + generate_test_desc_and_fn( + test, + scraped_test, + self.opts.clone(), + self.rustdoc_options.clone(), + self.unused_extern_reports.clone(), + ) + } +} + +fn generate_test_desc_and_fn( + test: DocTest, + scraped_test: ScrapedDoctest, + opts: GlobalTestOptions, + rustdoc_options: IndividualTestOptions, + unused_externs: Arc>>, +) -> test::TestDescAndFn { + let target_str = rustdoc_options.target.to_string(); + + let path = match &scraped_test.filename { + FileName::Real(path) => { + if let Some(local_path) = path.local_path() { + local_path.to_path_buf() + } else { + // Somehow we got the filename from the metadata of another crate, should never happen + unreachable!("doctest from a different crate"); + } + } + _ => PathBuf::from(r"doctest.rs"), + }; + + let name = &test.name; + let rustdoc_test_options = + IndividualTestOptions::new(&rustdoc_options, test.test_id.clone(), path); + // let rustdoc_options_clone = rustdoc_options.clone(); + + debug!("creating test {name}: {}", scraped_test.text); + test::TestDescAndFn { + desc: test::TestDesc { + name: test::DynTestName(name), + ignore: match scraped_test.langstr.ignore { + Ignore::All => true, + Ignore::None => false, + Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)), + }, + ignore_message: None, + source_file: "", + start_line: 0, + start_col: 0, + end_line: 0, + end_col: 0, + // compiler failures are test failures + should_panic: test::ShouldPanic::No, + 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, + scraped_test, + rustdoc_options, + unused_externs, + ) + })), + } } fn doctest_run_fn( @@ -770,7 +865,6 @@ fn doctest_run_fn( &scraped_test.text, scraped_test.langstr.test_harness, &global_opts, - Some(&test_opts.test_id), Some(&global_opts.crate_name), ); let runnable_test = RunnableDoctest { @@ -778,7 +872,10 @@ fn doctest_run_fn( full_test_line_offset, test_opts, global_opts, - scraped_test, + langstr: scraped_test.langstr.clone(), + line: scraped_test.line, + edition: scraped_test.edition(&rustdoc_options), + no_run: scraped_test.no_run(&rustdoc_options), }; let res = run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs); diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 759a3e31b23..c1d1e45ff04 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -24,10 +24,17 @@ pub(crate) struct DocTest { pub(crate) crate_attrs: String, pub(crate) crates: String, pub(crate) everything_else: String, + pub(crate) test_id: Option, } impl DocTest { - pub(crate) fn new(source: &str, crate_name: Option<&str>, edition: Edition) -> Self { + pub(crate) fn new( + source: &str, + crate_name: Option<&str>, + edition: Edition, + // If `test_id` is `None`, it means we're generating code for a code example "run" link. + test_id: Option, + ) -> Self { let (crate_attrs, everything_else, crates) = partition_source(source, edition); let mut supports_color = false; @@ -45,6 +52,7 @@ pub(crate) fn new(source: &str, crate_name: Option<&str>, edition: Edition) -> S crates, everything_else, already_has_extern_crate: false, + test_id, }; }; Self { @@ -54,6 +62,7 @@ pub(crate) fn new(source: &str, crate_name: Option<&str>, edition: Edition) -> S crates, everything_else, already_has_extern_crate, + test_id, } } @@ -64,8 +73,6 @@ pub(crate) fn generate_unique_doctest( 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. - test_id: Option<&str>, crate_name: Option<&str>, ) -> (String, usize) { let mut line_offset = 0; @@ -118,12 +125,12 @@ pub(crate) fn generate_unique_doctest( let returns_result = everything_else.ends_with("(())"); // Give each doctest main function a unique name. // This is for example needed for the tooling around `-C instrument-coverage`. - let inner_fn_name = if let Some(test_id) = test_id { + let inner_fn_name = if let Some(ref test_id) = self.test_id { format!("_doctest_main_{test_id}") } else { "_inner".into() }; - let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" }; + let inner_attr = if self.test_id.is_some() { "#[allow(non_snake_case)] " } else { "" }; let (main_pre, main_post) = if returns_result { ( format!( @@ -131,7 +138,7 @@ pub(crate) fn generate_unique_doctest( ), format!("\n}} {inner_fn_name}().unwrap() }}"), ) - } else if test_id.is_some() { + } else if self.test_id.is_some() { ( format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",), format!("\n}} {inner_fn_name}() }}"), diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index ff2adffe5c2..a5514857fff 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -22,13 +22,7 @@ fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) let filename = self.filename.clone(); // First line of Markdown is line 1. let line = 1 + rel_line.offset(); - self.tests.push(ScrapedDoctest { - filename, - line, - logical_path: self.cur_path.clone(), - langstr: config, - text: test, - }); + self.tests.push(ScrapedDoctest::new(filename, line, self.cur_path.clone(), config, test)); } fn visit_header(&mut self, name: &str, level: u32) { diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs new file mode 100644 index 00000000000..a672bb1bd9b --- /dev/null +++ b/src/librustdoc/doctest/runner.rs @@ -0,0 +1,188 @@ +use rustc_data_structures::fx::FxHashSet; +use rustc_span::edition::Edition; + +use std::fmt::Write; +use std::sync::{Arc, Mutex}; + +use crate::doctest::{ + run_test, DirState, DocTest, GlobalTestOptions, IndividualTestOptions, RunnableDoctest, + RustdocOptions, ScrapedDoctest, TestFailure, UnusedExterns, +}; +use crate::html::markdown::LangString; + +/// Convenient type to merge compatible doctests into one. +pub(crate) struct DocTestRunner { + crate_attrs: FxHashSet, + ids: String, + output: String, + supports_color: bool, + nb_tests: usize, + doctests: Vec, +} + +impl DocTestRunner { + pub(crate) fn new() -> Self { + Self { + crate_attrs: FxHashSet::default(), + ids: String::new(), + output: String::new(), + supports_color: true, + nb_tests: 0, + doctests: Vec::with_capacity(10), + } + } + + pub(crate) fn add_test(&mut self, doctest: &DocTest, scraped_test: &ScrapedDoctest) { + if !doctest.ignore { + for line in doctest.crate_attrs.split('\n') { + self.crate_attrs.insert(line.to_string()); + } + } + if !self.ids.is_empty() { + self.ids.push(','); + } + self.ids.push_str(&format!( + "{}::TEST", + generate_mergeable_doctest(doctest, scraped_test, self.nb_tests, &mut self.output), + )); + self.supports_color &= doctest.supports_color; + self.nb_tests += 1; + self.doctests.push(doctest); + } + + pub(crate) fn run_tests( + &mut self, + test_options: IndividualTestOptions, + edition: Edition, + opts: &GlobalTestOptions, + test_args: &[String], + outdir: &Arc, + rustdoc_options: &RustdocOptions, + unused_externs: Arc>>, + ) -> Result { + let mut code = "\ +#![allow(unused_extern_crates)] +#![allow(internal_features)] +#![feature(test)] +#![feature(rustc_attrs)] +#![feature(coverage_attribute)]\n" + .to_string(); + + for crate_attr in &self.crate_attrs { + code.push_str(crate_attr); + code.push('\n'); + } + + DocTest::push_attrs(&mut code, opts, &mut 0); + code.push_str("extern crate test;\n"); + + let test_args = + test_args.iter().map(|arg| format!("{arg:?}.to_string(),")).collect::(); + write!( + code, + "\ +{output} +#[rustc_main] +#[coverage(off)] +fn main() {{ +test::test_main(&[{test_args}], vec![{ids}], None); +}}", + output = self.output, + ids = self.ids, + ) + .expect("failed to generate test code"); + // let out_dir = build_test_dir(outdir, true, ""); + let runnable_test = RunnableDoctest { + full_test_code: code, + full_test_line_offset: 0, + test_opts: test_options, + global_opts: opts.clone(), + langstr: LangString::default(), + line: 0, + edition, + no_run: false, + }; + let ret = run_test(runnable_test, rustdoc_options, self.supports_color, unused_externs); + if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) } + } +} + +/// Push new doctest content into `output`. Returns the test ID for this doctest. +fn generate_mergeable_doctest( + doctest: &DocTest, + scraped_test: &ScrapedDoctest, + id: usize, + output: &mut String, +) -> String { + let test_id = format!("__doctest_{id}"); + + if doctest.ignore { + // We generate nothing else. + writeln!(output, "mod {test_id} {{\n").unwrap(); + } else { + writeln!(output, "mod {test_id} {{\n{}", doctest.crates).unwrap(); + if doctest.main_fn_span.is_some() { + output.push_str(&doctest.everything_else); + } else { + let returns_result = if doctest.everything_else.trim_end().ends_with("(())") { + "-> Result<(), impl core::fmt::Debug>" + } else { + "" + }; + write!( + output, + "\ + fn main() {returns_result} {{ + {} + }}", + doctest.everything_else + ) + .unwrap(); + } + } + writeln!( + output, + " +#[rustc_test_marker = {test_name:?}] +pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{ + desc: test::TestDesc {{ + name: test::StaticTestName({test_name:?}), + ignore: {ignore}, + ignore_message: None, + source_file: {file:?}, + start_line: {line}, + start_col: 0, + end_line: 0, + end_col: 0, + compile_fail: false, + no_run: {no_run}, + should_panic: test::ShouldPanic::{should_panic}, + test_type: test::TestType::UnitTest, + }}, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result({runner}), + ) +}}; +}}", + test_name = scraped_test.name, + ignore = scraped_test.langstr.ignore, + file = scraped_test.file, + line = scraped_test.line, + no_run = scraped_test.langstr.no_run, + should_panic = if !scraped_test.langstr.no_run && scraped_test.langstr.should_panic { + "Yes" + } else { + "No" + }, + // 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 scraped_test.langstr.no_run || scraped_test.langstr.ignore { + "Ok::<(), String>(())" + } else { + "self::main()" + }, + ) + .unwrap(); + test_id +} diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index f179f3aa1c9..17c29ba413a 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -51,13 +51,13 @@ fn get_base_line(&self) -> usize { impl DoctestVisitor for RustCollector { fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) { let line = self.get_base_line() + rel_line.offset(); - self.tests.push(ScrapedDoctest { - filename: self.get_filename(), + self.tests.push(ScrapedDoctest::new( + self.get_filename(), line, - logical_path: self.cur_path.clone(), - langstr: config, - text: test, - }); + self.cur_path.clone(), + config, + test, + )); } fn visit_header(&mut self, _name: &str, _level: u32) {} diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index c1f5f3d7e23..a268a2d704e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -297,8 +297,8 @@ fn next(&mut self) -> Option { attrs: vec![], args_file: PathBuf::new(), }; - let doctest = doctest::DocTest::new(&test, krate, edition); - let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, None, krate); + let doctest = doctest::DocTest::new(&test, krate, edition, None); + let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate); let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" }; let test_escaped = small_url_encode(test);