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
::collections
::HashMap
;
13 use std
::ffi
::OsString
;
14 use std
::io
::prelude
::*;
16 use std
::path
::{Path, PathBuf}
;
17 use std
::panic
::{self, AssertUnwindSafe}
;
18 use std
::process
::Command
;
21 use std
::sync
::{Arc, Mutex}
;
25 use rustc
::dep_graph
::DepGraph
;
27 use rustc
::hir
::intravisit
;
28 use rustc
::session
::{self, config}
;
29 use rustc
::session
::config
::{OutputType, OutputTypes, Externs}
;
30 use rustc
::session
::search_paths
::{SearchPaths, PathKind}
;
31 use rustc_back
::dynamic_lib
::DynamicLibrary
;
32 use rustc_back
::tempdir
::TempDir
;
33 use rustc_driver
::{self, driver, Compilation}
;
34 use rustc_driver
::driver
::phase_2_configure_and_expand
;
35 use rustc_metadata
::cstore
::CStore
;
36 use rustc_resolve
::MakeGlobMap
;
38 use rustc_trans
::back
::link
;
40 use syntax
::codemap
::CodeMap
;
41 use syntax
::feature_gate
::UnstableFeatures
;
42 use syntax_pos
::{BytePos, DUMMY_SP, Pos, Span}
;
44 use errors
::emitter
::ColorConfig
;
46 use clean
::Attributes
;
47 use html
::markdown
::{self, RenderType}
;
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,
59 mut test_args
: Vec
<String
>,
60 crate_name
: Option
<String
>,
61 maybe_sysroot
: Option
<PathBuf
>,
62 render_type
: RenderType
,
63 display_warnings
: bool
)
65 let input_path
= PathBuf
::from(input
);
66 let input
= config
::Input
::File(input_path
.clone());
68 let sessopts
= config
::Options
{
69 maybe_sysroot
: maybe_sysroot
.clone().or_else(
70 || Some(env
::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
71 search_paths
: libs
.clone(),
72 crate_types
: vec
![config
::CrateTypeDylib
],
73 externs
: externs
.clone(),
74 unstable_features
: UnstableFeatures
::from_environment(),
75 actually_rustdoc
: true,
76 ..config
::basic_options().clone()
79 let codemap
= Rc
::new(CodeMap
::new(sessopts
.file_path_mapping()));
81 errors
::Handler
::with_tty_emitter(ColorConfig
::Auto
, true, false, Some(codemap
.clone()));
83 let dep_graph
= DepGraph
::new(false);
84 let _ignore
= dep_graph
.in_ignore();
85 let cstore
= Rc
::new(CStore
::new(&dep_graph
, box rustc_trans
::LlvmMetadataLoader
));
86 let mut sess
= session
::build_session_(
87 sessopts
, &dep_graph
, Some(input_path
.clone()), handler
, codemap
.clone(), cstore
.clone(),
89 rustc_trans
::init(&sess
);
90 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
91 sess
.parse_sess
.config
=
92 config
::build_configuration(&sess
, config
::parse_cfgspecs(cfgs
.clone()));
94 let krate
= panictry
!(driver
::phase_1_parse_input(&sess
, &input
));
95 let driver
::ExpansionResult { defs, mut hir_forest, .. }
= {
96 phase_2_configure_and_expand(
97 &sess
, &cstore
, krate
, None
, "rustdoc-test", None
, MakeGlobMap
::No
, |_
| Ok(())
98 ).expect("phase_2_configure_and_expand aborted in rustdoc!")
101 let crate_name
= crate_name
.unwrap_or_else(|| {
102 link
::find_crate_name(None
, &hir_forest
.krate().attrs
, &input
)
104 let opts
= scrape_test_config(hir_forest
.krate());
105 let mut collector
= Collector
::new(crate_name
,
117 let dep_graph
= DepGraph
::new(false);
118 let _ignore
= dep_graph
.in_ignore();
119 let map
= hir
::map
::map_crate(&mut hir_forest
, defs
);
120 let krate
= map
.krate();
121 let mut hir_collector
= HirCollector
{
122 collector
: &mut collector
,
125 hir_collector
.visit_testable("".to_string(), &krate
.attrs
, |this
| {
126 intravisit
::walk_crate(this
, krate
);
130 test_args
.insert(0, "rustdoctest".to_string());
132 testing
::test_main(&test_args
,
133 collector
.tests
.into_iter().collect(),
134 testing
::Options
::new().display_output(display_warnings
));
138 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
139 fn scrape_test_config(krate
: &::rustc
::hir
::Crate
) -> TestOptions
{
140 use syntax
::print
::pprust
;
142 let mut opts
= TestOptions
{
143 no_crate_inject
: false,
147 let test_attrs
: Vec
<_
> = krate
.attrs
.iter()
148 .filter(|a
| a
.check_name("doc"))
149 .flat_map(|a
| a
.meta_item_list().unwrap_or_else(Vec
::new
))
150 .filter(|a
| a
.check_name("test"))
152 let attrs
= test_attrs
.iter().flat_map(|a
| a
.meta_item_list().unwrap_or(&[]));
155 if attr
.check_name("no_crate_inject") {
156 opts
.no_crate_inject
= true;
158 if attr
.check_name("attr") {
159 if let Some(l
) = attr
.meta_item_list() {
161 opts
.attrs
.push(pprust
::meta_list_item_to_string(item
));
170 fn runtest(test
: &str, cratename
: &str, cfgs
: Vec
<String
>, libs
: SearchPaths
,
172 should_panic
: bool
, no_run
: bool
, as_test_harness
: bool
,
173 compile_fail
: bool
, mut error_codes
: Vec
<String
>, opts
: &TestOptions
,
174 maybe_sysroot
: Option
<PathBuf
>) {
175 // the test harness wants its own `main` & top level functions, so
176 // never wrap the test in `fn main() { ... }`
177 let test
= maketest(test
, Some(cratename
), as_test_harness
, opts
);
178 let input
= config
::Input
::Str
{
179 name
: driver
::anon_src(),
180 input
: test
.to_owned(),
182 let outputs
= OutputTypes
::new(&[(OutputType
::Exe
, None
)]);
184 let sessopts
= config
::Options
{
185 maybe_sysroot
: maybe_sysroot
.or_else(
186 || Some(env
::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
188 crate_types
: vec
![config
::CrateTypeExecutable
],
189 output_types
: outputs
,
191 cg
: config
::CodegenOptions
{
192 prefer_dynamic
: true,
193 .. config
::basic_codegen_options()
195 test
: as_test_harness
,
196 unstable_features
: UnstableFeatures
::from_environment(),
197 ..config
::basic_options().clone()
200 // Shuffle around a few input and output handles here. We're going to pass
201 // an explicit handle into rustc to collect output messages, but we also
202 // want to catch the error message that rustc prints when it fails.
204 // We take our thread-local stderr (likely set by the test runner) and replace
205 // it with a sink that is also passed to rustc itself. When this function
206 // returns the output of the sink is copied onto the output of our own thread.
208 // The basic idea is to not use a default Handler for rustc, and then also
209 // not print things by default to the actual stderr.
210 struct Sink(Arc
<Mutex
<Vec
<u8>>>);
211 impl Write
for Sink
{
212 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
213 Write
::write(&mut *self.0.lock().unwrap(), data
)
215 fn flush(&mut self) -> io
::Result
<()> { Ok(()) }
217 struct Bomb(Arc
<Mutex
<Vec
<u8>>>, Box
<Write
+Send
>);
220 let _
= self.1.write_all(&self.0.lock().unwrap());
223 let data
= Arc
::new(Mutex
::new(Vec
::new()));
224 let codemap
= Rc
::new(CodeMap
::new(sessopts
.file_path_mapping()));
225 let emitter
= errors
::emitter
::EmitterWriter
::new(box Sink(data
.clone()),
226 Some(codemap
.clone()));
227 let old
= io
::set_panic(Some(box Sink(data
.clone())));
228 let _bomb
= Bomb(data
.clone(), old
.unwrap_or(box io
::stdout()));
231 let diagnostic_handler
= errors
::Handler
::with_emitter(true, false, box emitter
);
233 let dep_graph
= DepGraph
::new(false);
234 let cstore
= Rc
::new(CStore
::new(&dep_graph
, box rustc_trans
::LlvmMetadataLoader
));
235 let mut sess
= session
::build_session_(
236 sessopts
, &dep_graph
, None
, diagnostic_handler
, codemap
, cstore
.clone(),
238 rustc_trans
::init(&sess
);
239 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
241 let outdir
= Mutex
::new(TempDir
::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
242 let libdir
= sess
.target_filesearch(PathKind
::All
).get_lib_path();
243 let mut control
= driver
::CompileController
::basic();
244 sess
.parse_sess
.config
=
245 config
::build_configuration(&sess
, config
::parse_cfgspecs(cfgs
.clone()));
246 let out
= Some(outdir
.lock().unwrap().path().to_path_buf());
249 control
.after_analysis
.stop
= Compilation
::Stop
;
252 let res
= panic
::catch_unwind(AssertUnwindSafe(|| {
253 driver
::compile_input(&sess
, &cstore
, &input
, &out
, &None
, None
, &control
)
260 if count
> 0 && !compile_fail
{
261 sess
.fatal("aborting due to previous error(s)")
262 } else if count
== 0 && compile_fail
{
263 panic
!("test compiled while it wasn't supposed to")
265 if count
> 0 && error_codes
.len() > 0 {
266 let out
= String
::from_utf8(data
.lock().unwrap().to_vec()).unwrap();
267 error_codes
.retain(|err
| !out
.contains(err
));
270 Ok(()) if compile_fail
=> {
271 panic
!("test compiled while it wasn't supposed to")
278 panic
!("couldn't compile the test");
280 if error_codes
.len() > 0 {
281 let out
= String
::from_utf8(data
.lock().unwrap().to_vec()).unwrap();
282 error_codes
.retain(|err
| !out
.contains(err
));
287 if error_codes
.len() > 0 {
288 panic
!("Some expected error codes were not found: {:?}", error_codes
);
295 // We're careful to prepend the *target* dylib search path to the child's
296 // environment to ensure that the target loads the right libraries at
297 // runtime. It would be a sad day if the *host* libraries were loaded as a
299 let mut cmd
= Command
::new(&outdir
.lock().unwrap().path().join("rust_out"));
300 let var
= DynamicLibrary
::envvar();
302 let path
= env
::var_os(var
).unwrap_or(OsString
::new());
303 let mut path
= env
::split_paths(&path
).collect
::<Vec
<_
>>();
304 path
.insert(0, libdir
.clone());
305 env
::join_paths(path
).unwrap()
307 cmd
.env(var
, &newpath
);
310 Err(e
) => panic
!("couldn't run the test: {}{}", e
,
311 if e
.kind() == io
::ErrorKind
::PermissionDenied
{
312 " - maybe your tempdir is mounted with noexec?"
315 if should_panic
&& out
.status
.success() {
316 panic
!("test executable succeeded when it should have failed");
317 } else if !should_panic
&& !out
.status
.success() {
318 panic
!("test executable failed:\n{}\n{}\n",
319 str::from_utf8(&out
.stdout
).unwrap_or(""),
320 str::from_utf8(&out
.stderr
).unwrap_or(""));
326 pub fn maketest(s
: &str, cratename
: Option
<&str>, dont_insert_main
: bool
,
327 opts
: &TestOptions
) -> String
{
328 let (crate_attrs
, everything_else
) = partition_source(s
);
330 let mut prog
= String
::new();
332 // First push any outer attributes from the example, assuming they
333 // are intended to be crate attributes.
334 prog
.push_str(&crate_attrs
);
336 // Next, any attributes for other aspects such as lints.
337 for attr
in &opts
.attrs
{
338 prog
.push_str(&format
!("#![{}]\n", attr
));
341 // Don't inject `extern crate std` because it's already injected by the
343 if !s
.contains("extern crate") && !opts
.no_crate_inject
&& cratename
!= Some("std") {
344 if let Some(cratename
) = cratename
{
345 if s
.contains(cratename
) {
346 prog
.push_str(&format
!("extern crate {};\n", cratename
));
350 if dont_insert_main
|| s
.contains("fn main") {
351 prog
.push_str(&everything_else
);
353 prog
.push_str("fn main() {\n");
354 prog
.push_str(&everything_else
);
355 prog
= prog
.trim().into();
356 prog
.push_str("\n}");
359 info
!("final test program: {}", prog
);
364 // FIXME(aburka): use a real parser to deal with multiline attributes
365 fn partition_source(s
: &str) -> (String
, String
) {
366 use std_unicode
::str::UnicodeStr
;
368 let mut after_header
= false;
369 let mut before
= String
::new();
370 let mut after
= String
::new();
372 for line
in s
.lines() {
373 let trimline
= line
.trim();
374 let header
= trimline
.is_whitespace() ||
375 trimline
.starts_with("#![");
376 if !header
|| after_header
{
378 after
.push_str(line
);
379 after
.push_str("\n");
381 before
.push_str(line
);
382 before
.push_str("\n");
389 pub struct Collector
{
390 pub tests
: Vec
<testing
::TestDescAndFn
>,
391 // to be removed when hoedown will be definitely gone
392 pub old_tests
: HashMap
<String
, Vec
<String
>>,
399 current_header
: Option
<String
>,
402 maybe_sysroot
: Option
<PathBuf
>,
404 codemap
: Option
<Rc
<CodeMap
>>,
405 filename
: Option
<String
>,
406 // to be removed when hoedown will be removed as well
407 pub render_type
: RenderType
,
411 pub fn new(cratename
: String
, cfgs
: Vec
<String
>, libs
: SearchPaths
, externs
: Externs
,
412 use_headers
: bool
, opts
: TestOptions
, maybe_sysroot
: Option
<PathBuf
>,
413 codemap
: Option
<Rc
<CodeMap
>>, filename
: Option
<String
>,
414 render_type
: RenderType
) -> Collector
{
417 old_tests
: HashMap
::new(),
423 use_headers
: use_headers
,
424 current_header
: None
,
425 cratename
: cratename
,
427 maybe_sysroot
: maybe_sysroot
,
431 render_type
: render_type
,
435 fn generate_name(&self, line
: usize, filename
: &str) -> String
{
436 if self.use_headers
{
437 if let Some(ref header
) = self.current_header
{
438 format
!("{} - {} (line {})", filename
, header
, line
)
440 format
!("{} - (line {})", filename
, line
)
443 format
!("{} - {} (line {})", filename
, self.names
.join("::"), line
)
447 // to be removed once hoedown is gone
448 fn generate_name_beginning(&self, filename
: &str) -> String
{
449 if self.use_headers
{
450 if let Some(ref header
) = self.current_header
{
451 format
!("{} - {} (line", filename
, header
)
453 format
!("{} - (line", filename
)
456 format
!("{} - {} (line", filename
, self.names
.join("::"))
460 pub fn add_old_test(&mut self, test
: String
, filename
: String
) {
461 let name_beg
= self.generate_name_beginning(&filename
);
462 let entry
= self.old_tests
.entry(name_beg
)
463 .or_insert(Vec
::new());
464 entry
.push(test
.trim().to_owned());
467 pub fn add_test(&mut self, test
: String
,
468 should_panic
: bool
, no_run
: bool
, should_ignore
: bool
,
469 as_test_harness
: bool
, compile_fail
: bool
, error_codes
: Vec
<String
>,
470 line
: usize, filename
: String
) {
471 let name
= self.generate_name(line
, &filename
);
472 // to be removed when hoedown is removed
473 if self.render_type
== RenderType
::Pulldown
{
474 let name_beg
= self.generate_name_beginning(&filename
);
475 let mut found
= false;
476 let test
= test
.trim().to_owned();
477 if let Some(entry
) = self.old_tests
.get_mut(&name_beg
) {
478 found
= entry
.remove_item(&test
).is_some();
481 let _
= writeln
!(&mut io
::stderr(),
482 "WARNING: {} Code block is not currently run as a test, but will \
483 in future versions of rustdoc. Please ensure this code block is \
484 a runnable test, or use the `ignore` directive.",
489 let cfgs
= self.cfgs
.clone();
490 let libs
= self.libs
.clone();
491 let externs
= self.externs
.clone();
492 let cratename
= self.cratename
.to_string();
493 let opts
= self.opts
.clone();
494 let maybe_sysroot
= self.maybe_sysroot
.clone();
495 debug
!("Creating test {}: {}", name
, test
);
496 self.tests
.push(testing
::TestDescAndFn
{
497 desc
: testing
::TestDesc
{
498 name
: testing
::DynTestName(name
),
499 ignore
: should_ignore
,
500 // compiler failures are test failures
501 should_panic
: testing
::ShouldPanic
::No
,
503 testfn
: testing
::DynTestFn(box move |()| {
504 let panic
= io
::set_panic(None
);
505 let print
= io
::set_print(None
);
507 rustc_driver
::in_rustc_thread(move || {
508 io
::set_panic(panic
);
509 io
::set_print(print
);
525 Err(err
) => panic
::resume_unwind(err
),
531 pub fn get_line(&self) -> usize {
532 if let Some(ref codemap
) = self.codemap
{
533 let line
= self.position
.lo
.to_usize();
534 let line
= codemap
.lookup_char_pos(BytePos(line
as u32)).line
;
535 if line
> 0 { line - 1 }
else { line }
541 pub fn set_position(&mut self, position
: Span
) {
542 self.position
= position
;
545 pub fn get_filename(&self) -> String
{
546 if let Some(ref codemap
) = self.codemap
{
547 let filename
= codemap
.span_to_filename(self.position
);
548 if let Ok(cur_dir
) = env
::current_dir() {
549 if let Ok(path
) = Path
::new(&filename
).strip_prefix(&cur_dir
) {
550 if let Some(path
) = path
.to_str() {
551 return path
.to_owned();
556 } else if let Some(ref filename
) = self.filename
{
563 pub fn register_header(&mut self, name
: &str, level
: u32) {
564 if self.use_headers
&& level
== 1 {
565 // we use these headings as test names, so it's good if
566 // they're valid identifiers.
567 let name
= name
.chars().enumerate().map(|(i
, c
)| {
568 if (i
== 0 && c
.is_xid_start()) ||
569 (i
!= 0 && c
.is_xid_continue()) {
574 }).collect
::<String
>();
576 // new header => reset count.
578 self.current_header
= Some(name
);
583 struct HirCollector
<'a
, 'hir
: 'a
> {
584 collector
: &'a
mut Collector
,
585 map
: &'a hir
::map
::Map
<'hir
>
588 impl<'a
, 'hir
> HirCollector
<'a
, 'hir
> {
589 fn visit_testable
<F
: FnOnce(&mut Self)>(&mut self,
591 attrs
: &[ast
::Attribute
],
593 let has_name
= !name
.is_empty();
595 self.collector
.names
.push(name
);
598 let mut attrs
= Attributes
::from_ast(attrs
);
599 attrs
.collapse_doc_comments();
600 attrs
.unindent_doc_comments();
601 if let Some(doc
) = attrs
.doc_value() {
602 self.collector
.cnt
= 0;
603 if self.collector
.render_type
== RenderType
::Pulldown
{
604 markdown
::old_find_testable_code(doc
, self.collector
,
605 attrs
.span
.unwrap_or(DUMMY_SP
));
606 markdown
::find_testable_code(doc
, self.collector
,
607 attrs
.span
.unwrap_or(DUMMY_SP
));
609 markdown
::old_find_testable_code(doc
, self.collector
,
610 attrs
.span
.unwrap_or(DUMMY_SP
));
617 self.collector
.names
.pop();
622 impl<'a
, 'hir
> intravisit
::Visitor
<'hir
> for HirCollector
<'a
, 'hir
> {
623 fn nested_visit_map
<'this
>(&'this
mut self) -> intravisit
::NestedVisitorMap
<'this
, 'hir
> {
624 intravisit
::NestedVisitorMap
::All(&self.map
)
627 fn visit_item(&mut self, item
: &'hir hir
::Item
) {
628 let name
= if let hir
::ItemImpl(.., ref ty
, _
) = item
.node
{
629 self.map
.node_to_pretty_string(ty
.id
)
631 item
.name
.to_string()
634 self.visit_testable(name
, &item
.attrs
, |this
| {
635 intravisit
::walk_item(this
, item
);
639 fn visit_trait_item(&mut self, item
: &'hir hir
::TraitItem
) {
640 self.visit_testable(item
.name
.to_string(), &item
.attrs
, |this
| {
641 intravisit
::walk_trait_item(this
, item
);
645 fn visit_impl_item(&mut self, item
: &'hir hir
::ImplItem
) {
646 self.visit_testable(item
.name
.to_string(), &item
.attrs
, |this
| {
647 intravisit
::walk_impl_item(this
, item
);
651 fn visit_foreign_item(&mut self, item
: &'hir hir
::ForeignItem
) {
652 self.visit_testable(item
.name
.to_string(), &item
.attrs
, |this
| {
653 intravisit
::walk_foreign_item(this
, item
);
657 fn visit_variant(&mut self,
658 v
: &'hir hir
::Variant
,
659 g
: &'hir hir
::Generics
,
660 item_id
: ast
::NodeId
) {
661 self.visit_testable(v
.node
.name
.to_string(), &v
.node
.attrs
, |this
| {
662 intravisit
::walk_variant(this
, v
, g
, item_id
);
666 fn visit_struct_field(&mut self, f
: &'hir hir
::StructField
) {
667 self.visit_testable(f
.name
.to_string(), &f
.attrs
, |this
| {
668 intravisit
::walk_struct_field(this
, f
);
672 fn visit_macro_def(&mut self, macro_def
: &'hir hir
::MacroDef
) {
673 self.visit_testable(macro_def
.name
.to_string(), ¯o_def
.attrs
, |_
| ());