]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/test.rs
Rollup merge of #41520 - estebank:trace-macro, r=nikomatsakis
[rust.git] / src / librustdoc / test.rs
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.
4 //
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.
10
11 use std::collections::HashMap;
12 use std::env;
13 use std::ffi::OsString;
14 use std::io::prelude::*;
15 use std::io;
16 use std::path::{Path, PathBuf};
17 use std::panic::{self, AssertUnwindSafe};
18 use std::process::Command;
19 use std::rc::Rc;
20 use std::str;
21 use std::sync::{Arc, Mutex};
22
23 use testing;
24 use rustc_lint;
25 use rustc::dep_graph::DepGraph;
26 use rustc::hir;
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;
37 use rustc_trans::back::link;
38 use syntax::ast;
39 use syntax::codemap::CodeMap;
40 use syntax::feature_gate::UnstableFeatures;
41 use syntax_pos::{BytePos, DUMMY_SP, Pos, Span};
42 use errors;
43 use errors::emitter::ColorConfig;
44
45 use clean::Attributes;
46 use html::markdown::{self, RenderType};
47
48 #[derive(Clone, Default)]
49 pub struct TestOptions {
50     pub no_crate_inject: bool,
51     pub attrs: Vec<String>,
52 }
53
54 pub fn run(input: &str,
55            cfgs: Vec<String>,
56            libs: SearchPaths,
57            externs: Externs,
58            mut test_args: Vec<String>,
59            crate_name: Option<String>,
60            maybe_sysroot: Option<PathBuf>,
61            render_type: RenderType,
62            display_warnings: bool)
63            -> isize {
64     let input_path = PathBuf::from(input);
65     let input = config::Input::File(input_path.clone());
66
67     let sessopts = config::Options {
68         maybe_sysroot: maybe_sysroot.clone().or_else(
69             || Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
70         search_paths: libs.clone(),
71         crate_types: vec![config::CrateTypeDylib],
72         externs: externs.clone(),
73         unstable_features: UnstableFeatures::from_environment(),
74         actually_rustdoc: true,
75         ..config::basic_options().clone()
76     };
77
78     let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
79     let handler =
80         errors::Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
81
82     let dep_graph = DepGraph::new(false);
83     let _ignore = dep_graph.in_ignore();
84     let cstore = Rc::new(CStore::new(&dep_graph));
85     let mut sess = session::build_session_(
86         sessopts, &dep_graph, Some(input_path.clone()), handler, codemap.clone(), cstore.clone(),
87     );
88     rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
89     sess.parse_sess.config =
90         config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
91
92     let krate = panictry!(driver::phase_1_parse_input(&sess, &input));
93     let driver::ExpansionResult { defs, mut hir_forest, .. } = {
94         phase_2_configure_and_expand(
95             &sess, &cstore, krate, None, "rustdoc-test", None, MakeGlobMap::No, |_| Ok(())
96         ).expect("phase_2_configure_and_expand aborted in rustdoc!")
97     };
98
99     let crate_name = crate_name.unwrap_or_else(|| {
100         link::find_crate_name(None, &hir_forest.krate().attrs, &input)
101     });
102     let opts = scrape_test_config(hir_forest.krate());
103     let mut collector = Collector::new(crate_name,
104                                        cfgs,
105                                        libs,
106                                        externs,
107                                        false,
108                                        opts,
109                                        maybe_sysroot,
110                                        Some(codemap),
111                                        None,
112                                        render_type);
113
114     {
115         let dep_graph = DepGraph::new(false);
116         let _ignore = dep_graph.in_ignore();
117         let map = hir::map::map_crate(&mut hir_forest, defs);
118         let krate = map.krate();
119         let mut hir_collector = HirCollector {
120             collector: &mut collector,
121             map: &map
122         };
123         hir_collector.visit_testable("".to_string(), &krate.attrs, |this| {
124             intravisit::walk_crate(this, krate);
125         });
126     }
127
128     test_args.insert(0, "rustdoctest".to_string());
129
130     testing::test_main(&test_args,
131                        collector.tests.into_iter().collect(),
132                        testing::Options::new().display_output(display_warnings));
133     0
134 }
135
136 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
137 fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions {
138     use syntax::print::pprust;
139
140     let mut opts = TestOptions {
141         no_crate_inject: false,
142         attrs: Vec::new(),
143     };
144
145     let test_attrs: Vec<_> = krate.attrs.iter()
146         .filter(|a| a.check_name("doc"))
147         .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
148         .filter(|a| a.check_name("test"))
149         .collect();
150     let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
151
152     for attr in attrs {
153         if attr.check_name("no_crate_inject") {
154             opts.no_crate_inject = true;
155         }
156         if attr.check_name("attr") {
157             if let Some(l) = attr.meta_item_list() {
158                 for item in l {
159                     opts.attrs.push(pprust::meta_list_item_to_string(item));
160                 }
161             }
162         }
163     }
164
165     opts
166 }
167
168 fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
169            externs: Externs,
170            should_panic: bool, no_run: bool, as_test_harness: bool,
171            compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
172            maybe_sysroot: Option<PathBuf>) {
173     // the test harness wants its own `main` & top level functions, so
174     // never wrap the test in `fn main() { ... }`
175     let test = maketest(test, Some(cratename), as_test_harness, opts);
176     let input = config::Input::Str {
177         name: driver::anon_src(),
178         input: test.to_owned(),
179     };
180     let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
181
182     let sessopts = config::Options {
183         maybe_sysroot: maybe_sysroot.or_else(
184             || Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
185         search_paths: libs,
186         crate_types: vec![config::CrateTypeExecutable],
187         output_types: outputs,
188         externs: externs,
189         cg: config::CodegenOptions {
190             prefer_dynamic: true,
191             .. config::basic_codegen_options()
192         },
193         test: as_test_harness,
194         unstable_features: UnstableFeatures::from_environment(),
195         ..config::basic_options().clone()
196     };
197
198     // Shuffle around a few input and output handles here. We're going to pass
199     // an explicit handle into rustc to collect output messages, but we also
200     // want to catch the error message that rustc prints when it fails.
201     //
202     // We take our thread-local stderr (likely set by the test runner) and replace
203     // it with a sink that is also passed to rustc itself. When this function
204     // returns the output of the sink is copied onto the output of our own thread.
205     //
206     // The basic idea is to not use a default Handler for rustc, and then also
207     // not print things by default to the actual stderr.
208     struct Sink(Arc<Mutex<Vec<u8>>>);
209     impl Write for Sink {
210         fn write(&mut self, data: &[u8]) -> io::Result<usize> {
211             Write::write(&mut *self.0.lock().unwrap(), data)
212         }
213         fn flush(&mut self) -> io::Result<()> { Ok(()) }
214     }
215     struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>);
216     impl Drop for Bomb {
217         fn drop(&mut self) {
218             let _ = self.1.write_all(&self.0.lock().unwrap());
219         }
220     }
221     let data = Arc::new(Mutex::new(Vec::new()));
222     let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
223     let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
224                                                       Some(codemap.clone()));
225     let old = io::set_panic(Some(box Sink(data.clone())));
226     let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
227
228     // Compile the code
229     let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
230
231     let dep_graph = DepGraph::new(false);
232     let cstore = Rc::new(CStore::new(&dep_graph));
233     let mut sess = session::build_session_(
234         sessopts, &dep_graph, None, diagnostic_handler, codemap, cstore.clone(),
235     );
236     rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
237
238     let outdir = Mutex::new(TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
239     let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
240     let mut control = driver::CompileController::basic();
241     sess.parse_sess.config =
242         config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
243     let out = Some(outdir.lock().unwrap().path().to_path_buf());
244
245     if no_run {
246         control.after_analysis.stop = Compilation::Stop;
247     }
248
249     let res = panic::catch_unwind(AssertUnwindSafe(|| {
250         driver::compile_input(&sess, &cstore, &input, &out, &None, None, &control)
251     }));
252
253     match res {
254         Ok(r) => {
255             match r {
256                 Err(count) => {
257                     if count > 0 && !compile_fail {
258                         sess.fatal("aborting due to previous error(s)")
259                     } else if count == 0 && compile_fail {
260                         panic!("test compiled while it wasn't supposed to")
261                     }
262                     if count > 0 && error_codes.len() > 0 {
263                         let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
264                         error_codes.retain(|err| !out.contains(err));
265                     }
266                 }
267                 Ok(()) if compile_fail => {
268                     panic!("test compiled while it wasn't supposed to")
269                 }
270                 _ => {}
271             }
272         }
273         Err(_) => {
274             if !compile_fail {
275                 panic!("couldn't compile the test");
276             }
277             if error_codes.len() > 0 {
278                 let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
279                 error_codes.retain(|err| !out.contains(err));
280             }
281         }
282     }
283
284     if error_codes.len() > 0 {
285         panic!("Some expected error codes were not found: {:?}", error_codes);
286     }
287
288     if no_run { return }
289
290     // Run the code!
291     //
292     // We're careful to prepend the *target* dylib search path to the child's
293     // environment to ensure that the target loads the right libraries at
294     // runtime. It would be a sad day if the *host* libraries were loaded as a
295     // mistake.
296     let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
297     let var = DynamicLibrary::envvar();
298     let newpath = {
299         let path = env::var_os(var).unwrap_or(OsString::new());
300         let mut path = env::split_paths(&path).collect::<Vec<_>>();
301         path.insert(0, libdir.clone());
302         env::join_paths(path).unwrap()
303     };
304     cmd.env(var, &newpath);
305
306     match cmd.output() {
307         Err(e) => panic!("couldn't run the test: {}{}", e,
308                         if e.kind() == io::ErrorKind::PermissionDenied {
309                             " - maybe your tempdir is mounted with noexec?"
310                         } else { "" }),
311         Ok(out) => {
312             if should_panic && out.status.success() {
313                 panic!("test executable succeeded when it should have failed");
314             } else if !should_panic && !out.status.success() {
315                 panic!("test executable failed:\n{}\n{}\n",
316                        str::from_utf8(&out.stdout).unwrap_or(""),
317                        str::from_utf8(&out.stderr).unwrap_or(""));
318             }
319         }
320     }
321 }
322
323 pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
324                 opts: &TestOptions) -> String {
325     let (crate_attrs, everything_else) = partition_source(s);
326
327     let mut prog = String::new();
328
329     // First push any outer attributes from the example, assuming they
330     // are intended to be crate attributes.
331     prog.push_str(&crate_attrs);
332
333     // Next, any attributes for other aspects such as lints.
334     for attr in &opts.attrs {
335         prog.push_str(&format!("#![{}]\n", attr));
336     }
337
338     // Don't inject `extern crate std` because it's already injected by the
339     // compiler.
340     if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
341         if let Some(cratename) = cratename {
342             if s.contains(cratename) {
343                 prog.push_str(&format!("extern crate {};\n", cratename));
344             }
345         }
346     }
347     if dont_insert_main || s.contains("fn main") {
348         prog.push_str(&everything_else);
349     } else {
350         prog.push_str("fn main() {\n");
351         prog.push_str(&everything_else);
352         prog = prog.trim().into();
353         prog.push_str("\n}");
354     }
355
356     info!("final test program: {}", prog);
357
358     prog
359 }
360
361 // FIXME(aburka): use a real parser to deal with multiline attributes
362 fn partition_source(s: &str) -> (String, String) {
363     use std_unicode::str::UnicodeStr;
364
365     let mut after_header = false;
366     let mut before = String::new();
367     let mut after = String::new();
368
369     for line in s.lines() {
370         let trimline = line.trim();
371         let header = trimline.is_whitespace() ||
372             trimline.starts_with("#![");
373         if !header || after_header {
374             after_header = true;
375             after.push_str(line);
376             after.push_str("\n");
377         } else {
378             before.push_str(line);
379             before.push_str("\n");
380         }
381     }
382
383     (before, after)
384 }
385
386 pub struct Collector {
387     pub tests: Vec<testing::TestDescAndFn>,
388     // to be removed when hoedown will be definitely gone
389     pub old_tests: HashMap<String, Vec<String>>,
390     names: Vec<String>,
391     cfgs: Vec<String>,
392     libs: SearchPaths,
393     externs: Externs,
394     cnt: usize,
395     use_headers: bool,
396     current_header: Option<String>,
397     cratename: String,
398     opts: TestOptions,
399     maybe_sysroot: Option<PathBuf>,
400     position: Span,
401     codemap: Option<Rc<CodeMap>>,
402     filename: Option<String>,
403     // to be removed when hoedown will be removed as well
404     pub render_type: RenderType,
405 }
406
407 impl Collector {
408     pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
409                use_headers: bool, opts: TestOptions, maybe_sysroot: Option<PathBuf>,
410                codemap: Option<Rc<CodeMap>>, filename: Option<String>,
411                render_type: RenderType) -> Collector {
412         Collector {
413             tests: Vec::new(),
414             old_tests: HashMap::new(),
415             names: Vec::new(),
416             cfgs: cfgs,
417             libs: libs,
418             externs: externs,
419             cnt: 0,
420             use_headers: use_headers,
421             current_header: None,
422             cratename: cratename,
423             opts: opts,
424             maybe_sysroot: maybe_sysroot,
425             position: DUMMY_SP,
426             codemap: codemap,
427             filename: filename,
428             render_type: render_type,
429         }
430     }
431
432     fn generate_name(&self, line: usize, filename: &str) -> String {
433         if self.use_headers {
434             if let Some(ref header) = self.current_header {
435                 format!("{} - {} (line {})", filename, header, line)
436             } else {
437                 format!("{} - (line {})", filename, line)
438             }
439         } else {
440             format!("{} - {} (line {})", filename, self.names.join("::"), line)
441         }
442     }
443
444     // to be removed once hoedown is gone
445     fn generate_name_beginning(&self, filename: &str) -> String {
446         if self.use_headers {
447             if let Some(ref header) = self.current_header {
448                 format!("{} - {} (line", filename, header)
449             } else {
450                 format!("{} - (line", filename)
451             }
452         } else {
453             format!("{} - {} (line", filename, self.names.join("::"))
454         }
455     }
456
457     pub fn add_old_test(&mut self, test: String, filename: String) {
458         let name_beg = self.generate_name_beginning(&filename);
459         let entry = self.old_tests.entry(name_beg)
460                                   .or_insert(Vec::new());
461         entry.push(test.trim().to_owned());
462     }
463
464     pub fn add_test(&mut self, test: String,
465                     should_panic: bool, no_run: bool, should_ignore: bool,
466                     as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>,
467                     line: usize, filename: String) {
468         let name = self.generate_name(line, &filename);
469         // to be removed when hoedown is removed
470         if self.render_type == RenderType::Pulldown {
471             let name_beg = self.generate_name_beginning(&filename);
472             let mut found = false;
473             let test = test.trim().to_owned();
474             if let Some(entry) = self.old_tests.get_mut(&name_beg) {
475                 found = entry.remove_item(&test).is_some();
476             }
477             if !found {
478                 let _ = writeln!(&mut io::stderr(),
479                                  "WARNING: {} Code block is not currently run as a test, but will \
480                                   in future versions of rustdoc. Please ensure this code block is \
481                                   a runnable test, or use the `ignore` directive.",
482                                  name);
483                 return
484             }
485         }
486         let cfgs = self.cfgs.clone();
487         let libs = self.libs.clone();
488         let externs = self.externs.clone();
489         let cratename = self.cratename.to_string();
490         let opts = self.opts.clone();
491         let maybe_sysroot = self.maybe_sysroot.clone();
492         debug!("Creating test {}: {}", name, test);
493         self.tests.push(testing::TestDescAndFn {
494             desc: testing::TestDesc {
495                 name: testing::DynTestName(name),
496                 ignore: should_ignore,
497                 // compiler failures are test failures
498                 should_panic: testing::ShouldPanic::No,
499             },
500             testfn: testing::DynTestFn(box move |()| {
501                 let panic = io::set_panic(None);
502                 let print = io::set_print(None);
503                 match {
504                     rustc_driver::in_rustc_thread(move || {
505                         io::set_panic(panic);
506                         io::set_print(print);
507                         runtest(&test,
508                                 &cratename,
509                                 cfgs,
510                                 libs,
511                                 externs,
512                                 should_panic,
513                                 no_run,
514                                 as_test_harness,
515                                 compile_fail,
516                                 error_codes,
517                                 &opts,
518                                 maybe_sysroot)
519                     })
520                 } {
521                     Ok(()) => (),
522                     Err(err) => panic::resume_unwind(err),
523                 }
524             }),
525         });
526     }
527
528     pub fn get_line(&self) -> usize {
529         if let Some(ref codemap) = self.codemap {
530             let line = self.position.lo.to_usize();
531             let line = codemap.lookup_char_pos(BytePos(line as u32)).line;
532             if line > 0 { line - 1 } else { line }
533         } else {
534             0
535         }
536     }
537
538     pub fn set_position(&mut self, position: Span) {
539         self.position = position;
540     }
541
542     pub fn get_filename(&self) -> String {
543         if let Some(ref codemap) = self.codemap {
544             let filename = codemap.span_to_filename(self.position);
545             if let Ok(cur_dir) = env::current_dir() {
546                 if let Ok(path) = Path::new(&filename).strip_prefix(&cur_dir) {
547                     if let Some(path) = path.to_str() {
548                         return path.to_owned();
549                     }
550                 }
551             }
552             filename
553         } else if let Some(ref filename) = self.filename {
554             filename.clone()
555         } else {
556             "<input>".to_owned()
557         }
558     }
559
560     pub fn register_header(&mut self, name: &str, level: u32) {
561         if self.use_headers && level == 1 {
562             // we use these headings as test names, so it's good if
563             // they're valid identifiers.
564             let name = name.chars().enumerate().map(|(i, c)| {
565                     if (i == 0 && c.is_xid_start()) ||
566                         (i != 0 && c.is_xid_continue()) {
567                         c
568                     } else {
569                         '_'
570                     }
571                 }).collect::<String>();
572
573             // new header => reset count.
574             self.cnt = 0;
575             self.current_header = Some(name);
576         }
577     }
578 }
579
580 struct HirCollector<'a, 'hir: 'a> {
581     collector: &'a mut Collector,
582     map: &'a hir::map::Map<'hir>
583 }
584
585 impl<'a, 'hir> HirCollector<'a, 'hir> {
586     fn visit_testable<F: FnOnce(&mut Self)>(&mut self,
587                                             name: String,
588                                             attrs: &[ast::Attribute],
589                                             nested: F) {
590         let has_name = !name.is_empty();
591         if has_name {
592             self.collector.names.push(name);
593         }
594
595         let mut attrs = Attributes::from_ast(attrs);
596         attrs.collapse_doc_comments();
597         attrs.unindent_doc_comments();
598         if let Some(doc) = attrs.doc_value() {
599             self.collector.cnt = 0;
600             if self.collector.render_type == RenderType::Pulldown {
601                 markdown::old_find_testable_code(doc, self.collector,
602                                                  attrs.span.unwrap_or(DUMMY_SP));
603                 markdown::find_testable_code(doc, self.collector,
604                                              attrs.span.unwrap_or(DUMMY_SP));
605             } else {
606                 markdown::old_find_testable_code(doc, self.collector,
607                                                  attrs.span.unwrap_or(DUMMY_SP));
608             }
609         }
610
611         nested(self);
612
613         if has_name {
614             self.collector.names.pop();
615         }
616     }
617 }
618
619 impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> {
620     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'hir> {
621         intravisit::NestedVisitorMap::All(&self.map)
622     }
623
624     fn visit_item(&mut self, item: &'hir hir::Item) {
625         let name = if let hir::ItemImpl(.., ref ty, _) = item.node {
626             self.map.node_to_pretty_string(ty.id)
627         } else {
628             item.name.to_string()
629         };
630
631         self.visit_testable(name, &item.attrs, |this| {
632             intravisit::walk_item(this, item);
633         });
634     }
635
636     fn visit_trait_item(&mut self, item: &'hir hir::TraitItem) {
637         self.visit_testable(item.name.to_string(), &item.attrs, |this| {
638             intravisit::walk_trait_item(this, item);
639         });
640     }
641
642     fn visit_impl_item(&mut self, item: &'hir hir::ImplItem) {
643         self.visit_testable(item.name.to_string(), &item.attrs, |this| {
644             intravisit::walk_impl_item(this, item);
645         });
646     }
647
648     fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem) {
649         self.visit_testable(item.name.to_string(), &item.attrs, |this| {
650             intravisit::walk_foreign_item(this, item);
651         });
652     }
653
654     fn visit_variant(&mut self,
655                      v: &'hir hir::Variant,
656                      g: &'hir hir::Generics,
657                      item_id: ast::NodeId) {
658         self.visit_testable(v.node.name.to_string(), &v.node.attrs, |this| {
659             intravisit::walk_variant(this, v, g, item_id);
660         });
661     }
662
663     fn visit_struct_field(&mut self, f: &'hir hir::StructField) {
664         self.visit_testable(f.name.to_string(), &f.attrs, |this| {
665             intravisit::walk_struct_field(this, f);
666         });
667     }
668
669     fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef) {
670         self.visit_testable(macro_def.name.to_string(), &macro_def.attrs, |_| ());
671     }
672 }