2021-11-22 20:47:58 -06:00
|
|
|
use rustc_ast as ast;
|
2020-08-01 05:57:35 -05:00
|
|
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
2019-12-22 16:42:04 -06:00
|
|
|
use rustc_data_structures::sync::Lrc;
|
2022-01-23 12:34:26 -06:00
|
|
|
use rustc_errors::{ColorConfig, ErrorGuaranteed, FatalError};
|
2020-01-04 19:37:57 -06:00
|
|
|
use rustc_hir as hir;
|
2021-06-20 19:22:25 -05:00
|
|
|
use rustc_hir::def_id::LOCAL_CRATE;
|
2021-06-20 19:34:38 -05:00
|
|
|
use rustc_hir::intravisit;
|
2020-04-21 16:49:06 -05:00
|
|
|
use rustc_hir::{HirId, CRATE_HIR_ID};
|
2019-12-22 16:42:04 -06:00
|
|
|
use rustc_interface::interface;
|
2020-03-29 10:19:48 -05:00
|
|
|
use rustc_middle::hir::map::Map;
|
2021-11-03 18:03:12 -05:00
|
|
|
use rustc_middle::hir::nested_filter;
|
2020-04-21 16:49:06 -05:00
|
|
|
use rustc_middle::ty::TyCtxt;
|
2020-07-13 08:34:38 -05:00
|
|
|
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
2020-05-01 17:30:23 -05:00
|
|
|
use rustc_session::{lint, DiagnosticOutput, Session};
|
2020-01-01 12:40:49 -06:00
|
|
|
use rustc_span::edition::Edition;
|
2020-01-01 12:25:28 -06:00
|
|
|
use rustc_span::source_map::SourceMap;
|
2020-01-01 12:30:57 -06:00
|
|
|
use rustc_span::symbol::sym;
|
2021-06-20 19:34:38 -05:00
|
|
|
use rustc_span::Symbol;
|
2019-12-31 11:15:40 -06:00
|
|
|
use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
|
2019-12-22 16:42:04 -06:00
|
|
|
use rustc_target::spec::TargetTriple;
|
2020-05-01 17:30:23 -05:00
|
|
|
use tempfile::Builder as TempFileBuilder;
|
|
|
|
|
2018-11-26 20:59:49 -06:00
|
|
|
use std::env;
|
2019-08-29 16:15:31 -05:00
|
|
|
use std::io::{self, Write};
|
|
|
|
use std::panic;
|
2018-12-08 13:30:23 -06:00
|
|
|
use std::path::PathBuf;
|
2019-08-29 16:15:31 -05:00
|
|
|
use std::process::{self, Command, Stdio};
|
2018-11-26 20:59:49 -06:00
|
|
|
use std::str;
|
2020-08-01 05:57:35 -05:00
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
use std::sync::{Arc, Mutex};
|
2013-12-22 13:23:04 -06:00
|
|
|
|
2021-04-23 15:15:13 -05:00
|
|
|
use crate::clean::{types::AttributesExt, Attributes};
|
2021-12-03 21:54:38 -06:00
|
|
|
use crate::config::Options as RustdocOptions;
|
2019-12-22 16:42:04 -06:00
|
|
|
use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
|
2020-12-30 13:11:15 -06:00
|
|
|
use crate::lint::init_lints;
|
2020-04-21 16:49:06 -05:00
|
|
|
use crate::passes::span_of_attrs;
|
2013-12-22 13:23:04 -06:00
|
|
|
|
2021-12-03 21:49:31 -06:00
|
|
|
/// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
|
2015-04-06 20:39:39 -05:00
|
|
|
#[derive(Clone, Default)]
|
2021-12-03 21:49:31 -06:00
|
|
|
crate struct GlobalTestOptions {
|
2018-03-15 17:10:09 -05:00
|
|
|
/// Whether to disable the default `extern crate my_crate;` when creating doctests.
|
2020-11-14 16:59:58 -06:00
|
|
|
crate no_crate_inject: bool,
|
2018-03-15 17:10:09 -05:00
|
|
|
/// Additional crate-level attributes to add to doctests.
|
2020-11-14 16:59:58 -06:00
|
|
|
crate attrs: Vec<String>,
|
2015-04-06 20:39:39 -05:00
|
|
|
}
|
|
|
|
|
2022-01-23 12:34:26 -06:00
|
|
|
crate fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
|
2018-10-30 13:35:10 -05:00
|
|
|
let input = config::Input::File(options.input.clone());
|
2013-12-22 13:23:04 -06:00
|
|
|
|
2020-12-30 13:11:15 -06:00
|
|
|
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
|
2020-04-21 16:49:06 -05:00
|
|
|
|
2020-12-30 13:11:15 -06:00
|
|
|
// See core::create_config for what's going on here.
|
2020-12-29 22:16:16 -06:00
|
|
|
let allowed_lints = vec![
|
|
|
|
invalid_codeblock_attributes_name.to_owned(),
|
|
|
|
lint::builtin::UNKNOWN_LINTS.name.to_owned(),
|
|
|
|
lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_owned(),
|
|
|
|
];
|
2020-04-21 16:49:06 -05:00
|
|
|
|
2020-07-07 10:12:44 -05:00
|
|
|
let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| {
|
2020-07-13 10:11:50 -05:00
|
|
|
if lint.name == invalid_codeblock_attributes_name {
|
2020-04-26 07:07:13 -05:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some((lint.name_lower(), lint::Allow))
|
|
|
|
}
|
|
|
|
});
|
2020-04-21 16:49:06 -05:00
|
|
|
|
2021-11-26 12:52:28 -06:00
|
|
|
debug!(?lint_opts);
|
|
|
|
|
2020-05-01 17:30:23 -05:00
|
|
|
let crate_types =
|
|
|
|
if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
|
2019-07-20 15:34:41 -05:00
|
|
|
|
2014-05-06 06:38:01 -05:00
|
|
|
let sessopts = config::Options {
|
2019-06-02 06:39:50 -05:00
|
|
|
maybe_sysroot: options.maybe_sysroot.clone(),
|
2018-10-30 13:35:10 -05:00
|
|
|
search_paths: options.libs.clone(),
|
2019-07-20 15:34:41 -05:00
|
|
|
crate_types,
|
2021-11-26 12:52:28 -06:00
|
|
|
lint_opts,
|
2021-10-01 10:12:39 -05:00
|
|
|
lint_cap: Some(options.lint_cap.unwrap_or(lint::Forbid)),
|
2018-10-30 13:35:10 -05:00
|
|
|
cg: options.codegen_options.clone(),
|
|
|
|
externs: options.externs.clone(),
|
2020-10-10 13:27:52 -05:00
|
|
|
unstable_features: options.render_options.unstable_features,
|
2017-01-18 15:31:29 -06:00
|
|
|
actually_rustdoc: true,
|
2018-10-30 13:35:10 -05:00
|
|
|
edition: options.edition,
|
2019-06-12 12:49:41 -05:00
|
|
|
target_triple: options.target.clone(),
|
2020-11-30 21:54:20 -06:00
|
|
|
crate_name: options.crate_name.clone(),
|
2018-07-26 13:36:11 -05:00
|
|
|
..config::Options::default()
|
2013-12-22 13:23:04 -06:00
|
|
|
};
|
2018-04-25 17:49:52 -05:00
|
|
|
|
2019-09-25 10:08:40 -05:00
|
|
|
let mut cfgs = options.cfgs.clone();
|
2019-11-06 11:32:51 -06:00
|
|
|
cfgs.push("doc".to_owned());
|
2019-10-07 16:08:54 -05:00
|
|
|
cfgs.push("doctest".to_owned());
|
2018-12-08 13:30:23 -06:00
|
|
|
let config = interface::Config {
|
|
|
|
opts: sessopts,
|
2019-10-11 16:48:16 -05:00
|
|
|
crate_cfg: interface::parse_cfgspecs(cfgs),
|
2022-02-19 07:31:20 -06:00
|
|
|
crate_check_cfg: interface::parse_check_cfg(options.check_cfgs.clone()),
|
2018-12-08 13:30:23 -06:00
|
|
|
input,
|
|
|
|
input_path: None,
|
|
|
|
output_file: None,
|
|
|
|
output_dir: None,
|
|
|
|
file_loader: None,
|
|
|
|
diagnostic_output: DiagnosticOutput::Default,
|
2020-04-21 16:49:06 -05:00
|
|
|
lint_caps,
|
2021-03-15 04:57:53 -05:00
|
|
|
parse_sess_created: None,
|
2021-09-20 18:49:47 -05:00
|
|
|
register_lints: Some(box crate::lint::register_lints),
|
2019-11-11 09:09:03 -06:00
|
|
|
override_queries: None,
|
2020-09-08 06:44:41 -05:00
|
|
|
make_codegen_backend: None,
|
2019-11-15 12:41:50 -06:00
|
|
|
registry: rustc_driver::diagnostics_registry(),
|
2018-12-08 13:30:23 -06:00
|
|
|
};
|
|
|
|
|
2021-07-22 10:01:12 -05:00
|
|
|
let test_args = options.test_args.clone();
|
2021-06-11 09:53:32 -05:00
|
|
|
let nocapture = options.nocapture;
|
2020-08-01 05:57:35 -05:00
|
|
|
let externs = options.externs.clone();
|
|
|
|
let json_unused_externs = options.json_unused_externs;
|
2018-12-08 13:30:23 -06:00
|
|
|
|
2022-03-02 15:10:35 -06:00
|
|
|
let (tests, unused_extern_reports, compiling_test_count) =
|
|
|
|
interface::run_compiler(config, |compiler| {
|
|
|
|
compiler.enter(|queries| {
|
|
|
|
let mut global_ctxt = queries.global_ctxt()?.take();
|
|
|
|
|
|
|
|
let collector = global_ctxt.enter(|tcx| {
|
|
|
|
let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID);
|
|
|
|
|
|
|
|
let opts = scrape_test_config(crate_attrs);
|
|
|
|
let enable_per_target_ignores = options.enable_per_target_ignores;
|
|
|
|
let mut collector = Collector::new(
|
|
|
|
tcx.crate_name(LOCAL_CRATE),
|
|
|
|
options,
|
|
|
|
false,
|
|
|
|
opts,
|
|
|
|
Some(compiler.session().parse_sess.clone_source_map()),
|
|
|
|
None,
|
|
|
|
enable_per_target_ignores,
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut hir_collector = HirCollector {
|
|
|
|
sess: compiler.session(),
|
|
|
|
collector: &mut collector,
|
|
|
|
map: tcx.hir(),
|
|
|
|
codes: ErrorCodes::from(
|
|
|
|
compiler.session().opts.unstable_features.is_nightly_build(),
|
|
|
|
),
|
|
|
|
tcx,
|
|
|
|
};
|
|
|
|
hir_collector.visit_testable(
|
|
|
|
"".to_string(),
|
|
|
|
CRATE_HIR_ID,
|
|
|
|
tcx.hir().span(CRATE_HIR_ID),
|
|
|
|
|this| tcx.hir().walk_toplevel_module(this),
|
|
|
|
);
|
|
|
|
|
|
|
|
collector
|
|
|
|
});
|
|
|
|
if compiler.session().diagnostic().has_errors_or_lint_errors() {
|
|
|
|
FatalError.raise();
|
|
|
|
}
|
|
|
|
|
|
|
|
let unused_extern_reports = collector.unused_extern_reports.clone();
|
|
|
|
let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
|
|
|
|
let ret: Result<_, ErrorGuaranteed> =
|
|
|
|
Ok((collector.tests, unused_extern_reports, compiling_test_count));
|
|
|
|
ret
|
|
|
|
})
|
|
|
|
})?;
|
2013-12-22 13:23:04 -06:00
|
|
|
|
2021-11-26 12:52:28 -06:00
|
|
|
run_tests(test_args, nocapture, tests);
|
2018-12-08 13:30:23 -06:00
|
|
|
|
2020-08-01 05:57:35 -05:00
|
|
|
// Collect and warn about unused externs, but only if we've gotten
|
|
|
|
// reports for each doctest
|
|
|
|
if json_unused_externs {
|
|
|
|
let unused_extern_reports: Vec<_> =
|
|
|
|
std::mem::take(&mut unused_extern_reports.lock().unwrap());
|
|
|
|
if unused_extern_reports.len() == compiling_test_count {
|
|
|
|
let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>();
|
|
|
|
let mut unused_extern_names = unused_extern_reports
|
|
|
|
.iter()
|
|
|
|
.map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>())
|
|
|
|
.fold(extern_names, |uextsa, uextsb| {
|
2021-10-01 10:12:39 -05:00
|
|
|
uextsa.intersection(&uextsb).copied().collect::<FxHashSet<&String>>()
|
2020-08-01 05:57:35 -05:00
|
|
|
})
|
|
|
|
.iter()
|
|
|
|
.map(|v| (*v).clone())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
unused_extern_names.sort();
|
2020-08-09 18:57:35 -05:00
|
|
|
// Take the most severe lint level
|
|
|
|
let lint_level = unused_extern_reports
|
|
|
|
.iter()
|
|
|
|
.map(|uexts| uexts.lint_level.as_str())
|
|
|
|
.max_by_key(|v| match *v {
|
|
|
|
"warn" => 1,
|
|
|
|
"deny" => 2,
|
|
|
|
"forbid" => 3,
|
|
|
|
// The allow lint level is not expected,
|
|
|
|
// as if allow is specified, no message
|
|
|
|
// is to be emitted.
|
|
|
|
v => unreachable!("Invalid lint level '{}'", v),
|
|
|
|
})
|
|
|
|
.unwrap_or("warn")
|
|
|
|
.to_string();
|
|
|
|
let uext = UnusedExterns { lint_level, unused_extern_names };
|
|
|
|
let unused_extern_json = serde_json::to_string(&uext).unwrap();
|
2020-08-01 05:57:35 -05:00
|
|
|
eprintln!("{}", unused_extern_json);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-09 06:37:26 -05:00
|
|
|
Ok(())
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
2021-11-26 12:52:28 -06:00
|
|
|
crate fn run_tests(mut test_args: Vec<String>, nocapture: bool, tests: Vec<test::TestDescAndFn>) {
|
2021-07-22 10:01:12 -05:00
|
|
|
test_args.insert(0, "rustdoctest".to_string());
|
|
|
|
if nocapture {
|
|
|
|
test_args.push("--nocapture".to_string());
|
|
|
|
}
|
2021-11-26 12:52:28 -06:00
|
|
|
test::test_main(&test_args, tests, None);
|
2021-07-22 10:01:12 -05:00
|
|
|
}
|
|
|
|
|
2018-11-26 20:59:49 -06:00
|
|
|
// Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
|
2021-12-03 21:49:31 -06:00
|
|
|
fn scrape_test_config(attrs: &[ast::Attribute]) -> GlobalTestOptions {
|
2020-01-11 10:02:46 -06:00
|
|
|
use rustc_ast_pretty::pprust;
|
2015-03-15 19:54:30 -05:00
|
|
|
|
2021-12-03 21:49:31 -06:00
|
|
|
let mut opts = GlobalTestOptions { no_crate_inject: false, attrs: Vec::new() };
|
2015-04-06 20:39:39 -05:00
|
|
|
|
2020-11-26 16:38:53 -06:00
|
|
|
let test_attrs: Vec<_> = attrs
|
2019-12-22 16:42:04 -06:00
|
|
|
.iter()
|
2020-08-02 05:17:20 -05:00
|
|
|
.filter(|a| a.has_name(sym::doc))
|
2017-03-03 03:23:59 -06:00
|
|
|
.flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
|
2020-08-02 05:17:20 -05:00
|
|
|
.filter(|a| a.has_name(sym::test))
|
2017-03-03 03:23:59 -06:00
|
|
|
.collect();
|
|
|
|
let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
|
|
|
|
|
2015-04-06 20:39:39 -05:00
|
|
|
for attr in attrs {
|
2020-08-02 05:17:20 -05:00
|
|
|
if attr.has_name(sym::no_crate_inject) {
|
2015-04-06 20:39:39 -05:00
|
|
|
opts.no_crate_inject = true;
|
|
|
|
}
|
2020-08-02 05:17:20 -05:00
|
|
|
if attr.has_name(sym::attr) {
|
2015-04-06 20:39:39 -05:00
|
|
|
if let Some(l) = attr.meta_item_list() {
|
|
|
|
for item in l {
|
2016-08-19 20:58:14 -05:00
|
|
|
opts.attrs.push(pprust::meta_list_item_to_string(item));
|
2015-03-15 19:54:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-01 15:47:43 -05:00
|
|
|
opts
|
2015-03-15 19:54:30 -05:00
|
|
|
}
|
|
|
|
|
2019-05-04 14:37:12 -05:00
|
|
|
/// Documentation test failure modes.
|
|
|
|
enum TestFailure {
|
|
|
|
/// The test failed to compile.
|
|
|
|
CompileError,
|
|
|
|
/// The test is marked `compile_fail` but compiled successfully.
|
|
|
|
UnexpectedCompilePass,
|
|
|
|
/// The test failed to compile (as expected) but the compiler output did not contain all
|
|
|
|
/// expected error codes.
|
|
|
|
MissingErrorCodes(Vec<String>),
|
|
|
|
/// The test binary was unable to be executed.
|
|
|
|
ExecutionError(io::Error),
|
|
|
|
/// The test binary exited with a non-zero exit code.
|
|
|
|
///
|
|
|
|
/// This typically means an assertion in the test failed or another form of panic occurred.
|
|
|
|
ExecutionFailure(process::Output),
|
|
|
|
/// The test is marked `should_panic` but the test binary executed successfully.
|
|
|
|
UnexpectedRunPass,
|
|
|
|
}
|
|
|
|
|
2020-03-01 03:34:08 -06:00
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:07:13 -06:00
|
|
|
// NOTE: Keep this in sync with the equivalent structs in rustc
|
|
|
|
// and cargo.
|
|
|
|
// We could unify this struct the one in rustc but they have different
|
|
|
|
// ownership semantics, so doing so would create wasteful allocations.
|
2020-08-01 05:57:35 -05:00
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
|
|
struct UnusedExterns {
|
2020-08-09 18:57:35 -05:00
|
|
|
/// Lint level of the unused_crate_dependencies lint
|
|
|
|
lint_level: String,
|
2020-08-01 05:57:35 -05:00
|
|
|
/// List of unused externs by their names.
|
|
|
|
unused_extern_names: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2019-05-04 14:37:12 -05:00
|
|
|
fn run_test(
|
|
|
|
test: &str,
|
2021-06-20 19:24:59 -05:00
|
|
|
crate_name: &str,
|
2019-05-04 14:37:12 -05:00
|
|
|
line: usize,
|
2021-12-03 21:54:38 -06:00
|
|
|
rustdoc_options: RustdocOptions,
|
2021-12-03 21:43:50 -06:00
|
|
|
mut lang_string: LangString,
|
2019-05-04 14:37:12 -05:00
|
|
|
no_run: bool,
|
2019-04-26 15:52:56 -05:00
|
|
|
runtool: Option<String>,
|
|
|
|
runtool_args: Vec<String>,
|
2019-06-11 13:06:34 -05:00
|
|
|
target: TargetTriple,
|
2021-12-03 21:49:31 -06:00
|
|
|
opts: &GlobalTestOptions,
|
2019-05-04 14:37:12 -05:00
|
|
|
edition: Edition,
|
2020-03-01 03:34:08 -06:00
|
|
|
outdir: DirState,
|
|
|
|
path: PathBuf,
|
2020-12-06 06:57:37 -06:00
|
|
|
test_id: &str,
|
2020-08-01 05:57:35 -05:00
|
|
|
report_unused_externs: impl Fn(UnusedExterns),
|
2019-05-04 14:37:12 -05:00
|
|
|
) -> Result<(), TestFailure> {
|
2020-11-11 09:44:02 -06:00
|
|
|
let (test, line_offset, supports_color) =
|
2021-12-03 21:43:50 -06:00
|
|
|
make_test(test, Some(crate_name), lang_string.test_harness, opts, edition, Some(test_id));
|
2019-04-23 16:39:26 -05:00
|
|
|
|
2018-12-08 13:30:23 -06:00
|
|
|
let output_file = outdir.path().join("rust_out");
|
|
|
|
|
2021-12-03 21:54:38 -06:00
|
|
|
let rustc_binary = rustdoc_options
|
2019-12-22 16:42:04 -06:00
|
|
|
.test_builder
|
Fix clippy warnings
Fixes clippy::{cone_on_copy, filter_next, redundant_closure, single_char_pattern, len_zero,redundant_field_names, useless_format, identity_conversion, map_clone, into_iter_on_ref, needless_return, option_as_ref_deref, unused_unit, unnecessary_mut_passed}
2020-05-11 06:01:37 -05:00
|
|
|
.as_deref()
|
2019-12-22 16:42:04 -06:00
|
|
|
.unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
|
2019-09-09 20:11:27 -05:00
|
|
|
let mut compiler = Command::new(&rustc_binary);
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--crate-type").arg("bin");
|
2021-12-03 21:54:38 -06:00
|
|
|
for cfg in &rustdoc_options.cfgs {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--cfg").arg(&cfg);
|
|
|
|
}
|
2022-02-19 07:31:20 -06:00
|
|
|
if !rustdoc_options.check_cfgs.is_empty() {
|
|
|
|
compiler.arg("-Z").arg("unstable-options");
|
|
|
|
for check_cfg in &rustdoc_options.check_cfgs {
|
|
|
|
compiler.arg("--check-cfg").arg(&check_cfg);
|
|
|
|
}
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
if let Some(sysroot) = rustdoc_options.maybe_sysroot {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--sysroot").arg(sysroot);
|
|
|
|
}
|
|
|
|
compiler.arg("--edition").arg(&edition.to_string());
|
|
|
|
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
|
2019-12-22 16:42:04 -06:00
|
|
|
compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("-o").arg(&output_file);
|
2021-12-03 21:43:50 -06:00
|
|
|
if lang_string.test_harness {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--test");
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
if rustdoc_options.json_unused_externs && !lang_string.compile_fail {
|
2020-08-01 05:57:35 -05:00
|
|
|
compiler.arg("--error-format=json");
|
|
|
|
compiler.arg("--json").arg("unused-externs");
|
|
|
|
compiler.arg("-Z").arg("unstable-options");
|
2020-08-09 18:57:35 -05:00
|
|
|
compiler.arg("-W").arg("unused_crate_dependencies");
|
2020-08-01 05:57:35 -05:00
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
for lib_str in &rustdoc_options.lib_strs {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("-L").arg(&lib_str);
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
for extern_str in &rustdoc_options.extern_strs {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--extern").arg(&extern_str);
|
|
|
|
}
|
2019-08-30 14:26:04 -05:00
|
|
|
compiler.arg("-Ccodegen-units=1");
|
2021-12-03 21:54:38 -06:00
|
|
|
for codegen_options_str in &rustdoc_options.codegen_options_strs {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("-C").arg(&codegen_options_str);
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
for debugging_option_str in &rustdoc_options.debugging_opts_strs {
|
2019-10-10 19:00:00 -05:00
|
|
|
compiler.arg("-Z").arg(&debugging_option_str);
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
if no_run && !lang_string.compile_fail && rustdoc_options.persist_doctests.is_none() {
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("--emit=metadata");
|
|
|
|
}
|
2020-04-10 14:15:10 -05:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
});
|
2021-12-03 21:54:38 -06:00
|
|
|
if let ErrorOutputType::HumanReadable(kind) = rustdoc_options.error_format {
|
2021-02-02 14:18:46 -06:00
|
|
|
let (short, color_config) = kind.unzip();
|
|
|
|
|
|
|
|
if short {
|
|
|
|
compiler.arg("--error-format").arg("short");
|
|
|
|
}
|
|
|
|
|
2020-11-11 09:44:02 -06:00
|
|
|
match color_config {
|
|
|
|
ColorConfig::Never => {
|
|
|
|
compiler.arg("--color").arg("never");
|
|
|
|
}
|
|
|
|
ColorConfig::Always => {
|
|
|
|
compiler.arg("--color").arg("always");
|
|
|
|
}
|
|
|
|
ColorConfig::Auto => {
|
|
|
|
compiler.arg("--color").arg(if supports_color { "always" } else { "never" });
|
2020-07-13 08:34:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-02 08:09:09 -05:00
|
|
|
|
2019-08-29 16:15:31 -05:00
|
|
|
compiler.arg("-");
|
|
|
|
compiler.stdin(Stdio::piped());
|
|
|
|
compiler.stderr(Stdio::piped());
|
|
|
|
|
|
|
|
let mut child = compiler.spawn().expect("Failed to spawn rustc process");
|
|
|
|
{
|
|
|
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
|
|
|
stdin.write_all(test.as_bytes()).expect("could write out test sources");
|
|
|
|
}
|
|
|
|
let output = child.wait_with_output().expect("Failed to read stdout");
|
2018-07-09 19:11:06 -05:00
|
|
|
|
2019-08-29 16:15:31 -05:00
|
|
|
struct Bomb<'a>(&'a str);
|
|
|
|
impl Drop for Bomb<'_> {
|
|
|
|
fn drop(&mut self) {
|
2019-12-22 16:42:04 -06:00
|
|
|
eprint!("{}", self.0);
|
2019-08-29 16:15:31 -05:00
|
|
|
}
|
|
|
|
}
|
2020-08-01 05:57:35 -05:00
|
|
|
let mut out_lines = str::from_utf8(&output.stderr)
|
|
|
|
.unwrap()
|
|
|
|
.lines()
|
|
|
|
.filter(|l| {
|
|
|
|
if let Ok(uext) = serde_json::from_str::<UnusedExterns>(l) {
|
|
|
|
report_unused_externs(uext);
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// Add a \n to the end to properly terminate the last line,
|
|
|
|
// but only if there was output to be printed
|
2021-10-01 10:12:39 -05:00
|
|
|
if !out_lines.is_empty() {
|
2020-08-01 05:57:35 -05:00
|
|
|
out_lines.push("");
|
|
|
|
}
|
|
|
|
|
|
|
|
let out = out_lines.join("\n");
|
2019-08-29 16:15:31 -05:00
|
|
|
let _bomb = Bomb(&out);
|
2021-12-03 21:43:50 -06:00
|
|
|
match (output.status.success(), lang_string.compile_fail) {
|
2019-08-29 16:15:31 -05:00
|
|
|
(true, true) => {
|
2019-05-04 14:37:12 -05:00
|
|
|
return Err(TestFailure::UnexpectedCompilePass);
|
2018-07-09 19:11:06 -05:00
|
|
|
}
|
2019-08-29 16:15:31 -05:00
|
|
|
(true, false) => {}
|
|
|
|
(false, true) => {
|
2021-12-03 21:43:50 -06:00
|
|
|
if !lang_string.error_codes.is_empty() {
|
2020-11-13 04:19:39 -06:00
|
|
|
// 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 ":".
|
2021-12-03 21:43:50 -06:00
|
|
|
lang_string.error_codes.retain(|err| !out.contains(&format!("error[{}]", err)));
|
2019-05-04 14:37:12 -05:00
|
|
|
|
2021-12-03 21:43:50 -06:00
|
|
|
if !lang_string.error_codes.is_empty() {
|
|
|
|
return Err(TestFailure::MissingErrorCodes(lang_string.error_codes));
|
2019-05-04 14:37:12 -05:00
|
|
|
}
|
2016-06-09 16:50:52 -05:00
|
|
|
}
|
|
|
|
}
|
2019-08-29 16:15:31 -05:00
|
|
|
(false, false) => {
|
2019-05-04 14:37:12 -05:00
|
|
|
return Err(TestFailure::CompileError);
|
2017-07-02 08:09:09 -05:00
|
|
|
}
|
2018-07-09 19:11:06 -05:00
|
|
|
}
|
2016-06-09 16:50:52 -05:00
|
|
|
|
2019-05-04 14:37:12 -05:00
|
|
|
if no_run {
|
|
|
|
return Ok(());
|
2018-07-09 19:11:06 -05:00
|
|
|
}
|
2013-12-22 13:23:04 -06:00
|
|
|
|
2014-02-28 14:33:49 -06:00
|
|
|
// Run the code!
|
2019-04-26 15:52:56 -05:00
|
|
|
let mut cmd;
|
|
|
|
|
|
|
|
if let Some(tool) = runtool {
|
|
|
|
cmd = Command::new(tool);
|
|
|
|
cmd.args(runtool_args);
|
2020-04-10 14:15:10 -05:00
|
|
|
cmd.arg(output_file);
|
2019-06-06 18:01:53 -05:00
|
|
|
} else {
|
2019-04-26 15:52:56 -05:00
|
|
|
cmd = Command::new(output_file);
|
|
|
|
}
|
2021-12-03 21:54:38 -06:00
|
|
|
if let Some(run_directory) = rustdoc_options.test_run_directory {
|
2021-01-22 03:54:53 -06:00
|
|
|
cmd.current_dir(run_directory);
|
|
|
|
}
|
2018-12-08 13:30:23 -06:00
|
|
|
|
2021-12-03 21:54:38 -06:00
|
|
|
let result = if rustdoc_options.nocapture {
|
2021-07-09 03:58:02 -05:00
|
|
|
cmd.status().map(|status| process::Output {
|
|
|
|
status,
|
|
|
|
stdout: Vec::new(),
|
|
|
|
stderr: Vec::new(),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
cmd.output()
|
|
|
|
};
|
|
|
|
match result {
|
2019-05-04 14:37:12 -05:00
|
|
|
Err(e) => return Err(TestFailure::ExecutionError(e)),
|
2014-01-30 13:30:21 -06:00
|
|
|
Ok(out) => {
|
2021-12-03 21:43:50 -06:00
|
|
|
if lang_string.should_panic && out.status.success() {
|
2019-05-04 14:37:12 -05:00
|
|
|
return Err(TestFailure::UnexpectedRunPass);
|
2021-12-03 21:43:50 -06:00
|
|
|
} else if !lang_string.should_panic && !out.status.success() {
|
2019-05-04 14:37:12 -05:00
|
|
|
return Err(TestFailure::ExecutionFailure(out));
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-04 14:37:12 -05:00
|
|
|
|
|
|
|
Ok(())
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
2019-04-23 16:39:26 -05:00
|
|
|
/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
|
2020-11-11 09:44:02 -06:00
|
|
|
/// lines before the test code begins as well as if the output stream supports colors or not.
|
2020-11-14 16:59:58 -06:00
|
|
|
crate fn make_test(
|
2019-12-22 16:42:04 -06:00
|
|
|
s: &str,
|
2021-06-20 19:24:59 -05:00
|
|
|
crate_name: Option<&str>,
|
2019-12-22 16:42:04 -06:00
|
|
|
dont_insert_main: bool,
|
2021-12-03 21:49:31 -06:00
|
|
|
opts: &GlobalTestOptions,
|
2019-12-22 16:42:04 -06:00
|
|
|
edition: Edition,
|
2020-12-06 06:57:37 -06:00
|
|
|
test_id: Option<&str>,
|
2020-11-11 09:44:02 -06:00
|
|
|
) -> (String, usize, bool) {
|
2018-10-20 13:45:44 -05:00
|
|
|
let (crate_attrs, everything_else, crates) = partition_source(s);
|
2018-02-09 09:24:23 -06:00
|
|
|
let everything_else = everything_else.trim();
|
2018-01-08 08:47:23 -06:00
|
|
|
let mut line_offset = 0;
|
2014-06-06 11:12:18 -05:00
|
|
|
let mut prog = String::new();
|
2020-11-11 09:44:02 -06:00
|
|
|
let mut supports_color = false;
|
2015-03-12 15:01:06 -05:00
|
|
|
|
2021-11-26 12:52:28 -06:00
|
|
|
if opts.attrs.is_empty() {
|
2017-11-04 14:13:44 -05:00
|
|
|
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
|
|
|
|
// lints that are commonly triggered in doctests. The crate-level test attributes are
|
|
|
|
// commonly used to make tests fail in case they trigger warnings, so having this there in
|
|
|
|
// that case may cause some tests to pass when they shouldn't have.
|
|
|
|
prog.push_str("#![allow(unused)]\n");
|
2018-01-08 08:47:23 -06:00
|
|
|
line_offset += 1;
|
2017-11-04 14:13:44 -05:00
|
|
|
}
|
2015-03-12 15:01:06 -05:00
|
|
|
|
2017-11-04 14:13:44 -05:00
|
|
|
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
|
2015-04-06 20:39:39 -05:00
|
|
|
for attr in &opts.attrs {
|
|
|
|
prog.push_str(&format!("#![{}]\n", attr));
|
2018-01-08 08:47:23 -06:00
|
|
|
line_offset += 1;
|
2014-06-06 11:12:18 -05:00
|
|
|
}
|
2014-03-08 04:30:43 -06:00
|
|
|
|
2017-11-04 14:13:44 -05:00
|
|
|
// Now push any outer attributes from the example, assuming they
|
|
|
|
// are intended to be crate attributes.
|
|
|
|
prog.push_str(&crate_attrs);
|
2018-12-13 14:31:17 -06:00
|
|
|
prog.push_str(&crates);
|
2017-11-04 14:13:44 -05:00
|
|
|
|
2020-02-29 11:16:26 -06:00
|
|
|
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
|
2018-10-05 18:22:19 -05:00
|
|
|
// crate already is included.
|
2020-01-02 17:34:00 -06:00
|
|
|
let result = rustc_driver::catch_fatal_errors(|| {
|
2021-05-05 14:31:25 -05:00
|
|
|
rustc_span::create_session_if_not_set_then(edition, |_| {
|
2020-11-11 09:44:02 -06:00
|
|
|
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
2020-01-09 04:18:47 -06:00
|
|
|
use rustc_errors::Handler;
|
2020-01-02 17:34:00 -06:00
|
|
|
use rustc_parse::maybe_new_parser_from_source_str;
|
2021-11-22 20:47:58 -06:00
|
|
|
use rustc_parse::parser::ForceCollect;
|
2020-01-11 08:03:15 -06:00
|
|
|
use rustc_session::parse::ParseSess;
|
2020-01-02 17:34:00 -06:00
|
|
|
use rustc_span::source_map::FilePathMapping;
|
|
|
|
|
|
|
|
let filename = FileName::anon_source_code(s);
|
2020-02-28 06:36:45 -06:00
|
|
|
let source = crates + everything_else;
|
2020-01-02 17:34:00 -06:00
|
|
|
|
|
|
|
// Any errors in parsing should also appear when the doctest is compiled for real, so just
|
2020-02-29 11:16:26 -06:00
|
|
|
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
|
2020-02-22 08:07:05 -06:00
|
|
|
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
2020-11-12 15:32:42 -06:00
|
|
|
supports_color =
|
|
|
|
EmitterWriter::stderr(ColorConfig::Auto, None, false, false, Some(80), false)
|
|
|
|
.supports_color();
|
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
let emitter =
|
2021-09-20 18:49:47 -05:00
|
|
|
EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
|
2020-11-11 09:44:02 -06:00
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
|
2021-09-20 18:49:47 -05:00
|
|
|
let handler = Handler::with_emitter(false, None, box emitter);
|
2020-02-22 08:07:05 -06:00
|
|
|
let sess = ParseSess::with_span_handler(handler, sm);
|
2020-01-02 17:34:00 -06:00
|
|
|
|
|
|
|
let mut found_main = false;
|
2021-06-20 19:24:59 -05:00
|
|
|
let mut found_extern_crate = crate_name.is_none();
|
2020-01-02 17:34:00 -06:00
|
|
|
let mut found_macro = false;
|
|
|
|
|
|
|
|
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
|
|
|
|
Ok(p) => p,
|
|
|
|
Err(errs) => {
|
2022-01-25 21:39:14 -06:00
|
|
|
drop(errs);
|
2020-01-02 17:34:00 -06:00
|
|
|
return (found_main, found_extern_crate, found_macro);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-22 20:47:58 -06:00
|
|
|
loop {
|
|
|
|
match parser.parse_item(ForceCollect::No) {
|
|
|
|
Ok(Some(item)) => {
|
2020-01-02 17:34:00 -06:00
|
|
|
if !found_main {
|
|
|
|
if let ast::ItemKind::Fn(..) = item.kind {
|
|
|
|
if item.ident.name == sym::main {
|
|
|
|
found_main = true;
|
|
|
|
}
|
2018-10-20 13:45:44 -05:00
|
|
|
}
|
|
|
|
}
|
2018-10-07 11:36:47 -05:00
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
if !found_extern_crate {
|
|
|
|
if let ast::ItemKind::ExternCrate(original) = item.kind {
|
2021-06-20 19:24:59 -05:00
|
|
|
// This code will never be reached if `crate_name` is none because
|
2020-01-02 17:34:00 -06:00
|
|
|
// `found_extern_crate` is initialized to `true` if it is none.
|
2021-06-20 19:24:59 -05:00
|
|
|
let crate_name = crate_name.unwrap();
|
2018-10-20 13:45:44 -05:00
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
match original {
|
2021-06-20 19:24:59 -05:00
|
|
|
Some(name) => found_extern_crate = name.as_str() == crate_name,
|
|
|
|
None => found_extern_crate = item.ident.as_str() == crate_name,
|
2020-01-02 17:34:00 -06:00
|
|
|
}
|
2018-10-20 13:45:44 -05:00
|
|
|
}
|
|
|
|
}
|
2018-10-05 18:22:19 -05:00
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
if !found_macro {
|
2020-02-29 10:32:20 -06:00
|
|
|
if let ast::ItemKind::MacCall(..) = item.kind {
|
2020-01-02 17:34:00 -06:00
|
|
|
found_macro = true;
|
|
|
|
}
|
2018-12-20 15:10:07 -06:00
|
|
|
}
|
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
if found_main && found_extern_crate {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-11-22 20:47:58 -06:00
|
|
|
Ok(None) => break,
|
2022-01-25 21:39:14 -06:00
|
|
|
Err(e) => {
|
2021-11-22 20:47:58 -06:00
|
|
|
e.cancel();
|
|
|
|
break;
|
|
|
|
}
|
rustdoc doctest: detect `fn main` after an unexpected semicolon
The basic problem with this is that rustdoc, when hunting for `fn main`, will stop
parsing after it reaches a fatal error. This unexpected semicolon was a fatal error,
so in `src/test/rustdoc-ui/failed-doctest-extra-semicolon-on-item.rs`, it would wrap
the doctest in an implied main function, turning it into this:
fn main() {
struct S {};
fn main() {
assert_eq!(0, 1);
}
}
This, as it turns out, is totally valid, and it executes no assertions, so *it passes,*
even though the user wanted it to execute the assertion.
The Rust parser already has the ability to recover from these unexpected semicolons,
but to do so, it needs to use the `parse_mod` function, so this commit changes it to do that.
2021-11-18 17:15:12 -06:00
|
|
|
}
|
2021-11-22 20:47:58 -06:00
|
|
|
|
|
|
|
// The supplied slice is only used for diagnostics,
|
|
|
|
// which are swallowed here anyway.
|
|
|
|
parser.maybe_consume_incorrect_semicolon(&[]);
|
2018-10-05 18:22:19 -05:00
|
|
|
}
|
|
|
|
|
2021-01-15 06:51:32 -06:00
|
|
|
// Reset errors so that they won't be reported as compiler bugs when dropping the
|
|
|
|
// handler. Any errors in the tests will be reported when the test file is compiled,
|
|
|
|
// Note that we still need to cancel the errors above otherwise `DiagnosticBuilder`
|
|
|
|
// will panic on drop.
|
|
|
|
sess.span_diagnostic.reset_err_count();
|
|
|
|
|
2020-01-02 17:34:00 -06:00
|
|
|
(found_main, found_extern_crate, found_macro)
|
|
|
|
})
|
2018-10-05 18:22:19 -05:00
|
|
|
});
|
2022-02-18 17:43:47 -06:00
|
|
|
let Ok((already_has_main, already_has_extern_crate, found_macro)) = result
|
|
|
|
else {
|
|
|
|
// If the parser panicked due to a fatal error, pass the test code through unchanged.
|
|
|
|
// The error will be reported during compilation.
|
|
|
|
return (s.to_owned(), 0, false);
|
2020-01-02 17:34:00 -06:00
|
|
|
};
|
2018-10-05 18:22:19 -05:00
|
|
|
|
2018-12-20 15:10:07 -06:00
|
|
|
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
|
|
|
|
// see it. In that case, run the old text-based scan to see if they at least have a main
|
|
|
|
// function written inside a macro invocation. See
|
|
|
|
// https://github.com/rust-lang/rust/issues/56898
|
|
|
|
let already_has_main = if found_macro && !already_has_main {
|
|
|
|
s.lines()
|
|
|
|
.map(|line| {
|
|
|
|
let comment = line.find("//");
|
2019-12-22 16:42:04 -06:00
|
|
|
if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
|
2018-12-20 15:10:07 -06:00
|
|
|
})
|
|
|
|
.any(|code| code.contains("fn main"))
|
|
|
|
} else {
|
|
|
|
already_has_main
|
|
|
|
};
|
|
|
|
|
2014-06-18 03:07:59 -05:00
|
|
|
// Don't inject `extern crate std` because it's already injected by the
|
|
|
|
// compiler.
|
2021-06-20 19:24:59 -05:00
|
|
|
if !already_has_extern_crate && !opts.no_crate_inject && crate_name != Some("std") {
|
|
|
|
if let Some(crate_name) = crate_name {
|
2020-12-06 16:56:01 -06:00
|
|
|
// Don't inject `extern crate` if the crate is never used.
|
|
|
|
// NOTE: this is terribly inaccurate because it doesn't actually
|
|
|
|
// parse the source, but only has false positives, not false
|
|
|
|
// negatives.
|
2021-06-20 19:24:59 -05:00
|
|
|
if s.contains(crate_name) {
|
|
|
|
prog.push_str(&format!("extern crate r#{};\n", crate_name));
|
2018-01-08 08:47:23 -06:00
|
|
|
line_offset += 1;
|
2014-06-06 11:12:18 -05:00
|
|
|
}
|
2014-02-17 22:43:32 -06:00
|
|
|
}
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
2017-09-20 00:58:33 -05:00
|
|
|
|
2018-12-03 11:16:20 -06:00
|
|
|
// FIXME: This code cannot yet handle no_std test cases yet
|
|
|
|
if dont_insert_main || already_has_main || prog.contains("![no_std]") {
|
2018-02-09 09:24:23 -06:00
|
|
|
prog.push_str(everything_else);
|
2013-12-22 13:23:04 -06:00
|
|
|
} else {
|
2018-12-03 11:16:20 -06:00
|
|
|
let returns_result = everything_else.trim_end().ends_with("(())");
|
2020-12-06 06:57:37 -06:00
|
|
|
// Give each doctest main function a unique name.
|
2021-10-21 09:04:22 -05:00
|
|
|
// This is for example needed for the tooling around `-C instrument-coverage`.
|
2020-12-06 06:57:37 -06:00
|
|
|
let inner_fn_name = if let Some(test_id) = test_id {
|
|
|
|
format!("_doctest_main_{}", test_id)
|
|
|
|
} else {
|
|
|
|
"_inner".into()
|
|
|
|
};
|
2020-12-20 03:00:32 -06:00
|
|
|
let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
|
2018-12-03 11:16:20 -06:00
|
|
|
let (main_pre, main_post) = if returns_result {
|
2019-12-22 16:42:04 -06:00
|
|
|
(
|
2020-12-06 06:57:37 -06:00
|
|
|
format!(
|
2020-12-20 03:00:32 -06:00
|
|
|
"fn main() {{ {}fn {}() -> Result<(), impl core::fmt::Debug> {{\n",
|
|
|
|
inner_attr, inner_fn_name
|
2020-12-06 06:57:37 -06:00
|
|
|
),
|
2020-12-28 13:44:33 -06:00
|
|
|
format!("\n}} {}().unwrap() }}", inner_fn_name),
|
2020-12-06 06:57:37 -06:00
|
|
|
)
|
|
|
|
} else if test_id.is_some() {
|
|
|
|
(
|
2020-12-20 03:00:32 -06:00
|
|
|
format!("fn main() {{ {}fn {}() {{\n", inner_attr, inner_fn_name),
|
2020-12-28 13:44:33 -06:00
|
|
|
format!("\n}} {}() }}", inner_fn_name),
|
2019-12-22 16:42:04 -06:00
|
|
|
)
|
2018-12-03 11:16:20 -06:00
|
|
|
} else {
|
2020-12-06 06:57:37 -06:00
|
|
|
("fn main() {\n".into(), "\n}".into())
|
2018-12-03 11:16:20 -06:00
|
|
|
};
|
2020-12-06 06:57:37 -06:00
|
|
|
// Note on newlines: We insert a line/newline *before*, and *after*
|
|
|
|
// the doctest and adjust the `line_offset` accordingly.
|
2021-10-21 09:04:22 -05:00
|
|
|
// In the case of `-C instrument-coverage`, this means that the generated
|
2020-12-06 06:57:37 -06:00
|
|
|
// inner `main` function spans from the doctest opening codeblock to the
|
|
|
|
// closing one. For example
|
|
|
|
// /// ``` <- start of the inner main
|
|
|
|
// /// <- code under doctest
|
|
|
|
// /// ``` <- end of the inner main
|
2018-01-08 08:47:23 -06:00
|
|
|
line_offset += 1;
|
2020-12-06 06:57:37 -06:00
|
|
|
|
|
|
|
prog.extend([&main_pre, everything_else, &main_post].iter().cloned());
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
2018-12-13 14:31:17 -06:00
|
|
|
debug!("final doctest:\n{}", prog);
|
|
|
|
|
2020-11-11 09:44:02 -06:00
|
|
|
(prog, line_offset, supports_color)
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
2016-12-04 13:07:27 -06:00
|
|
|
// FIXME(aburka): use a real parser to deal with multiline attributes
|
2018-10-20 13:45:44 -05:00
|
|
|
fn partition_source(s: &str) -> (String, String, String) {
|
2018-12-13 14:31:42 -06:00
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
|
|
enum PartitionState {
|
|
|
|
Attrs,
|
|
|
|
Crates,
|
|
|
|
Other,
|
|
|
|
}
|
|
|
|
let mut state = PartitionState::Attrs;
|
2015-03-12 15:01:06 -05:00
|
|
|
let mut before = String::new();
|
2018-10-20 13:45:44 -05:00
|
|
|
let mut crates = String::new();
|
2015-03-12 15:01:06 -05:00
|
|
|
let mut after = String::new();
|
|
|
|
|
|
|
|
for line in s.lines() {
|
2015-03-15 19:54:30 -05:00
|
|
|
let trimline = line.trim();
|
2018-12-13 14:31:42 -06:00
|
|
|
|
|
|
|
// FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
|
|
|
|
// shunted into "everything else"
|
|
|
|
match state {
|
|
|
|
PartitionState::Attrs => {
|
2019-12-22 16:42:04 -06:00
|
|
|
state = if trimline.starts_with("#![")
|
|
|
|
|| trimline.chars().all(|c| c.is_whitespace())
|
|
|
|
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
|
2018-12-13 14:31:42 -06:00
|
|
|
{
|
|
|
|
PartitionState::Attrs
|
2019-12-22 16:42:04 -06:00
|
|
|
} else if trimline.starts_with("extern crate")
|
|
|
|
|| trimline.starts_with("#[macro_use] extern crate")
|
2018-12-13 14:31:42 -06:00
|
|
|
{
|
|
|
|
PartitionState::Crates
|
|
|
|
} else {
|
|
|
|
PartitionState::Other
|
|
|
|
};
|
|
|
|
}
|
|
|
|
PartitionState::Crates => {
|
2019-12-22 16:42:04 -06:00
|
|
|
state = if trimline.starts_with("extern crate")
|
|
|
|
|| trimline.starts_with("#[macro_use] extern crate")
|
|
|
|
|| trimline.chars().all(|c| c.is_whitespace())
|
|
|
|
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
|
2018-12-13 14:31:42 -06:00
|
|
|
{
|
|
|
|
PartitionState::Crates
|
|
|
|
} else {
|
|
|
|
PartitionState::Other
|
|
|
|
};
|
|
|
|
}
|
|
|
|
PartitionState::Other => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
match state {
|
|
|
|
PartitionState::Attrs => {
|
|
|
|
before.push_str(line);
|
2020-12-30 19:49:44 -06:00
|
|
|
before.push('\n');
|
2018-12-13 14:31:42 -06:00
|
|
|
}
|
|
|
|
PartitionState::Crates => {
|
2018-10-20 13:45:44 -05:00
|
|
|
crates.push_str(line);
|
2020-12-30 19:49:44 -06:00
|
|
|
crates.push('\n');
|
2018-10-20 13:45:44 -05:00
|
|
|
}
|
2018-12-13 14:31:42 -06:00
|
|
|
PartitionState::Other => {
|
|
|
|
after.push_str(line);
|
2020-12-30 19:49:44 -06:00
|
|
|
after.push('\n');
|
2018-12-13 14:31:42 -06:00
|
|
|
}
|
2015-03-12 15:01:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 14:31:42 -06:00
|
|
|
debug!("before:\n{}", before);
|
|
|
|
debug!("crates:\n{}", crates);
|
|
|
|
debug!("after:\n{}", after);
|
|
|
|
|
2018-10-20 13:45:44 -05:00
|
|
|
(before, after, crates)
|
2015-03-12 15:01:06 -05:00
|
|
|
}
|
|
|
|
|
2020-11-14 16:59:58 -06:00
|
|
|
crate trait Tester {
|
2018-09-17 17:25:50 -05:00
|
|
|
fn add_test(&mut self, test: String, config: LangString, line: usize);
|
|
|
|
fn get_line(&self) -> usize {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
fn register_header(&mut self, _name: &str, _level: u32) {}
|
|
|
|
}
|
|
|
|
|
2020-11-14 16:59:58 -06:00
|
|
|
crate struct Collector {
|
2021-07-13 20:40:09 -05:00
|
|
|
crate tests: Vec<test::TestDescAndFn>,
|
2017-09-16 02:59:15 -05:00
|
|
|
|
|
|
|
// The name of the test displayed to the user, separated by `::`.
|
|
|
|
//
|
|
|
|
// In tests from Rust source, this is the path to the item
|
2018-11-26 20:59:49 -06:00
|
|
|
// e.g., `["std", "vec", "Vec", "push"]`.
|
2017-09-16 02:59:15 -05:00
|
|
|
//
|
|
|
|
// In tests from a markdown file, this is the titles of all headers (h1~h6)
|
2018-11-26 20:59:49 -06:00
|
|
|
// of the sections that contain the code block, e.g., if the markdown file is
|
2017-09-16 02:59:15 -05:00
|
|
|
// written as:
|
|
|
|
//
|
|
|
|
// ``````markdown
|
|
|
|
// # Title
|
|
|
|
//
|
|
|
|
// ## Subtitle
|
|
|
|
//
|
|
|
|
// ```rust
|
|
|
|
// assert!(true);
|
|
|
|
// ```
|
|
|
|
// ``````
|
|
|
|
//
|
|
|
|
// the `names` vector of that test will be `["Title", "Subtitle"]`.
|
2014-05-22 18:57:53 -05:00
|
|
|
names: Vec<String>,
|
2017-09-16 02:59:15 -05:00
|
|
|
|
2021-12-03 21:54:38 -06:00
|
|
|
rustdoc_options: RustdocOptions,
|
2014-03-28 12:27:24 -05:00
|
|
|
use_headers: bool,
|
2019-08-27 17:44:11 -05:00
|
|
|
enable_per_target_ignores: bool,
|
2021-06-20 19:34:38 -05:00
|
|
|
crate_name: Symbol,
|
2021-12-03 21:49:31 -06:00
|
|
|
opts: GlobalTestOptions,
|
2017-02-06 15:11:03 -06:00
|
|
|
position: Span,
|
2018-08-18 05:14:14 -05:00
|
|
|
source_map: Option<Lrc<SourceMap>>,
|
2017-12-14 01:09:19 -06:00
|
|
|
filename: Option<PathBuf>,
|
2020-12-31 22:25:30 -06:00
|
|
|
visited_tests: FxHashMap<(String, usize), usize>,
|
2020-08-01 05:57:35 -05:00
|
|
|
unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
|
|
|
|
compiling_test_count: AtomicUsize,
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Collector {
|
2020-11-14 16:59:58 -06:00
|
|
|
crate fn new(
|
2021-06-20 19:34:38 -05:00
|
|
|
crate_name: Symbol,
|
2021-12-03 21:54:38 -06:00
|
|
|
rustdoc_options: RustdocOptions,
|
2019-12-22 16:42:04 -06:00
|
|
|
use_headers: bool,
|
2021-12-03 21:49:31 -06:00
|
|
|
opts: GlobalTestOptions,
|
2019-12-22 16:42:04 -06:00
|
|
|
source_map: Option<Lrc<SourceMap>>,
|
|
|
|
filename: Option<PathBuf>,
|
|
|
|
enable_per_target_ignores: bool,
|
|
|
|
) -> Collector {
|
2014-03-06 21:31:41 -06:00
|
|
|
Collector {
|
2014-03-05 17:28:08 -06:00
|
|
|
tests: Vec::new(),
|
|
|
|
names: Vec::new(),
|
2021-12-03 21:54:38 -06:00
|
|
|
rustdoc_options,
|
2017-08-07 00:54:09 -05:00
|
|
|
use_headers,
|
2019-08-27 17:44:11 -05:00
|
|
|
enable_per_target_ignores,
|
2021-06-20 19:24:59 -05:00
|
|
|
crate_name,
|
2017-08-07 00:54:09 -05:00
|
|
|
opts,
|
2017-02-06 15:11:03 -06:00
|
|
|
position: DUMMY_SP,
|
2018-08-18 05:14:14 -05:00
|
|
|
source_map,
|
2017-08-07 00:54:09 -05:00
|
|
|
filename,
|
2020-12-31 22:25:30 -06:00
|
|
|
visited_tests: FxHashMap::default(),
|
2020-08-01 05:57:35 -05:00
|
|
|
unused_extern_reports: Default::default(),
|
|
|
|
compiling_test_count: AtomicUsize::new(0),
|
2016-10-01 19:11:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 01:09:19 -06:00
|
|
|
fn generate_name(&self, line: usize, filename: &FileName) -> String {
|
2020-05-13 17:31:33 -05:00
|
|
|
let mut item_path = self.names.join("::");
|
2021-10-01 04:46:34 -05:00
|
|
|
item_path.retain(|c| c != ' ');
|
2020-05-13 17:31:33 -05:00
|
|
|
if !item_path.is_empty() {
|
|
|
|
item_path.push(' ');
|
|
|
|
}
|
2021-04-19 17:27:02 -05:00
|
|
|
format!("{} - {}(line {})", filename.prefer_local(), item_path, line)
|
2017-04-13 18:23:14 -05:00
|
|
|
}
|
|
|
|
|
2020-11-14 16:59:58 -06:00
|
|
|
crate fn set_position(&mut self, position: Span) {
|
2018-09-17 17:25:50 -05:00
|
|
|
self.position = position;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_filename(&self) -> FileName {
|
|
|
|
if let Some(ref source_map) = self.source_map {
|
|
|
|
let filename = source_map.span_to_filename(self.position);
|
|
|
|
if let FileName::Real(ref filename) = filename {
|
|
|
|
if let Ok(cur_dir) = env::current_dir() {
|
2021-04-08 18:54:51 -05:00
|
|
|
if let Some(local_path) = filename.local_path() {
|
|
|
|
if let Ok(path) = local_path.strip_prefix(&cur_dir) {
|
|
|
|
return path.to_owned().into();
|
|
|
|
}
|
2018-09-17 17:25:50 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
filename
|
|
|
|
} else if let Some(ref filename) = self.filename {
|
|
|
|
filename.clone().into()
|
|
|
|
} else {
|
|
|
|
FileName::Custom("input".to_owned())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Tester for Collector {
|
|
|
|
fn add_test(&mut self, test: String, config: LangString, line: usize) {
|
2018-07-21 17:54:30 -05:00
|
|
|
let filename = self.get_filename();
|
2017-04-13 18:23:14 -05:00
|
|
|
let name = self.generate_name(line, &filename);
|
2021-06-20 19:24:59 -05:00
|
|
|
let crate_name = self.crate_name.to_string();
|
2015-04-06 20:39:39 -05:00
|
|
|
let opts = self.opts.clone();
|
2021-12-03 21:54:38 -06:00
|
|
|
let edition = config.edition.unwrap_or(self.rustdoc_options.edition);
|
|
|
|
let rustdoc_options = self.rustdoc_options.clone();
|
|
|
|
let runtool = self.rustdoc_options.runtool.clone();
|
|
|
|
let runtool_args = self.rustdoc_options.runtool_args.clone();
|
|
|
|
let target = self.rustdoc_options.target.clone();
|
2019-06-11 13:06:34 -05:00
|
|
|
let target_str = target.to_string();
|
2020-08-01 05:57:35 -05:00
|
|
|
let unused_externs = self.unused_extern_reports.clone();
|
2021-12-03 21:54:38 -06:00
|
|
|
let no_run = config.no_run || rustdoc_options.no_run;
|
2020-08-01 05:57:35 -05:00
|
|
|
if !config.compile_fail {
|
|
|
|
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
|
|
|
|
}
|
2018-12-08 13:17:50 -06:00
|
|
|
|
2020-03-01 03:34:08 -06:00
|
|
|
let path = match &filename {
|
2021-04-08 18:54:51 -05:00
|
|
|
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
|
2021-04-19 17:27:02 -05:00
|
|
|
unreachable!("doctest from a different crate");
|
2021-04-08 18:54:51 -05:00
|
|
|
}
|
|
|
|
}
|
2020-03-01 03:34:08 -06:00
|
|
|
_ => PathBuf::from(r"doctest.rs"),
|
|
|
|
};
|
|
|
|
|
2020-12-06 06:57:37 -06:00
|
|
|
// For example `module/file.rs` would become `module_file_rs`
|
|
|
|
let file = filename
|
2021-04-19 17:27:02 -05:00
|
|
|
.prefer_local()
|
|
|
|
.to_string_lossy()
|
2020-12-06 06:57:37 -06:00
|
|
|
.chars()
|
|
|
|
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
|
|
|
|
.collect::<String>();
|
|
|
|
let test_id = format!(
|
|
|
|
"{file}_{line}_{number}",
|
|
|
|
file = file,
|
|
|
|
line = 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(), line)).and_modify(|v| *v += 1).or_insert(0)
|
|
|
|
},
|
|
|
|
);
|
2021-12-03 21:54:38 -06:00
|
|
|
let outdir = if let Some(mut path) = rustdoc_options.persist_doctests.clone() {
|
2020-12-06 06:57:37 -06:00
|
|
|
path.push(&test_id);
|
2020-03-01 03:34:08 -06:00
|
|
|
|
|
|
|
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"),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2019-07-23 13:06:00 -05:00
|
|
|
debug!("creating test {}: {}", name, test);
|
2021-07-13 20:40:09 -05:00
|
|
|
self.tests.push(test::TestDescAndFn {
|
|
|
|
desc: test::TestDesc {
|
|
|
|
name: test::DynTestName(name),
|
2019-04-26 15:52:56 -05:00
|
|
|
ignore: match config.ignore {
|
|
|
|
Ignore::All => true,
|
|
|
|
Ignore::None => false,
|
2019-12-22 16:42:04 -06:00
|
|
|
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
|
2019-04-26 15:52:56 -05:00
|
|
|
},
|
2022-01-10 00:01:12 -06:00
|
|
|
#[cfg(not(bootstrap))]
|
|
|
|
ignore_message: None,
|
2015-04-06 20:39:39 -05:00
|
|
|
// compiler failures are test failures
|
2021-07-13 20:40:09 -05:00
|
|
|
should_panic: test::ShouldPanic::No,
|
2021-05-03 07:55:22 -05:00
|
|
|
compile_fail: config.compile_fail,
|
|
|
|
no_run,
|
2021-07-13 20:40:09 -05:00
|
|
|
test_type: test::TestType::DocTest,
|
2013-12-22 13:23:04 -06:00
|
|
|
},
|
2021-09-20 18:49:47 -05:00
|
|
|
testfn: test::DynTestFn(box move || {
|
2020-08-01 05:57:35 -05:00
|
|
|
let report_unused_externs = |uext| {
|
|
|
|
unused_externs.lock().unwrap().push(uext);
|
|
|
|
};
|
2019-05-04 14:37:12 -05:00
|
|
|
let res = run_test(
|
2018-12-08 13:30:23 -06:00
|
|
|
&test,
|
2021-06-20 19:24:59 -05:00
|
|
|
&crate_name,
|
2018-12-08 13:30:23 -06:00
|
|
|
line,
|
2021-12-03 21:54:38 -06:00
|
|
|
rustdoc_options,
|
2021-12-03 21:43:50 -06:00
|
|
|
config,
|
2021-04-06 10:51:25 -05:00
|
|
|
no_run,
|
2019-04-26 15:52:56 -05:00
|
|
|
runtool,
|
|
|
|
runtool_args,
|
|
|
|
target,
|
2018-12-08 13:30:23 -06:00
|
|
|
&opts,
|
|
|
|
edition,
|
2020-03-01 03:34:08 -06:00
|
|
|
outdir,
|
|
|
|
path,
|
2020-12-06 06:57:37 -06:00
|
|
|
&test_id,
|
2020-08-01 05:57:35 -05:00
|
|
|
report_unused_externs,
|
2019-05-04 14:37:12 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
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) => {
|
|
|
|
let reason = if let Some(code) = out.status.code() {
|
|
|
|
format!("exit code {}", code)
|
|
|
|
} else {
|
|
|
|
String::from("terminated by signal")
|
|
|
|
};
|
|
|
|
|
|
|
|
eprintln!("Test executable failed ({}).", reason);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:49:47 -05:00
|
|
|
panic::resume_unwind(box ());
|
2019-05-04 14:37:12 -05:00
|
|
|
}
|
2021-09-20 18:49:47 -05:00
|
|
|
}),
|
2013-12-22 13:23:04 -06:00
|
|
|
});
|
|
|
|
}
|
2014-03-06 21:31:41 -06:00
|
|
|
|
2018-09-17 17:25:50 -05:00
|
|
|
fn get_line(&self) -> usize {
|
2018-08-18 05:14:14 -05:00
|
|
|
if let Some(ref source_map) = self.source_map {
|
2017-07-31 15:04:34 -05:00
|
|
|
let line = self.position.lo().to_usize();
|
2018-08-18 05:14:14 -05:00
|
|
|
let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
|
2017-01-23 15:46:18 -06:00
|
|
|
if line > 0 { line - 1 } else { line }
|
2017-01-17 16:54:51 -06:00
|
|
|
} else {
|
2017-02-06 15:11:03 -06:00
|
|
|
0
|
2017-01-17 16:54:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-17 17:25:50 -05:00
|
|
|
fn register_header(&mut self, name: &str, level: u32) {
|
2017-09-16 02:59:15 -05:00
|
|
|
if self.use_headers {
|
2018-11-26 20:59:49 -06:00
|
|
|
// We use these headings as test names, so it's good if
|
2014-03-06 21:31:41 -06:00
|
|
|
// they're valid identifiers.
|
2019-12-22 16:42:04 -06:00
|
|
|
let name = name
|
|
|
|
.chars()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(i, c)| {
|
|
|
|
if (i == 0 && rustc_lexer::is_id_start(c))
|
|
|
|
|| (i != 0 && rustc_lexer::is_id_continue(c))
|
|
|
|
{
|
2014-03-06 21:31:41 -06:00
|
|
|
c
|
|
|
|
} else {
|
|
|
|
'_'
|
|
|
|
}
|
2019-12-22 16:42:04 -06:00
|
|
|
})
|
|
|
|
.collect::<String>();
|
2014-03-06 21:31:41 -06:00
|
|
|
|
2017-09-16 02:59:15 -05:00
|
|
|
// Here we try to efficiently assemble the header titles into the
|
|
|
|
// test name in the form of `h1::h2::h3::h4::h5::h6`.
|
|
|
|
//
|
2018-11-26 20:59:49 -06:00
|
|
|
// Suppose that originally `self.names` contains `[h1, h2, h3]`...
|
2017-09-16 02:59:15 -05:00
|
|
|
let level = level as usize;
|
|
|
|
if level <= self.names.len() {
|
|
|
|
// ... Consider `level == 2`. All headers in the lower levels
|
|
|
|
// are irrelevant in this new level. So we should reset
|
|
|
|
// `self.names` to contain headers until <h2>, and replace that
|
|
|
|
// slot with the new name: `[h1, name]`.
|
|
|
|
self.names.truncate(level);
|
|
|
|
self.names[level - 1] = name;
|
|
|
|
} else {
|
|
|
|
// ... On the other hand, consider `level == 5`. This means we
|
|
|
|
// need to extend `self.names` to contain five headers. We fill
|
|
|
|
// in the missing level (<h4>) with `_`. Thus `self.names` will
|
|
|
|
// become `[h1, h2, h3, "_", name]`.
|
|
|
|
if level - 1 > self.names.len() {
|
|
|
|
self.names.resize(level - 1, "_".to_owned());
|
|
|
|
}
|
|
|
|
self.names.push(name);
|
|
|
|
}
|
2014-03-06 21:31:41 -06:00
|
|
|
}
|
|
|
|
}
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
|
2020-04-21 16:49:06 -05:00
|
|
|
struct HirCollector<'a, 'hir, 'tcx> {
|
2020-03-11 06:49:08 -05:00
|
|
|
sess: &'a Session,
|
2016-11-19 19:11:20 -06:00
|
|
|
collector: &'a mut Collector,
|
2020-02-09 08:32:00 -06:00
|
|
|
map: Map<'hir>,
|
2018-07-22 12:10:19 -05:00
|
|
|
codes: ErrorCodes,
|
2020-04-21 16:49:06 -05:00
|
|
|
tcx: TyCtxt<'tcx>,
|
2016-11-19 19:11:20 -06:00
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2020-04-21 16:49:06 -05:00
|
|
|
impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
|
2019-12-22 16:42:04 -06:00
|
|
|
fn visit_testable<F: FnOnce(&mut Self)>(
|
|
|
|
&mut self,
|
|
|
|
name: String,
|
2020-04-21 16:49:06 -05:00
|
|
|
hir_id: HirId,
|
|
|
|
sp: Span,
|
2019-12-22 16:42:04 -06:00
|
|
|
nested: F,
|
|
|
|
) {
|
2021-04-23 15:15:13 -05:00
|
|
|
let ast_attrs = self.tcx.hir().attrs(hir_id);
|
2021-04-25 11:17:11 -05:00
|
|
|
let mut attrs = Attributes::from_ast(ast_attrs, None);
|
2021-04-23 15:15:13 -05:00
|
|
|
|
2020-11-04 14:59:35 -06:00
|
|
|
if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) {
|
2021-10-01 10:12:39 -05:00
|
|
|
if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) {
|
2017-08-05 01:38:52 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-19 19:11:20 -06:00
|
|
|
let has_name = !name.is_empty();
|
|
|
|
if has_name {
|
|
|
|
self.collector.names.push(name);
|
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2016-11-19 19:11:20 -06:00
|
|
|
attrs.unindent_doc_comments();
|
2018-11-26 20:59:49 -06:00
|
|
|
// The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
|
|
|
|
// anything else, this will combine them for us.
|
2017-09-21 22:37:00 -05:00
|
|
|
if let Some(doc) = attrs.collapsed_doc_value() {
|
2020-06-28 12:06:33 -05:00
|
|
|
// Use the outermost invocation, so that doctest names come from where the docs were written.
|
2021-04-23 15:15:13 -05:00
|
|
|
let span = ast_attrs
|
|
|
|
.span()
|
2020-06-28 12:06:33 -05:00
|
|
|
.map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
|
|
|
|
.unwrap_or(DUMMY_SP);
|
|
|
|
self.collector.set_position(span);
|
2019-12-22 16:42:04 -06:00
|
|
|
markdown::find_testable_code(
|
|
|
|
&doc,
|
|
|
|
self.collector,
|
|
|
|
self.codes,
|
|
|
|
self.collector.enable_per_target_ignores,
|
2020-04-21 16:49:06 -05:00
|
|
|
Some(&crate::html::markdown::ExtraInfo::new(
|
2020-12-31 22:25:30 -06:00
|
|
|
self.tcx,
|
2020-04-21 16:49:06 -05:00
|
|
|
hir_id,
|
|
|
|
span_of_attrs(&attrs).unwrap_or(sp),
|
|
|
|
)),
|
2019-12-22 16:42:04 -06:00
|
|
|
);
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2016-11-19 19:11:20 -06:00
|
|
|
nested(self);
|
|
|
|
|
|
|
|
if has_name {
|
|
|
|
self.collector.names.pop();
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
2016-11-19 19:11:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 16:49:06 -05:00
|
|
|
impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> {
|
2021-11-03 18:03:12 -05:00
|
|
|
type NestedFilter = nested_filter::All;
|
2020-01-07 10:25:33 -06:00
|
|
|
|
2021-11-03 18:03:12 -05:00
|
|
|
fn nested_visit_map(&mut self) -> Self::Map {
|
|
|
|
self.map
|
2016-11-25 15:10:23 -06:00
|
|
|
}
|
|
|
|
|
2020-06-12 17:44:56 -05:00
|
|
|
fn visit_item(&mut self, item: &'hir hir::Item<'_>) {
|
2021-08-05 18:58:46 -05:00
|
|
|
let name = match &item.kind {
|
2021-12-11 05:52:23 -06:00
|
|
|
hir::ItemKind::Macro(ref macro_def, _) => {
|
2021-08-05 18:58:46 -05:00
|
|
|
// FIXME(#88038): Non exported macros have historically not been tested,
|
|
|
|
// but we really ought to start testing them.
|
|
|
|
let def_id = item.def_id.to_def_id();
|
|
|
|
if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) {
|
|
|
|
intravisit::walk_item(self, item);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
item.ident.to_string()
|
|
|
|
}
|
|
|
|
hir::ItemKind::Impl(impl_) => {
|
|
|
|
rustc_hir_pretty::id_to_string(&self.map, impl_.self_ty.hir_id)
|
|
|
|
}
|
|
|
|
_ => item.ident.to_string(),
|
2016-11-19 19:11:20 -06:00
|
|
|
};
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(name, item.hir_id(), item.span, |this| {
|
2016-11-19 19:11:20 -06:00
|
|
|
intravisit::walk_item(this, item);
|
|
|
|
});
|
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2020-06-12 17:44:56 -05:00
|
|
|
fn visit_trait_item(&mut self, item: &'hir hir::TraitItem<'_>) {
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
|
|
|
|
intravisit::walk_trait_item(this, item);
|
|
|
|
});
|
2016-11-19 19:11:20 -06:00
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2020-06-12 17:44:56 -05:00
|
|
|
fn visit_impl_item(&mut self, item: &'hir hir::ImplItem<'_>) {
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
|
|
|
|
intravisit::walk_impl_item(this, item);
|
|
|
|
});
|
2016-11-19 19:11:20 -06:00
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2020-06-12 17:44:56 -05:00
|
|
|
fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem<'_>) {
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
|
|
|
|
intravisit::walk_foreign_item(this, item);
|
|
|
|
});
|
2016-11-19 19:11:20 -06:00
|
|
|
}
|
2015-11-04 17:41:33 -06:00
|
|
|
|
2019-12-22 16:42:04 -06:00
|
|
|
fn visit_variant(
|
|
|
|
&mut self,
|
2020-06-12 17:44:56 -05:00
|
|
|
v: &'hir hir::Variant<'_>,
|
|
|
|
g: &'hir hir::Generics<'_>,
|
2019-12-22 16:42:04 -06:00
|
|
|
item_id: hir::HirId,
|
|
|
|
) {
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(v.ident.to_string(), v.id, v.span, |this| {
|
2016-11-19 19:11:20 -06:00
|
|
|
intravisit::walk_variant(this, v, g, item_id);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-03-15 16:36:07 -05:00
|
|
|
fn visit_field_def(&mut self, f: &'hir hir::FieldDef<'_>) {
|
2021-02-18 12:35:12 -06:00
|
|
|
self.visit_testable(f.ident.to_string(), f.hir_id, f.span, |this| {
|
2021-03-15 16:36:07 -05:00
|
|
|
intravisit::walk_field_def(this, f);
|
2016-11-19 19:11:20 -06:00
|
|
|
});
|
2013-12-22 13:23:04 -06:00
|
|
|
}
|
|
|
|
}
|
2018-02-09 10:40:27 -06:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2019-06-05 13:33:39 -05:00
|
|
|
mod tests;
|