rust/src/librustdoc/test.rs

675 lines
24 KiB
Rust
Raw Normal View History

// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
2017-04-20 00:31:34 +02:00
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
2015-03-11 23:24:14 +01:00
use std::io::prelude::*;
use std::io;
use std::path::{Path, PathBuf};
std: Stabilize APIs for the 1.9 release This commit applies all stabilizations, renamings, and deprecations that the library team has decided on for the upcoming 1.9 release. All tracking issues have gone through a cycle-long "final comment period" and the specific APIs stabilized/deprecated are: Stable * `std::panic` * `std::panic::catch_unwind` (renamed from `recover`) * `std::panic::resume_unwind` (renamed from `propagate`) * `std::panic::AssertUnwindSafe` (renamed from `AssertRecoverSafe`) * `std::panic::UnwindSafe` (renamed from `RecoverSafe`) * `str::is_char_boundary` * `<*const T>::as_ref` * `<*mut T>::as_ref` * `<*mut T>::as_mut` * `AsciiExt::make_ascii_uppercase` * `AsciiExt::make_ascii_lowercase` * `char::decode_utf16` * `char::DecodeUtf16` * `char::DecodeUtf16Error` * `char::DecodeUtf16Error::unpaired_surrogate` * `BTreeSet::take` * `BTreeSet::replace` * `BTreeSet::get` * `HashSet::take` * `HashSet::replace` * `HashSet::get` * `OsString::with_capacity` * `OsString::clear` * `OsString::capacity` * `OsString::reserve` * `OsString::reserve_exact` * `OsStr::is_empty` * `OsStr::len` * `std::os::unix::thread` * `RawPthread` * `JoinHandleExt` * `JoinHandleExt::as_pthread_t` * `JoinHandleExt::into_pthread_t` * `HashSet::hasher` * `HashMap::hasher` * `CommandExt::exec` * `File::try_clone` * `SocketAddr::set_ip` * `SocketAddr::set_port` * `SocketAddrV4::set_ip` * `SocketAddrV4::set_port` * `SocketAddrV6::set_ip` * `SocketAddrV6::set_port` * `SocketAddrV6::set_flowinfo` * `SocketAddrV6::set_scope_id` * `<[T]>::copy_from_slice` * `ptr::read_volatile` * `ptr::write_volatile` * The `#[deprecated]` attribute * `OpenOptions::create_new` Deprecated * `std::raw::Slice` - use raw parts of `slice` module instead * `std::raw::Repr` - use raw parts of `slice` module instead * `str::char_range_at` - use slicing plus `chars()` plus `len_utf8` * `str::char_range_at_reverse` - use slicing plus `chars().rev()` plus `len_utf8` * `str::char_at` - use slicing plus `chars()` * `str::char_at_reverse` - use slicing plus `chars().rev()` * `str::slice_shift_char` - use `chars()` plus `Chars::as_str` * `CommandExt::session_leader` - use `before_exec` instead. Closes #27719 cc #27751 (deprecating the `Slice` bits) Closes #27754 Closes #27780 Closes #27809 Closes #27811 Closes #27830 Closes #28050 Closes #29453 Closes #29791 Closes #29935 Closes #30014 Closes #30752 Closes #31262 cc #31398 (still need to deal with `before_exec`) Closes #31405 Closes #31572 Closes #31755 Closes #31756
2016-04-07 19:42:53 +02:00
use std::panic::{self, AssertUnwindSafe};
use std::process::Command;
2015-11-22 20:02:04 +01:00
use std::rc::Rc;
use std::str;
2015-03-11 23:24:14 +01:00
use std::sync::{Arc, Mutex};
2014-02-14 02:49:11 +01:00
use testing;
use rustc_lint;
use rustc::dep_graph::DepGraph;
use rustc::hir;
use rustc::hir::intravisit;
2015-01-04 04:42:21 +01:00
use rustc::session::{self, config};
use rustc::session::config::{OutputType, OutputTypes, Externs};
use rustc::session::search_paths::{SearchPaths, PathKind};
use rustc_back::dynamic_lib::DynamicLibrary;
std: Stabilize the `fs` module This commit performs a stabilization pass over the `std::fs` module now that it's had some time to bake. The change was largely just adding `#[stable]` tags, but there are a few APIs that remain `#[unstable]`. The following apis are now marked `#[stable]`: * `std::fs` (the name) * `File` * `Metadata` * `ReadDir` * `DirEntry` * `OpenOptions` * `Permissions` * `File::{open, create}` * `File::{sync_all, sync_data}` * `File::set_len` * `File::metadata` * Trait implementations for `File` and `&File` * `OpenOptions::new` * `OpenOptions::{read, write, append, truncate, create}` * `OpenOptions::open` - this function was modified, however, to not attempt to reject cross-platform openings of directories. This means that some platforms will succeed in opening a directory and others will fail. * `Metadata::{is_dir, is_file, len, permissions}` * `Permissions::{readonly, set_readonly}` * `Iterator for ReadDir` * `DirEntry::path` * `remove_file` - like with `OpenOptions::open`, the extra windows code to remove a readonly file has been removed. This means that removing a readonly file will succeed on some platforms but fail on others. * `metadata` * `rename` * `copy` * `hard_link` * `soft_link` * `read_link` * `create_dir` * `create_dir_all` * `remove_dir` * `remove_dir_all` * `read_dir` The following apis remain `#[unstable]`. * `WalkDir` and `walk` - there are many methods by which a directory walk can be constructed, and it's unclear whether the current semantics are the right ones. For example symlinks are not handled super well currently. This is now behind a new `fs_walk` feature. * `File::path` - this is an extra abstraction which the standard library provides on top of what the system offers and it's unclear whether we should be doing so. This is now behind a new `file_path` feature. * `Metadata::{accessed, modified}` - we do not currently have a good abstraction for a moment in time which is what these APIs should likely be returning, so these remain `#[unstable]` for now. These are now behind a new `fs_time` feature * `set_file_times` - like with `Metadata::accessed`, we do not currently have the appropriate abstraction for the arguments here so this API remains unstable behind the `fs_time` feature gate. * `PathExt` - the precise set of methods on this trait may change over time and some methods may be removed. This API remains unstable behind the `path_ext` feature gate. * `set_permissions` - we may wish to expose a more granular ability to set the permissions on a file instead of just a blanket "set all permissions" method. This function remains behind the `fs` feature. The following apis are now `#[deprecated]` * The `TempDir` type is now entirely deprecated and is [located on crates.io][tempdir] as the `tempdir` crate with [its source][github] at rust-lang/tempdir. [tempdir]: https://crates.io/crates/tempdir [github]: https://github.com/rust-lang/tempdir The stability of some of these APIs has been questioned over the past few weeks in using these APIs, and it is intentional that the majority of APIs here are marked `#[stable]`. The `std::fs` module has a lot of room to grow and the material is [being tracked in a RFC issue][rfc-issue]. [rfc-issue]: https://github.com/rust-lang/rfcs/issues/939 [breaking-change]
2015-03-04 04:18:29 +01:00
use rustc_back::tempdir::TempDir;
use rustc_driver::{self, driver, Compilation};
use rustc_driver::driver::phase_2_configure_and_expand;
2015-11-25 00:23:22 +01:00
use rustc_metadata::cstore::CStore;
use rustc_resolve::MakeGlobMap;
use rustc_trans::back::link;
use syntax::ast;
use syntax::codemap::CodeMap;
use syntax::feature_gate::UnstableFeatures;
use syntax_pos::{BytePos, DUMMY_SP, Pos, Span};
use errors;
use errors::emitter::ColorConfig;
use clean::Attributes;
2017-04-21 00:32:23 +02:00
use html::markdown::{self, RenderType};
#[derive(Clone, Default)]
pub struct TestOptions {
pub no_crate_inject: bool,
pub attrs: Vec<String>,
}
pub fn run(input: &str,
cfgs: Vec<String>,
libs: SearchPaths,
externs: Externs,
mut test_args: Vec<String>,
crate_name: Option<String>,
2017-04-21 00:32:23 +02:00
maybe_sysroot: Option<PathBuf>,
render_type: RenderType,
display_warnings: bool)
-> isize {
let input_path = PathBuf::from(input);
let input = config::Input::File(input_path.clone());
let sessopts = config::Options {
maybe_sysroot: maybe_sysroot.clone().or_else(
|| Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
search_paths: libs.clone(),
crate_types: vec![config::CrateTypeDylib],
externs: externs.clone(),
unstable_features: UnstableFeatures::from_environment(),
actually_rustdoc: true,
..config::basic_options().clone()
};
let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
let handler =
errors::Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
let dep_graph = DepGraph::new(false);
let _ignore = dep_graph.in_ignore();
let cstore = Rc::new(CStore::new(&dep_graph));
let mut sess = session::build_session_(
sessopts, &dep_graph, Some(input_path.clone()), handler, codemap.clone(), cstore.clone(),
);
rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
sess.parse_sess.config =
config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
let krate = panictry!(driver::phase_1_parse_input(&sess, &input));
2017-01-12 20:33:01 +01:00
let driver::ExpansionResult { defs, mut hir_forest, .. } = {
phase_2_configure_and_expand(
&sess, &cstore, krate, None, "rustdoc-test", None, MakeGlobMap::No, |_| Ok(())
).expect("phase_2_configure_and_expand aborted in rustdoc!")
};
let crate_name = crate_name.unwrap_or_else(|| {
link::find_crate_name(None, &hir_forest.krate().attrs, &input)
});
let opts = scrape_test_config(hir_forest.krate());
let mut collector = Collector::new(crate_name,
cfgs,
libs,
externs,
false,
opts,
maybe_sysroot,
Some(codemap),
2017-04-21 00:32:23 +02:00
None,
render_type);
{
let dep_graph = DepGraph::new(false);
let _ignore = dep_graph.in_ignore();
let map = hir::map::map_crate(&mut hir_forest, defs);
let krate = map.krate();
let mut hir_collector = HirCollector {
collector: &mut collector,
map: &map
};
hir_collector.visit_testable("".to_string(), &krate.attrs, |this| {
intravisit::walk_crate(this, krate);
});
}
test_args.insert(0, "rustdoctest".to_string());
if display_warnings {
test_args.insert(1, "--display-stdout".to_string());
}
testing::test_main(&test_args,
2014-09-15 05:27:36 +02:00
collector.tests.into_iter().collect());
0
}
// Look for #![doc(test(no_crate_inject))], used by crates in the std facade
2016-03-29 07:50:44 +02:00
fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions {
use syntax::print::pprust;
let mut opts = TestOptions {
2015-04-08 05:11:59 +02:00
no_crate_inject: false,
attrs: Vec::new(),
};
let test_attrs: Vec<_> = krate.attrs.iter()
.filter(|a| a.check_name("doc"))
.flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
.filter(|a| a.check_name("test"))
.collect();
let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
for attr in attrs {
if attr.check_name("no_crate_inject") {
opts.no_crate_inject = true;
}
if attr.check_name("attr") {
if let Some(l) = attr.meta_item_list() {
for item in l {
opts.attrs.push(pprust::meta_list_item_to_string(item));
}
}
}
}
2016-10-01 22:47:43 +02:00
opts
}
fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
externs: Externs,
should_panic: bool, no_run: bool, as_test_harness: bool,
compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
2017-01-12 20:33:01 +01:00
maybe_sysroot: Option<PathBuf>) {
// the test harness wants its own `main` & top level functions, so
// never wrap the test in `fn main() { ... }`
2016-11-15 22:29:46 +01:00
let test = maketest(test, Some(cratename), as_test_harness, opts);
let input = config::Input::Str {
name: driver::anon_src(),
2016-11-15 22:29:46 +01:00
input: test.to_owned(),
};
let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
let sessopts = config::Options {
maybe_sysroot: maybe_sysroot.or_else(
|| Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
search_paths: libs,
crate_types: vec![config::CrateTypeExecutable],
output_types: outputs,
externs: externs,
cg: config::CodegenOptions {
prefer_dynamic: true,
.. config::basic_codegen_options()
},
test: as_test_harness,
unstable_features: UnstableFeatures::from_environment(),
..config::basic_options().clone()
};
// Shuffle around a few input and output handles here. We're going to pass
// an explicit handle into rustc to collect output messages, but we also
// want to catch the error message that rustc prints when it fails.
//
// We take our thread-local stderr (likely set by the test runner) and replace
2015-03-11 23:24:14 +01:00
// it with a sink that is also passed to rustc itself. When this function
// returns the output of the sink is copied onto the output of our own thread.
//
// The basic idea is to not use a default Handler for rustc, and then also
// not print things by default to the actual stderr.
2015-03-11 23:24:14 +01:00
struct Sink(Arc<Mutex<Vec<u8>>>);
impl Write for Sink {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
Write::write(&mut *self.0.lock().unwrap(), data)
}
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>);
impl Drop for Bomb {
fn drop(&mut self) {
let _ = self.1.write_all(&self.0.lock().unwrap());
}
}
let data = Arc::new(Mutex::new(Vec::new()));
let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
2015-12-15 04:51:13 +01:00
let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
Some(codemap.clone()));
let old = io::set_panic(Some(box Sink(data.clone())));
2016-06-09 23:50:52 +02:00
let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
// Compile the code
let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
let dep_graph = DepGraph::new(false);
let cstore = Rc::new(CStore::new(&dep_graph));
let mut sess = session::build_session_(
sessopts, &dep_graph, None, diagnostic_handler, codemap, cstore.clone(),
);
rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
2016-01-05 23:38:11 +01:00
let outdir = Mutex::new(TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
let mut control = driver::CompileController::basic();
sess.parse_sess.config =
config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
2016-01-05 23:38:11 +01:00
let out = Some(outdir.lock().unwrap().path().to_path_buf());
if no_run {
2015-02-03 01:40:52 +01:00
control.after_analysis.stop = Compilation::Stop;
}
2016-01-05 23:38:11 +01:00
std: Stabilize APIs for the 1.9 release This commit applies all stabilizations, renamings, and deprecations that the library team has decided on for the upcoming 1.9 release. All tracking issues have gone through a cycle-long "final comment period" and the specific APIs stabilized/deprecated are: Stable * `std::panic` * `std::panic::catch_unwind` (renamed from `recover`) * `std::panic::resume_unwind` (renamed from `propagate`) * `std::panic::AssertUnwindSafe` (renamed from `AssertRecoverSafe`) * `std::panic::UnwindSafe` (renamed from `RecoverSafe`) * `str::is_char_boundary` * `<*const T>::as_ref` * `<*mut T>::as_ref` * `<*mut T>::as_mut` * `AsciiExt::make_ascii_uppercase` * `AsciiExt::make_ascii_lowercase` * `char::decode_utf16` * `char::DecodeUtf16` * `char::DecodeUtf16Error` * `char::DecodeUtf16Error::unpaired_surrogate` * `BTreeSet::take` * `BTreeSet::replace` * `BTreeSet::get` * `HashSet::take` * `HashSet::replace` * `HashSet::get` * `OsString::with_capacity` * `OsString::clear` * `OsString::capacity` * `OsString::reserve` * `OsString::reserve_exact` * `OsStr::is_empty` * `OsStr::len` * `std::os::unix::thread` * `RawPthread` * `JoinHandleExt` * `JoinHandleExt::as_pthread_t` * `JoinHandleExt::into_pthread_t` * `HashSet::hasher` * `HashMap::hasher` * `CommandExt::exec` * `File::try_clone` * `SocketAddr::set_ip` * `SocketAddr::set_port` * `SocketAddrV4::set_ip` * `SocketAddrV4::set_port` * `SocketAddrV6::set_ip` * `SocketAddrV6::set_port` * `SocketAddrV6::set_flowinfo` * `SocketAddrV6::set_scope_id` * `<[T]>::copy_from_slice` * `ptr::read_volatile` * `ptr::write_volatile` * The `#[deprecated]` attribute * `OpenOptions::create_new` Deprecated * `std::raw::Slice` - use raw parts of `slice` module instead * `std::raw::Repr` - use raw parts of `slice` module instead * `str::char_range_at` - use slicing plus `chars()` plus `len_utf8` * `str::char_range_at_reverse` - use slicing plus `chars().rev()` plus `len_utf8` * `str::char_at` - use slicing plus `chars()` * `str::char_at_reverse` - use slicing plus `chars().rev()` * `str::slice_shift_char` - use `chars()` plus `Chars::as_str` * `CommandExt::session_leader` - use `before_exec` instead. Closes #27719 cc #27751 (deprecating the `Slice` bits) Closes #27754 Closes #27780 Closes #27809 Closes #27811 Closes #27830 Closes #28050 Closes #29453 Closes #29791 Closes #29935 Closes #30014 Closes #30752 Closes #31262 cc #31398 (still need to deal with `before_exec`) Closes #31405 Closes #31572 Closes #31755 Closes #31756
2016-04-07 19:42:53 +02:00
let res = panic::catch_unwind(AssertUnwindSafe(|| {
driver::compile_input(&sess, &cstore, &input, &out, &None, None, &control)
std: Stabilize APIs for the 1.9 release This commit applies all stabilizations, renamings, and deprecations that the library team has decided on for the upcoming 1.9 release. All tracking issues have gone through a cycle-long "final comment period" and the specific APIs stabilized/deprecated are: Stable * `std::panic` * `std::panic::catch_unwind` (renamed from `recover`) * `std::panic::resume_unwind` (renamed from `propagate`) * `std::panic::AssertUnwindSafe` (renamed from `AssertRecoverSafe`) * `std::panic::UnwindSafe` (renamed from `RecoverSafe`) * `str::is_char_boundary` * `<*const T>::as_ref` * `<*mut T>::as_ref` * `<*mut T>::as_mut` * `AsciiExt::make_ascii_uppercase` * `AsciiExt::make_ascii_lowercase` * `char::decode_utf16` * `char::DecodeUtf16` * `char::DecodeUtf16Error` * `char::DecodeUtf16Error::unpaired_surrogate` * `BTreeSet::take` * `BTreeSet::replace` * `BTreeSet::get` * `HashSet::take` * `HashSet::replace` * `HashSet::get` * `OsString::with_capacity` * `OsString::clear` * `OsString::capacity` * `OsString::reserve` * `OsString::reserve_exact` * `OsStr::is_empty` * `OsStr::len` * `std::os::unix::thread` * `RawPthread` * `JoinHandleExt` * `JoinHandleExt::as_pthread_t` * `JoinHandleExt::into_pthread_t` * `HashSet::hasher` * `HashMap::hasher` * `CommandExt::exec` * `File::try_clone` * `SocketAddr::set_ip` * `SocketAddr::set_port` * `SocketAddrV4::set_ip` * `SocketAddrV4::set_port` * `SocketAddrV6::set_ip` * `SocketAddrV6::set_port` * `SocketAddrV6::set_flowinfo` * `SocketAddrV6::set_scope_id` * `<[T]>::copy_from_slice` * `ptr::read_volatile` * `ptr::write_volatile` * The `#[deprecated]` attribute * `OpenOptions::create_new` Deprecated * `std::raw::Slice` - use raw parts of `slice` module instead * `std::raw::Repr` - use raw parts of `slice` module instead * `str::char_range_at` - use slicing plus `chars()` plus `len_utf8` * `str::char_range_at_reverse` - use slicing plus `chars().rev()` plus `len_utf8` * `str::char_at` - use slicing plus `chars()` * `str::char_at_reverse` - use slicing plus `chars().rev()` * `str::slice_shift_char` - use `chars()` plus `Chars::as_str` * `CommandExt::session_leader` - use `before_exec` instead. Closes #27719 cc #27751 (deprecating the `Slice` bits) Closes #27754 Closes #27780 Closes #27809 Closes #27811 Closes #27830 Closes #28050 Closes #29453 Closes #29791 Closes #29935 Closes #30014 Closes #30752 Closes #31262 cc #31398 (still need to deal with `before_exec`) Closes #31405 Closes #31572 Closes #31755 Closes #31756
2016-04-07 19:42:53 +02:00
}));
2016-01-05 23:38:11 +01:00
std: Stabilize APIs for the 1.9 release This commit applies all stabilizations, renamings, and deprecations that the library team has decided on for the upcoming 1.9 release. All tracking issues have gone through a cycle-long "final comment period" and the specific APIs stabilized/deprecated are: Stable * `std::panic` * `std::panic::catch_unwind` (renamed from `recover`) * `std::panic::resume_unwind` (renamed from `propagate`) * `std::panic::AssertUnwindSafe` (renamed from `AssertRecoverSafe`) * `std::panic::UnwindSafe` (renamed from `RecoverSafe`) * `str::is_char_boundary` * `<*const T>::as_ref` * `<*mut T>::as_ref` * `<*mut T>::as_mut` * `AsciiExt::make_ascii_uppercase` * `AsciiExt::make_ascii_lowercase` * `char::decode_utf16` * `char::DecodeUtf16` * `char::DecodeUtf16Error` * `char::DecodeUtf16Error::unpaired_surrogate` * `BTreeSet::take` * `BTreeSet::replace` * `BTreeSet::get` * `HashSet::take` * `HashSet::replace` * `HashSet::get` * `OsString::with_capacity` * `OsString::clear` * `OsString::capacity` * `OsString::reserve` * `OsString::reserve_exact` * `OsStr::is_empty` * `OsStr::len` * `std::os::unix::thread` * `RawPthread` * `JoinHandleExt` * `JoinHandleExt::as_pthread_t` * `JoinHandleExt::into_pthread_t` * `HashSet::hasher` * `HashMap::hasher` * `CommandExt::exec` * `File::try_clone` * `SocketAddr::set_ip` * `SocketAddr::set_port` * `SocketAddrV4::set_ip` * `SocketAddrV4::set_port` * `SocketAddrV6::set_ip` * `SocketAddrV6::set_port` * `SocketAddrV6::set_flowinfo` * `SocketAddrV6::set_scope_id` * `<[T]>::copy_from_slice` * `ptr::read_volatile` * `ptr::write_volatile` * The `#[deprecated]` attribute * `OpenOptions::create_new` Deprecated * `std::raw::Slice` - use raw parts of `slice` module instead * `std::raw::Repr` - use raw parts of `slice` module instead * `str::char_range_at` - use slicing plus `chars()` plus `len_utf8` * `str::char_range_at_reverse` - use slicing plus `chars().rev()` plus `len_utf8` * `str::char_at` - use slicing plus `chars()` * `str::char_at_reverse` - use slicing plus `chars().rev()` * `str::slice_shift_char` - use `chars()` plus `Chars::as_str` * `CommandExt::session_leader` - use `before_exec` instead. Closes #27719 cc #27751 (deprecating the `Slice` bits) Closes #27754 Closes #27780 Closes #27809 Closes #27811 Closes #27830 Closes #28050 Closes #29453 Closes #29791 Closes #29935 Closes #30014 Closes #30752 Closes #31262 cc #31398 (still need to deal with `before_exec`) Closes #31405 Closes #31572 Closes #31755 Closes #31756
2016-04-07 19:42:53 +02:00
match res {
2016-01-05 23:38:11 +01:00
Ok(r) => {
2016-02-11 11:09:01 +01:00
match r {
Err(count) => {
2016-10-02 03:18:33 +02:00
if count > 0 && !compile_fail {
sess.fatal("aborting due to previous error(s)")
2016-10-02 03:18:33 +02:00
} else if count == 0 && compile_fail {
2016-11-19 17:51:25 +01:00
panic!("test compiled while it wasn't supposed to")
}
2016-06-09 23:50:52 +02:00
if count > 0 && error_codes.len() > 0 {
let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
2016-06-10 00:34:46 +02:00
error_codes.retain(|err| !out.contains(err));
2016-06-09 23:50:52 +02:00
}
2016-01-05 23:38:11 +01:00
}
Ok(()) if compile_fail => {
2016-11-19 17:51:25 +01:00
panic!("test compiled while it wasn't supposed to")
}
2016-01-05 23:38:11 +01:00
_ => {}
}
}
2016-06-09 23:50:52 +02:00
Err(_) => {
2016-10-02 03:18:33 +02:00
if !compile_fail {
2016-11-19 17:51:25 +01:00
panic!("couldn't compile the test");
2016-06-09 23:50:52 +02:00
}
if error_codes.len() > 0 {
let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
error_codes.retain(|err| !out.contains(err));
}
}
}
if error_codes.len() > 0 {
2016-11-19 17:51:25 +01:00
panic!("Some expected error codes were not found: {:?}", error_codes);
}
if no_run { return }
// Run the code!
//
// We're careful to prepend the *target* dylib search path to the child's
// environment to ensure that the target loads the right libraries at
// runtime. It would be a sad day if the *host* libraries were loaded as a
// mistake.
2016-01-05 23:38:11 +01:00
let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
let var = DynamicLibrary::envvar();
let newpath = {
let path = env::var_os(var).unwrap_or(OsString::new());
let mut path = env::split_paths(&path).collect::<Vec<_>>();
path.insert(0, libdir.clone());
env::join_paths(path).unwrap()
};
cmd.env(var, &newpath);
match cmd.output() {
2016-11-19 17:51:25 +01:00
Err(e) => panic!("couldn't run the test: {}{}", e,
if e.kind() == io::ErrorKind::PermissionDenied {
" - maybe your tempdir is mounted with noexec?"
2016-11-19 17:51:25 +01:00
} else { "" }),
2014-01-30 20:30:21 +01:00
Ok(out) => {
if should_panic && out.status.success() {
2016-11-19 17:51:25 +01:00
panic!("test executable succeeded when it should have failed");
} else if !should_panic && !out.status.success() {
2016-11-19 17:51:25 +01:00
panic!("test executable failed:\n{}\n{}\n",
str::from_utf8(&out.stdout).unwrap_or(""),
2016-11-19 17:51:25 +01:00
str::from_utf8(&out.stderr).unwrap_or(""));
}
}
}
}
pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
opts: &TestOptions) -> String {
let (crate_attrs, everything_else) = partition_source(s);
let mut prog = String::new();
// First push any outer attributes from the example, assuming they
// are intended to be crate attributes.
prog.push_str(&crate_attrs);
// Next, any attributes for other aspects such as lints.
for attr in &opts.attrs {
prog.push_str(&format!("#![{}]\n", attr));
}
// Don't inject `extern crate std` because it's already injected by the
// compiler.
if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
2016-02-28 12:11:13 +01:00
if let Some(cratename) = cratename {
if s.contains(cratename) {
prog.push_str(&format!("extern crate {};\n", cratename));
}
}
}
if dont_insert_main || s.contains("fn main") {
prog.push_str(&everything_else);
} else {
prog.push_str("fn main() {\n");
prog.push_str(&everything_else);
prog = prog.trim().into();
prog.push_str("\n}");
}
info!("final test program: {}", prog);
2016-10-01 22:47:43 +02:00
prog
}
// FIXME(aburka): use a real parser to deal with multiline attributes
fn partition_source(s: &str) -> (String, String) {
use std_unicode::str::UnicodeStr;
let mut after_header = false;
let mut before = String::new();
let mut after = String::new();
for line in s.lines() {
let trimline = line.trim();
let header = trimline.is_whitespace() ||
trimline.starts_with("#![");
if !header || after_header {
after_header = true;
after.push_str(line);
after.push_str("\n");
} else {
before.push_str(line);
before.push_str("\n");
}
}
2016-10-01 22:47:43 +02:00
(before, after)
}
pub struct Collector {
pub tests: Vec<testing::TestDescAndFn>,
2017-04-14 01:23:14 +02:00
// to be removed when hoedown will be definitely gone
2017-04-20 00:31:34 +02:00
pub old_tests: HashMap<String, Vec<String>>,
names: Vec<String>,
cfgs: Vec<String>,
libs: SearchPaths,
externs: Externs,
cnt: usize,
use_headers: bool,
current_header: Option<String>,
cratename: String,
opts: TestOptions,
maybe_sysroot: Option<PathBuf>,
position: Span,
codemap: Option<Rc<CodeMap>>,
filename: Option<String>,
2017-04-21 00:32:23 +02:00
// to be removed when hoedown will be removed as well
pub render_type: RenderType,
}
impl Collector {
pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
use_headers: bool, opts: TestOptions, maybe_sysroot: Option<PathBuf>,
2017-04-21 00:32:23 +02:00
codemap: Option<Rc<CodeMap>>, filename: Option<String>,
render_type: RenderType) -> Collector {
Collector {
tests: Vec::new(),
2017-04-20 00:31:34 +02:00
old_tests: HashMap::new(),
names: Vec::new(),
cfgs: cfgs,
libs: libs,
externs: externs,
cnt: 0,
use_headers: use_headers,
current_header: None,
cratename: cratename,
opts: opts,
maybe_sysroot: maybe_sysroot,
position: DUMMY_SP,
codemap: codemap,
filename: filename,
2017-04-21 00:32:23 +02:00
render_type: render_type,
}
}
2017-04-14 01:23:14 +02:00
fn generate_name(&self, line: usize, filename: &str) -> String {
if self.use_headers {
if let Some(ref header) = self.current_header {
format!("{} - {} (line {})", filename, header, line)
} else {
format!("{} - (line {})", filename, line)
}
} else {
format!("{} - {} (line {})", filename, self.names.join("::"), line)
2017-04-14 01:23:14 +02:00
}
}
2017-04-20 00:31:34 +02:00
// to be removed once hoedown is gone
fn generate_name_beginning(&self, filename: &str) -> String {
if self.use_headers {
if let Some(ref header) = self.current_header {
format!("{} - {} (line", filename, header)
} else {
format!("{} - (line", filename)
}
} else {
format!("{} - {} (line", filename, self.names.join("::"))
}
}
pub fn add_old_test(&mut self, test: String, filename: String) {
let name_beg = self.generate_name_beginning(&filename);
let entry = self.old_tests.entry(name_beg)
.or_insert(Vec::new());
entry.push(test.trim().to_owned());
2017-04-14 01:23:14 +02:00
}
pub fn add_test(&mut self, test: String,
should_panic: bool, no_run: bool, should_ignore: bool,
as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>,
line: usize, filename: String) {
let name = self.generate_name(line, &filename);
2017-04-22 14:56:36 +02:00
// to be removed when hoedown is removed
2017-04-21 00:32:23 +02:00
if self.render_type == RenderType::Pulldown {
let name_beg = self.generate_name_beginning(&filename);
let mut found = false;
let test = test.trim().to_owned();
if let Some(entry) = self.old_tests.get_mut(&name_beg) {
found = entry.remove_item(&test).is_some();
}
if !found {
let _ = writeln!(&mut io::stderr(),
2017-04-22 14:56:36 +02:00
"WARNING: {} Code block is not currently run as a test, but will \
in future versions of rustdoc. Please ensure this code block is \
a runnable test, or use the `ignore` directive.",
2017-04-21 00:32:23 +02:00
name);
return
}
2017-04-14 01:23:14 +02:00
}
let cfgs = self.cfgs.clone();
2014-03-09 13:24:58 +01:00
let libs = self.libs.clone();
let externs = self.externs.clone();
2014-05-25 12:10:11 +02:00
let cratename = self.cratename.to_string();
let opts = self.opts.clone();
let maybe_sysroot = self.maybe_sysroot.clone();
debug!("Creating test {}: {}", name, test);
2014-02-14 02:49:11 +01:00
self.tests.push(testing::TestDescAndFn {
desc: testing::TestDesc {
name: testing::DynTestName(name),
ignore: should_ignore,
// compiler failures are test failures
should_panic: testing::ShouldPanic::No,
},
testfn: testing::DynTestFn(box move |()| {
let panic = io::set_panic(None);
let print = io::set_print(None);
match {
rustc_driver::in_rustc_thread(move || {
io::set_panic(panic);
io::set_print(print);
runtest(&test,
&cratename,
cfgs,
libs,
externs,
should_panic,
no_run,
as_test_harness,
compile_fail,
error_codes,
&opts,
2017-01-12 20:33:01 +01:00
maybe_sysroot)
})
} {
Ok(()) => (),
Err(err) => panic::resume_unwind(err),
}
}),
});
}
pub fn get_line(&self) -> usize {
if let Some(ref codemap) = self.codemap {
let line = self.position.lo.to_usize();
let line = codemap.lookup_char_pos(BytePos(line as u32)).line;
2017-01-23 22:46:18 +01:00
if line > 0 { line - 1 } else { line }
} else {
0
}
}
pub fn set_position(&mut self, position: Span) {
self.position = position;
}
pub fn get_filename(&self) -> String {
if let Some(ref codemap) = self.codemap {
let filename = codemap.span_to_filename(self.position);
if let Ok(cur_dir) = env::current_dir() {
if let Ok(path) = Path::new(&filename).strip_prefix(&cur_dir) {
if let Some(path) = path.to_str() {
return path.to_owned();
}
}
}
filename
} else if let Some(ref filename) = self.filename {
filename.clone()
} else {
"<input>".to_owned()
}
}
pub fn register_header(&mut self, name: &str, level: u32) {
if self.use_headers && level == 1 {
// we use these headings as test names, so it's good if
// they're valid identifiers.
let name = name.chars().enumerate().map(|(i, c)| {
if (i == 0 && c.is_xid_start()) ||
(i != 0 && c.is_xid_continue()) {
c
} else {
'_'
}
}).collect::<String>();
// new header => reset count.
self.cnt = 0;
self.current_header = Some(name);
}
}
}
struct HirCollector<'a, 'hir: 'a> {
collector: &'a mut Collector,
map: &'a hir::map::Map<'hir>
}
impl<'a, 'hir> HirCollector<'a, 'hir> {
fn visit_testable<F: FnOnce(&mut Self)>(&mut self,
name: String,
attrs: &[ast::Attribute],
nested: F) {
let has_name = !name.is_empty();
if has_name {
self.collector.names.push(name);
}
let mut attrs = Attributes::from_ast(attrs);
attrs.collapse_doc_comments();
attrs.unindent_doc_comments();
if let Some(doc) = attrs.doc_value() {
self.collector.cnt = 0;
2017-04-21 00:32:23 +02:00
if self.collector.render_type == RenderType::Pulldown {
markdown::old_find_testable_code(doc, self.collector,
attrs.span.unwrap_or(DUMMY_SP));
markdown::find_testable_code(doc, self.collector,
2017-04-14 01:23:14 +02:00
attrs.span.unwrap_or(DUMMY_SP));
2017-04-21 00:32:23 +02:00
} else {
markdown::old_find_testable_code(doc, self.collector,
attrs.span.unwrap_or(DUMMY_SP));
}
}
nested(self);
if has_name {
self.collector.names.pop();
}
}
}
impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> {
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'hir> {
intravisit::NestedVisitorMap::All(&self.map)
2016-11-25 22:10:23 +01:00
}
fn visit_item(&mut self, item: &'hir hir::Item) {
let name = if let hir::ItemImpl(.., ref ty, _) = item.node {
self.map.node_to_pretty_string(ty.id)
} else {
item.name.to_string()
};
self.visit_testable(name, &item.attrs, |this| {
intravisit::walk_item(this, item);
});
}
fn visit_trait_item(&mut self, item: &'hir hir::TraitItem) {
self.visit_testable(item.name.to_string(), &item.attrs, |this| {
intravisit::walk_trait_item(this, item);
});
}
fn visit_impl_item(&mut self, item: &'hir hir::ImplItem) {
self.visit_testable(item.name.to_string(), &item.attrs, |this| {
intravisit::walk_impl_item(this, item);
});
}
fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem) {
self.visit_testable(item.name.to_string(), &item.attrs, |this| {
intravisit::walk_foreign_item(this, item);
});
}
fn visit_variant(&mut self,
v: &'hir hir::Variant,
g: &'hir hir::Generics,
item_id: ast::NodeId) {
self.visit_testable(v.node.name.to_string(), &v.node.attrs, |this| {
intravisit::walk_variant(this, v, g, item_id);
});
}
fn visit_struct_field(&mut self, f: &'hir hir::StructField) {
self.visit_testable(f.name.to_string(), &f.attrs, |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(), &macro_def.attrs, |_| ());
}
}