diff options
Diffstat (limited to 'src/librustdoc/test.rs')
| -rw-r--r-- | src/librustdoc/test.rs | 227 |
1 files changed, 148 insertions, 79 deletions
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index 5dd7bd82755..4a9ad39e236 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -1,29 +1,36 @@ -use rustc::hir::map::Map; -use rustc::session::{self, config, DiagnosticOutput}; -use rustc::util::common::ErrorReported; +use rustc_ast::ast; +use rustc_ast::with_globals; use rustc_data_structures::sync::Lrc; +use rustc_errors::ErrorReported; use rustc_feature::UnstableFeatures; use rustc_hir as hir; use rustc_hir::intravisit; +use rustc_hir::{HirId, CRATE_HIR_ID}; use rustc_interface::interface; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::{self, CrateType}; +use rustc_session::{lint, DiagnosticOutput, Session}; use rustc_span::edition::Edition; use rustc_span::source_map::SourceMap; use rustc_span::symbol::sym; use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP}; use rustc_target::spec::TargetTriple; +use tempfile::Builder as TempFileBuilder; + +use std::collections::HashMap; use std::env; use std::io::{self, Write}; use std::panic; use std::path::PathBuf; use std::process::{self, Command, Stdio}; use std::str; -use syntax::ast; -use syntax::with_globals; -use tempfile::Builder as TempFileBuilder; use crate::clean::Attributes; use crate::config::Options; +use crate::core::init_lints; use crate::html::markdown::{self, ErrorCodes, Ignore, LangString}; +use crate::passes::span_of_attrs; #[derive(Clone, Default)] pub struct TestOptions { @@ -39,20 +46,32 @@ pub struct TestOptions { pub fn run(options: Options) -> i32 { let input = config::Input::File(options.input.clone()); - let crate_types = if options.proc_macro_crate { - vec![config::CrateType::ProcMacro] - } else { - vec![config::CrateType::Rlib] - }; + let invalid_codeblock_attribute_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTE.name; + + // In addition to those specific lints, we also need to whitelist those given through + // command line, otherwise they'll get ignored and we don't want that. + let whitelisted_lints = vec![invalid_codeblock_attribute_name.to_owned()]; + + let (lint_opts, lint_caps) = init_lints(whitelisted_lints, options.lint_opts.clone(), |lint| { + if lint.name == invalid_codeblock_attribute_name { + None + } else { + Some((lint.name_lower(), lint::Allow)) + } + }); + + let crate_types = + if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), search_paths: options.libs.clone(), crate_types, + lint_opts: if !options.display_warnings { lint_opts } else { vec![] }, + lint_cap: Some(options.lint_cap.clone().unwrap_or_else(|| lint::Forbid)), cg: options.codegen_options.clone(), externs: options.externs.clone(), unstable_features: UnstableFeatures::from_environment(), - lint_cap: Some(::rustc::lint::Level::Allow), actually_rustdoc: true, debugging_opts: config::DebuggingOptions { ..config::basic_debugging_options() }, edition: options.edition, @@ -74,7 +93,7 @@ pub fn run(options: Options) -> i32 { diagnostic_output: DiagnosticOutput::Default, stderr: None, crate_name: options.crate_name.clone(), - lint_caps: Default::default(), + lint_caps, register_lints: None, override_queries: None, registry: rustc_driver::diagnostics_registry(), @@ -104,17 +123,25 @@ pub fn run(options: Options) -> i32 { global_ctxt.enter(|tcx| { let krate = tcx.hir().krate(); + let mut hir_collector = HirCollector { sess: compiler.session(), collector: &mut collector, - map: *tcx.hir(), + map: tcx.hir(), codes: ErrorCodes::from( compiler.session().opts.unstable_features.is_nightly_build(), ), + tcx, }; - hir_collector.visit_testable("".to_string(), &krate.attrs, |this| { - intravisit::walk_crate(this, krate); - }); + hir_collector.visit_testable( + "".to_string(), + &krate.item.attrs, + CRATE_HIR_ID, + krate.item.span, + |this| { + intravisit::walk_crate(this, krate); + }, + ); }); compiler.session().abort_if_errors(); @@ -146,6 +173,7 @@ fn scrape_test_config(krate: &::rustc_hir::Crate) -> TestOptions { TestOptions { no_crate_inject: false, display_warnings: false, attrs: Vec::new() }; let test_attrs: Vec<_> = krate + .item .attrs .iter() .filter(|a| a.check_name(sym::doc)) @@ -189,10 +217,23 @@ enum TestFailure { UnexpectedRunPass, } +enum DirState { + Temp(tempfile::TempDir), + Perm(PathBuf), +} + +impl DirState { + fn path(&self) -> &std::path::Path { + match self { + DirState::Temp(t) => t.path(), + DirState::Perm(p) => p.as_path(), + } + } +} + fn run_test( test: &str, cratename: &str, - filename: &FileName, line: usize, options: Options, should_panic: bool, @@ -205,47 +246,11 @@ fn run_test( mut error_codes: Vec<String>, opts: &TestOptions, edition: Edition, + outdir: DirState, + path: PathBuf, ) -> Result<(), TestFailure> { let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts, edition); - // FIXME(#44940): if doctests ever support path remapping, then this filename - // needs to be the result of `SourceMap::span_to_unmapped_path`. - let path = match filename { - FileName::Real(path) => path.clone(), - _ => PathBuf::from(r"doctest.rs"), - }; - - enum DirState { - Temp(tempfile::TempDir), - Perm(PathBuf), - } - - impl DirState { - fn path(&self) -> &std::path::Path { - match self { - DirState::Temp(t) => t.path(), - DirState::Perm(p) => p.as_path(), - } - } - } - - let outdir = if let Some(mut path) = options.persist_doctests { - path.push(format!( - "{}_{}", - filename.to_string().rsplit('/').next().unwrap().replace(".", "_"), - line - )); - std::fs::create_dir_all(&path).expect("Couldn't create directory for doctest executables"); - - DirState::Perm(path) - } else { - DirState::Temp( - TempFileBuilder::new() - .prefix("rustdoctest") - .tempdir() - .expect("rustdoc needs a tempdir"), - ) - }; let output_file = outdir.path().join("rust_out"); let rustc_binary = options @@ -284,7 +289,12 @@ fn run_test( if no_run && !compile_fail { compiler.arg("--emit=metadata"); } - compiler.arg("--target").arg(target.to_string()); + compiler.arg("--target").arg(match target { + TargetTriple::TargetTriple(s) => s, + TargetTriple::TargetPath(path) => { + path.to_str().expect("target path must be valid unicode").to_string() + } + }); compiler.arg("-"); compiler.stdin(Stdio::piped()); @@ -333,8 +343,8 @@ fn run_test( if let Some(tool) = runtool { cmd = Command::new(tool); - cmd.arg(output_file); cmd.args(runtool_args); + cmd.arg(output_file); } else { cmd = Command::new(output_file); } @@ -387,7 +397,7 @@ pub fn make_test( prog.push_str(&crate_attrs); prog.push_str(&crates); - // Uses libsyntax to parse the doctest and find if there's a main fn and the extern + // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern // crate already is included. let result = rustc_driver::catch_fatal_errors(|| { with_globals(edition, || { @@ -398,10 +408,10 @@ pub fn make_test( use rustc_span::source_map::FilePathMapping; let filename = FileName::anon_source_code(s); - let source = crates + &everything_else; + let source = crates + everything_else; // Any errors in parsing should also appear when the doctest is compiled for real, so just - // send all the errors that libsyntax emits directly into a `Sink` instead of stderr. + // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false); @@ -449,7 +459,7 @@ pub fn make_test( } if !found_macro { - if let ast::ItemKind::Mac(..) = item.kind { + if let ast::ItemKind::MacCall(..) = item.kind { found_macro = true; } } @@ -638,6 +648,7 @@ pub struct Collector { position: Span, source_map: Option<Lrc<SourceMap>>, filename: Option<PathBuf>, + visited_tests: HashMap<(String, usize), usize>, } impl Collector { @@ -661,6 +672,7 @@ impl Collector { position: DUMMY_SP, source_map, filename, + visited_tests: HashMap::new(), } } @@ -697,13 +709,55 @@ impl Tester for Collector { let name = self.generate_name(line, &filename); let cratename = self.cratename.to_string(); let opts = self.opts.clone(); - let edition = config.edition.unwrap_or(self.options.edition.clone()); + let edition = config.edition.unwrap_or(self.options.edition); let options = self.options.clone(); let runtool = self.options.runtool.clone(); let runtool_args = self.options.runtool_args.clone(); let target = self.options.target.clone(); let target_str = target.to_string(); + // FIXME(#44940): if doctests ever support path remapping, then this filename + // needs to be the result of `SourceMap::span_to_unmapped_path`. + let path = match &filename { + FileName::Real(path) => path.clone(), + _ => PathBuf::from(r"doctest.rs"), + }; + + let outdir = if let Some(mut path) = options.persist_doctests.clone() { + // For example `module/file.rs` would become `module_file_rs` + let folder_name = filename + .to_string() + .chars() + .map(|c| if c == '/' || c == '.' { '_' } else { c }) + .collect::<String>(); + + path.push(format!( + "{name}_{line}_{number}", + name = folder_name, + 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((folder_name.clone(), line)) + .and_modify(|v| *v += 1) + .or_insert(0) + }, + line = line, + )); + + std::fs::create_dir_all(&path) + .expect("Couldn't create directory for doctest executables"); + + DirState::Perm(path) + } else { + DirState::Temp( + TempFileBuilder::new() + .prefix("rustdoctest") + .tempdir() + .expect("rustdoc needs a tempdir"), + ) + }; + debug!("creating test {}: {}", name, test); self.tests.push(testing::TestDescAndFn { desc: testing::TestDesc { @@ -722,7 +776,6 @@ impl Tester for Collector { let res = run_test( &test, &cratename, - &filename, line, options, config.should_panic, @@ -735,6 +788,8 @@ impl Tester for Collector { config.error_codes, &opts, edition, + outdir, + path, ); if let Err(err) = res { @@ -852,18 +907,21 @@ impl Tester for Collector { } } -struct HirCollector<'a, 'hir> { - sess: &'a session::Session, +struct HirCollector<'a, 'hir, 'tcx> { + sess: &'a Session, collector: &'a mut Collector, - map: &'a Map<'hir>, + map: Map<'hir>, codes: ErrorCodes, + tcx: TyCtxt<'tcx>, } -impl<'a, 'hir> HirCollector<'a, 'hir> { +impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { fn visit_testable<F: FnOnce(&mut Self)>( &mut self, name: String, attrs: &[ast::Attribute], + hir_id: HirId, + sp: Span, nested: F, ) { let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs); @@ -889,6 +947,11 @@ impl<'a, 'hir> HirCollector<'a, 'hir> { self.collector, self.codes, self.collector.enable_per_target_ignores, + Some(&crate::html::markdown::ExtraInfo::new( + &self.tcx, + hir_id, + span_of_attrs(&attrs).unwrap_or(sp), + )), ); } @@ -900,39 +963,39 @@ impl<'a, 'hir> HirCollector<'a, 'hir> { } } -impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> { +impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> { type Map = Map<'hir>; - fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<'_, Self::Map> { - intravisit::NestedVisitorMap::All(&self.map) + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::All(self.map) } fn visit_item(&mut self, item: &'hir hir::Item) { let name = if let hir::ItemKind::Impl { ref self_ty, .. } = item.kind { - self.map.hir_to_pretty_string(self_ty.hir_id) + rustc_hir_pretty::id_to_string(&self.map, self_ty.hir_id) } else { item.ident.to_string() }; - self.visit_testable(name, &item.attrs, |this| { + self.visit_testable(name, &item.attrs, item.hir_id, item.span, |this| { intravisit::walk_item(this, item); }); } fn visit_trait_item(&mut self, item: &'hir hir::TraitItem) { - self.visit_testable(item.ident.to_string(), &item.attrs, |this| { + self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| { intravisit::walk_trait_item(this, item); }); } fn visit_impl_item(&mut self, item: &'hir hir::ImplItem) { - self.visit_testable(item.ident.to_string(), &item.attrs, |this| { + self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| { intravisit::walk_impl_item(this, item); }); } fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem) { - self.visit_testable(item.ident.to_string(), &item.attrs, |this| { + self.visit_testable(item.ident.to_string(), &item.attrs, item.hir_id, item.span, |this| { intravisit::walk_foreign_item(this, item); }); } @@ -943,19 +1006,25 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> { g: &'hir hir::Generics, item_id: hir::HirId, ) { - self.visit_testable(v.ident.to_string(), &v.attrs, |this| { + self.visit_testable(v.ident.to_string(), &v.attrs, v.id, v.span, |this| { intravisit::walk_variant(this, v, g, item_id); }); } fn visit_struct_field(&mut self, f: &'hir hir::StructField) { - self.visit_testable(f.ident.to_string(), &f.attrs, |this| { + self.visit_testable(f.ident.to_string(), &f.attrs, f.hir_id, f.span, |this| { intravisit::walk_struct_field(this, f); }); } fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef) { - self.visit_testable(macro_def.name.to_string(), ¯o_def.attrs, |_| ()); + self.visit_testable( + macro_def.ident.to_string(), + ¯o_def.attrs, + macro_def.hir_id, + macro_def.span, + |_| (), + ); } } |
