1 // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use std
::cell
::{RefCell, Cell}
;
12 use std
::collections
::HashMap
;
14 use std
::ffi
::OsString
;
15 use std
::io
::prelude
::*;
17 use std
::path
::PathBuf
;
18 use std
::panic
::{self, AssertUnwindSafe}
;
19 use std
::process
::Command
;
22 use std
::sync
::{Arc, Mutex}
;
26 use rustc
::dep_graph
::DepGraph
;
27 use rustc
::hir
::map
as hir_map
;
28 use rustc
::session
::{self, config}
;
29 use rustc
::session
::config
::{get_unstable_features_setting, OutputType}
;
30 use rustc
::session
::search_paths
::{SearchPaths, PathKind}
;
31 use rustc
::hir
::lowering
::{lower_crate, LoweringContext}
;
32 use rustc_back
::dynamic_lib
::DynamicLibrary
;
33 use rustc_back
::tempdir
::TempDir
;
34 use rustc_driver
::{driver, Compilation}
;
35 use rustc_metadata
::cstore
::CStore
;
36 use syntax
::codemap
::CodeMap
;
38 use syntax
::errors
::emitter
::ColorConfig
;
39 use syntax
::parse
::token
;
47 use visit_ast
::RustdocVisitor
;
49 #[derive(Clone, Default)]
50 pub struct TestOptions
{
51 pub no_crate_inject
: bool
,
52 pub attrs
: Vec
<String
>,
55 pub fn run(input
: &str,
58 externs
: core
::Externs
,
59 mut test_args
: Vec
<String
>,
60 crate_name
: Option
<String
>)
62 let input_path
= PathBuf
::from(input
);
63 let input
= config
::Input
::File(input_path
.clone());
65 let sessopts
= config
::Options
{
66 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
67 .parent().unwrap().to_path_buf()),
68 search_paths
: libs
.clone(),
69 crate_types
: vec
!(config
::CrateTypeDylib
),
70 externs
: externs
.clone(),
71 unstable_features
: get_unstable_features_setting(),
72 ..config
::basic_options().clone()
75 let codemap
= Rc
::new(CodeMap
::new());
76 let diagnostic_handler
= errors
::Handler
::with_tty_emitter(ColorConfig
::Auto
,
82 let cstore
= Rc
::new(CStore
::new(token
::get_ident_interner()));
83 let sess
= session
::build_session_(sessopts
,
84 Some(input_path
.clone()),
88 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
90 let mut cfg
= config
::build_configuration(&sess
);
91 cfg
.extend(config
::parse_cfgspecs(cfgs
.clone()));
92 let krate
= panictry
!(driver
::phase_1_parse_input(&sess
, cfg
, &input
));
93 let krate
= driver
::phase_2_configure_and_expand(&sess
, &cstore
, krate
,
95 .expect("phase_2_configure_and_expand aborted in rustdoc!");
96 let krate
= driver
::assign_node_ids(&sess
, krate
);
97 let lcx
= LoweringContext
::new(&sess
, Some(&krate
));
98 let krate
= lower_crate(&lcx
, &krate
);
100 let opts
= scrape_test_config(&krate
);
102 let dep_graph
= DepGraph
::new(false);
103 let _ignore
= dep_graph
.in_ignore();
104 let mut forest
= hir_map
::Forest
::new(krate
, dep_graph
.clone());
105 let map
= hir_map
::map_crate(&mut forest
);
107 let ctx
= core
::DocContext
{
109 maybe_typed
: core
::NotTyped(&sess
),
111 external_paths
: RefCell
::new(Some(HashMap
::new())),
112 external_traits
: RefCell
::new(None
),
113 external_typarams
: RefCell
::new(None
),
114 inlined
: RefCell
::new(None
),
115 all_crate_impls
: RefCell
::new(HashMap
::new()),
116 deref_trait_did
: Cell
::new(None
),
119 let mut v
= RustdocVisitor
::new(&ctx
, None
);
120 v
.visit(ctx
.map
.krate());
121 let mut krate
= v
.clean(&ctx
);
122 if let Some(name
) = crate_name
{
125 let krate
= passes
::collapse_docs(krate
);
126 let krate
= passes
::unindent_comments(krate
);
128 let mut collector
= Collector
::new(krate
.name
.to_string(),
134 collector
.fold_crate(krate
);
136 test_args
.insert(0, "rustdoctest".to_string());
138 testing
::test_main(&test_args
,
139 collector
.tests
.into_iter().collect());
143 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
144 fn scrape_test_config(krate
: &::rustc
::hir
::Crate
) -> TestOptions
{
145 use syntax
::attr
::AttrMetaMethods
;
146 use syntax
::print
::pprust
;
148 let mut opts
= TestOptions
{
149 no_crate_inject
: false,
153 let attrs
= krate
.attrs
.iter()
154 .filter(|a
| a
.check_name("doc"))
155 .filter_map(|a
| a
.meta_item_list())
157 .filter(|a
| a
.check_name("test"))
158 .filter_map(|a
| a
.meta_item_list())
161 if attr
.check_name("no_crate_inject") {
162 opts
.no_crate_inject
= true;
164 if attr
.check_name("attr") {
165 if let Some(l
) = attr
.meta_item_list() {
167 opts
.attrs
.push(pprust
::meta_item_to_string(item
));
176 fn runtest(test
: &str, cratename
: &str, cfgs
: Vec
<String
>, libs
: SearchPaths
,
177 externs
: core
::Externs
,
178 should_panic
: bool
, no_run
: bool
, as_test_harness
: bool
,
179 compile_fail
: bool
, opts
: &TestOptions
) {
180 // the test harness wants its own `main` & top level functions, so
181 // never wrap the test in `fn main() { ... }`
182 let test
= maketest(test
, Some(cratename
), as_test_harness
, opts
);
183 let input
= config
::Input
::Str
{
184 name
: driver
::anon_src(),
185 input
: test
.to_owned(),
187 let mut outputs
= HashMap
::new();
188 outputs
.insert(OutputType
::Exe
, None
);
190 let sessopts
= config
::Options
{
191 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
192 .parent().unwrap().to_path_buf()),
194 crate_types
: vec
!(config
::CrateTypeExecutable
),
195 output_types
: outputs
,
197 cg
: config
::CodegenOptions
{
198 prefer_dynamic
: true,
199 .. config
::basic_codegen_options()
201 test
: as_test_harness
,
202 unstable_features
: get_unstable_features_setting(),
203 ..config
::basic_options().clone()
206 // Shuffle around a few input and output handles here. We're going to pass
207 // an explicit handle into rustc to collect output messages, but we also
208 // want to catch the error message that rustc prints when it fails.
210 // We take our thread-local stderr (likely set by the test runner) and replace
211 // it with a sink that is also passed to rustc itself. When this function
212 // returns the output of the sink is copied onto the output of our own thread.
214 // The basic idea is to not use a default Handler for rustc, and then also
215 // not print things by default to the actual stderr.
216 struct Sink(Arc
<Mutex
<Vec
<u8>>>);
217 impl Write
for Sink
{
218 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
219 Write
::write(&mut *self.0.lock().unwrap(), data
)
221 fn flush(&mut self) -> io
::Result
<()> { Ok(()) }
223 struct Bomb(Arc
<Mutex
<Vec
<u8>>>, Box
<Write
+Send
>);
226 let _
= self.1.write_all(&self.0.lock().unwrap());
229 let data
= Arc
::new(Mutex
::new(Vec
::new()));
230 let codemap
= Rc
::new(CodeMap
::new());
231 let emitter
= errors
::emitter
::EmitterWriter
::new(box Sink(data
.clone()),
234 let old
= io
::set_panic(box Sink(data
.clone()));
235 let _bomb
= Bomb(data
, old
.unwrap_or(box io
::stdout()));
238 let diagnostic_handler
= errors
::Handler
::with_emitter(true, false, box emitter
);
240 let cstore
= Rc
::new(CStore
::new(token
::get_ident_interner()));
241 let sess
= session
::build_session_(sessopts
,
246 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
248 let outdir
= Mutex
::new(TempDir
::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
249 let libdir
= sess
.target_filesearch(PathKind
::All
).get_lib_path();
250 let mut control
= driver
::CompileController
::basic();
251 let mut cfg
= config
::build_configuration(&sess
);
252 cfg
.extend(config
::parse_cfgspecs(cfgs
.clone()));
253 let out
= Some(outdir
.lock().unwrap().path().to_path_buf());
256 control
.after_analysis
.stop
= Compilation
::Stop
;
259 let res
= panic
::catch_unwind(AssertUnwindSafe(|| {
260 driver
::compile_input(&sess
, &cstore
, cfg
.clone(),
262 &None
, None
, &control
)
268 Err(count
) if count
> 0 && compile_fail
== false => {
269 sess
.fatal("aborting due to previous error(s)")
271 Ok(()) if compile_fail
=> panic
!("test compiled while it wasn't supposed to"),
275 Err(_
) if compile_fail
== false => panic
!("couldn't compile the test"),
283 // We're careful to prepend the *target* dylib search path to the child's
284 // environment to ensure that the target loads the right libraries at
285 // runtime. It would be a sad day if the *host* libraries were loaded as a
287 let mut cmd
= Command
::new(&outdir
.lock().unwrap().path().join("rust_out"));
288 let var
= DynamicLibrary
::envvar();
290 let path
= env
::var_os(var
).unwrap_or(OsString
::new());
291 let mut path
= env
::split_paths(&path
).collect
::<Vec
<_
>>();
292 path
.insert(0, libdir
.clone());
293 env
::join_paths(path
).unwrap()
295 cmd
.env(var
, &newpath
);
298 Err(e
) => panic
!("couldn't run the test: {}{}", e
,
299 if e
.kind() == io
::ErrorKind
::PermissionDenied
{
300 " - maybe your tempdir is mounted with noexec?"
303 if should_panic
&& out
.status
.success() {
304 panic
!("test executable succeeded when it should have failed");
305 } else if !should_panic
&& !out
.status
.success() {
306 panic
!("test executable failed:\n{}\n{}",
307 str::from_utf8(&out
.stdout
).unwrap_or(""),
308 str::from_utf8(&out
.stderr
).unwrap_or(""));
314 pub fn maketest(s
: &str, cratename
: Option
<&str>, dont_insert_main
: bool
,
315 opts
: &TestOptions
) -> String
{
316 let (crate_attrs
, everything_else
) = partition_source(s
);
318 let mut prog
= String
::new();
320 // First push any outer attributes from the example, assuming they
321 // are intended to be crate attributes.
322 prog
.push_str(&crate_attrs
);
324 // Next, any attributes for other aspects such as lints.
325 for attr
in &opts
.attrs
{
326 prog
.push_str(&format
!("#![{}]\n", attr
));
329 // Don't inject `extern crate std` because it's already injected by the
331 if !s
.contains("extern crate") && !opts
.no_crate_inject
&& cratename
!= Some("std") {
332 if let Some(cratename
) = cratename
{
333 if s
.contains(cratename
) {
334 prog
.push_str(&format
!("extern crate {};\n", cratename
));
338 if dont_insert_main
|| s
.contains("fn main") {
339 prog
.push_str(&everything_else
);
341 prog
.push_str("fn main() {\n ");
342 prog
.push_str(&everything_else
.replace("\n", "\n "));
343 prog
= prog
.trim().into();
344 prog
.push_str("\n}");
347 info
!("final test program: {}", prog
);
352 fn partition_source(s
: &str) -> (String
, String
) {
353 use rustc_unicode
::str::UnicodeStr
;
355 let mut after_header
= false;
356 let mut before
= String
::new();
357 let mut after
= String
::new();
359 for line
in s
.lines() {
360 let trimline
= line
.trim();
361 let header
= trimline
.is_whitespace() ||
362 trimline
.starts_with("#![feature");
363 if !header
|| after_header
{
365 after
.push_str(line
);
366 after
.push_str("\n");
368 before
.push_str(line
);
369 before
.push_str("\n");
373 return (before
, after
);
376 pub struct Collector
{
377 pub tests
: Vec
<testing
::TestDescAndFn
>,
381 externs
: core
::Externs
,
384 current_header
: Option
<String
>,
390 pub fn new(cratename
: String
, cfgs
: Vec
<String
>, libs
: SearchPaths
, externs
: core
::Externs
,
391 use_headers
: bool
, opts
: TestOptions
) -> Collector
{
399 use_headers
: use_headers
,
400 current_header
: None
,
401 cratename
: cratename
,
406 pub fn add_test(&mut self, test
: String
,
407 should_panic
: bool
, no_run
: bool
, should_ignore
: bool
,
408 as_test_harness
: bool
, compile_fail
: bool
) {
409 let name
= if self.use_headers
{
410 let s
= self.current_header
.as_ref().map(|s
| &**s
).unwrap_or("");
411 format
!("{}_{}", s
, self.cnt
)
413 format
!("{}_{}", self.names
.join("::"), self.cnt
)
416 let cfgs
= self.cfgs
.clone();
417 let libs
= self.libs
.clone();
418 let externs
= self.externs
.clone();
419 let cratename
= self.cratename
.to_string();
420 let opts
= self.opts
.clone();
421 debug
!("Creating test {}: {}", name
, test
);
422 self.tests
.push(testing
::TestDescAndFn
{
423 desc
: testing
::TestDesc
{
424 name
: testing
::DynTestName(name
),
425 ignore
: should_ignore
,
426 // compiler failures are test failures
427 should_panic
: testing
::ShouldPanic
::No
,
429 testfn
: testing
::DynTestFn(box move|| {
444 pub fn register_header(&mut self, name
: &str, level
: u32) {
445 if self.use_headers
&& level
== 1 {
446 // we use these headings as test names, so it's good if
447 // they're valid identifiers.
448 let name
= name
.chars().enumerate().map(|(i
, c
)| {
449 if (i
== 0 && c
.is_xid_start()) ||
450 (i
!= 0 && c
.is_xid_continue()) {
455 }).collect
::<String
>();
457 // new header => reset count.
459 self.current_header
= Some(name
);
464 impl DocFolder
for Collector
{
465 fn fold_item(&mut self, item
: clean
::Item
) -> Option
<clean
::Item
> {
466 let current_name
= match item
.name
{
467 Some(ref name
) if !name
.is_empty() => Some(name
.clone()),
468 _
=> typename_if_impl(&item
)
471 let pushed
= current_name
.map(|name
| self.names
.push(name
)).is_some();
473 if let Some(doc
) = item
.doc_value() {
475 markdown
::find_testable_code(doc
, &mut *self);
478 let ret
= self.fold_item_recur(item
);
485 // FIXME: it would be better to not have the escaped version in the first place
486 fn unescape_for_testname(mut s
: String
) -> String
{
488 if s
.contains("&") {
489 s
= s
.replace("&", "&");
491 // `::&'a mut Foo::` looks weird, let's make it `::<&'a mut Foo>`::
492 if let Some('
&'
) = s
.chars().nth(0) {
493 s
= format
!("<{}>", s
);
497 // either `<..>` or `->`
498 if s
.contains(">") {
499 s
.replace(">", ">")
500 .replace("<", "<")
506 fn typename_if_impl(item
: &clean
::Item
) -> Option
<String
> {
507 if let clean
::ItemEnum
::ImplItem(ref impl_
) = item
.inner
{
508 let path
= impl_
.for_
.to_string();
509 let unescaped_path
= unescape_for_testname(path
);