]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/test.rs
std: Clean out deprecated APIs
[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::cell::{RefCell, Cell};
12 use std::collections::HashMap;
13 use std::env;
14 use std::ffi::OsString;
15 use std::io::prelude::*;
16 use std::io;
17 use std::path::PathBuf;
18 use std::panic::{self, AssertRecoverSafe};
19 use std::process::Command;
20 use std::rc::Rc;
21 use std::str;
22 use std::sync::{Arc, Mutex};
23
24 use testing;
25 use rustc_lint;
26 use rustc::dep_graph::DepGraph;
27 use rustc::front::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_front::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;
37 use syntax::errors;
38 use syntax::errors::emitter::ColorConfig;
39 use syntax::parse::token;
40
41 use core;
42 use clean;
43 use clean::Clean;
44 use fold::DocFolder;
45 use html::markdown;
46 use passes;
47 use visit_ast::RustdocVisitor;
48
49 #[derive(Clone, Default)]
50 pub struct TestOptions {
51     pub no_crate_inject: bool,
52     pub attrs: Vec<String>,
53 }
54
55 pub fn run(input: &str,
56            cfgs: Vec<String>,
57            libs: SearchPaths,
58            externs: core::Externs,
59            mut test_args: Vec<String>,
60            crate_name: Option<String>)
61            -> isize {
62     let input_path = PathBuf::from(input);
63     let input = config::Input::File(input_path.clone());
64
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()
73     };
74
75     let codemap = Rc::new(CodeMap::new());
76     let diagnostic_handler = errors::Handler::with_tty_emitter(ColorConfig::Auto,
77                                                                None,
78                                                                true,
79                                                                false,
80                                                                codemap.clone());
81
82     let cstore = Rc::new(CStore::new(token::get_ident_interner()));
83     let sess = session::build_session_(sessopts,
84                                        Some(input_path.clone()),
85                                        diagnostic_handler,
86                                        codemap,
87                                        cstore.clone());
88     rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
89
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,
94                                                      "rustdoc-test", None)
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);
99
100     let opts = scrape_test_config(&krate);
101
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);
106
107     let ctx = core::DocContext {
108         map: &map,
109         maybe_typed: core::NotTyped(&sess),
110         input: input,
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),
117     };
118
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 {
123         krate.name = name;
124     }
125     let (krate, _) = passes::collapse_docs(krate);
126     let (krate, _) = passes::unindent_comments(krate);
127
128     let mut collector = Collector::new(krate.name.to_string(),
129                                        cfgs,
130                                        libs,
131                                        externs,
132                                        false,
133                                        opts);
134     collector.fold_crate(krate);
135
136     test_args.insert(0, "rustdoctest".to_string());
137
138     testing::test_main(&test_args,
139                        collector.tests.into_iter().collect());
140     0
141 }
142
143 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
144 fn scrape_test_config(krate: &::rustc_front::hir::Crate) -> TestOptions {
145     use syntax::attr::AttrMetaMethods;
146     use syntax::print::pprust;
147
148     let mut opts = TestOptions {
149         no_crate_inject: false,
150         attrs: Vec::new(),
151     };
152
153     let attrs = krate.attrs.iter()
154                      .filter(|a| a.check_name("doc"))
155                      .filter_map(|a| a.meta_item_list())
156                      .flat_map(|l| l)
157                      .filter(|a| a.check_name("test"))
158                      .filter_map(|a| a.meta_item_list())
159                      .flat_map(|l| l);
160     for attr in attrs {
161         if attr.check_name("no_crate_inject") {
162             opts.no_crate_inject = true;
163         }
164         if attr.check_name("attr") {
165             if let Some(l) = attr.meta_item_list() {
166                 for item in l {
167                     opts.attrs.push(pprust::meta_item_to_string(item));
168                 }
169             }
170         }
171     }
172
173     return opts;
174 }
175
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(test.to_string());
184     let mut outputs = HashMap::new();
185     outputs.insert(OutputType::Exe, None);
186
187     let sessopts = config::Options {
188         maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap()
189                                               .parent().unwrap().to_path_buf()),
190         search_paths: libs,
191         crate_types: vec!(config::CrateTypeExecutable),
192         output_types: outputs,
193         externs: externs,
194         cg: config::CodegenOptions {
195             prefer_dynamic: true,
196             .. config::basic_codegen_options()
197         },
198         test: as_test_harness,
199         unstable_features: get_unstable_features_setting(),
200         ..config::basic_options().clone()
201     };
202
203     // Shuffle around a few input and output handles here. We're going to pass
204     // an explicit handle into rustc to collect output messages, but we also
205     // want to catch the error message that rustc prints when it fails.
206     //
207     // We take our thread-local stderr (likely set by the test runner) and replace
208     // it with a sink that is also passed to rustc itself. When this function
209     // returns the output of the sink is copied onto the output of our own thread.
210     //
211     // The basic idea is to not use a default Handler for rustc, and then also
212     // not print things by default to the actual stderr.
213     struct Sink(Arc<Mutex<Vec<u8>>>);
214     impl Write for Sink {
215         fn write(&mut self, data: &[u8]) -> io::Result<usize> {
216             Write::write(&mut *self.0.lock().unwrap(), data)
217         }
218         fn flush(&mut self) -> io::Result<()> { Ok(()) }
219     }
220     struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>);
221     impl Drop for Bomb {
222         fn drop(&mut self) {
223             let _ = self.1.write_all(&self.0.lock().unwrap());
224         }
225     }
226     let data = Arc::new(Mutex::new(Vec::new()));
227     let codemap = Rc::new(CodeMap::new());
228     let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
229                                                       None,
230                                                       codemap.clone());
231     let old = io::set_panic(box Sink(data.clone()));
232     let _bomb = Bomb(data, old.unwrap_or(box io::stdout()));
233
234     // Compile the code
235     let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
236
237     let cstore = Rc::new(CStore::new(token::get_ident_interner()));
238     let sess = session::build_session_(sessopts,
239                                        None,
240                                        diagnostic_handler,
241                                        codemap,
242                                        cstore.clone());
243     rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
244
245     let outdir = Mutex::new(TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
246     let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
247     let mut control = driver::CompileController::basic();
248     let mut cfg = config::build_configuration(&sess);
249     cfg.extend(config::parse_cfgspecs(cfgs.clone()));
250     let out = Some(outdir.lock().unwrap().path().to_path_buf());
251
252     if no_run {
253         control.after_analysis.stop = Compilation::Stop;
254     }
255
256     match {
257         let b_sess = AssertRecoverSafe::new(&sess);
258         let b_cstore = AssertRecoverSafe::new(&cstore);
259         let b_cfg = AssertRecoverSafe::new(cfg.clone());
260         let b_control = AssertRecoverSafe::new(&control);
261
262         panic::recover(|| {
263             driver::compile_input(&b_sess, &b_cstore, (*b_cfg).clone(),
264                                   &input, &out,
265                                   &None, None, &b_control)
266         })
267     } {
268         Ok(r) => {
269             match r {
270                 Err(count) if count > 0 && compile_fail == false => {
271                     sess.fatal("aborting due to previous error(s)")
272                 }
273                 Ok(()) if compile_fail => panic!("test compiled while it wasn't supposed to"),
274                 _ => {}
275             }
276         }
277         Err(_) if compile_fail == false => panic!("couldn't compile the test"),
278         _ => {}
279     }
280
281     if no_run { return }
282
283     // Run the code!
284     //
285     // We're careful to prepend the *target* dylib search path to the child's
286     // environment to ensure that the target loads the right libraries at
287     // runtime. It would be a sad day if the *host* libraries were loaded as a
288     // mistake.
289     let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
290     let var = DynamicLibrary::envvar();
291     let newpath = {
292         let path = env::var_os(var).unwrap_or(OsString::new());
293         let mut path = env::split_paths(&path).collect::<Vec<_>>();
294         path.insert(0, libdir.clone());
295         env::join_paths(path).unwrap()
296     };
297     cmd.env(var, &newpath);
298
299     match cmd.output() {
300         Err(e) => panic!("couldn't run the test: {}{}", e,
301                         if e.kind() == io::ErrorKind::PermissionDenied {
302                             " - maybe your tempdir is mounted with noexec?"
303                         } else { "" }),
304         Ok(out) => {
305             if should_panic && out.status.success() {
306                 panic!("test executable succeeded when it should have failed");
307             } else if !should_panic && !out.status.success() {
308                 panic!("test executable failed:\n{}\n{}",
309                        str::from_utf8(&out.stdout).unwrap_or(""),
310                        str::from_utf8(&out.stderr).unwrap_or(""));
311             }
312         }
313     }
314 }
315
316 pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
317                 opts: &TestOptions) -> String {
318     let (crate_attrs, everything_else) = partition_source(s);
319
320     let mut prog = String::new();
321
322     // First push any outer attributes from the example, assuming they
323     // are intended to be crate attributes.
324     prog.push_str(&crate_attrs);
325
326     // Next, any attributes for other aspects such as lints.
327     for attr in &opts.attrs {
328         prog.push_str(&format!("#![{}]\n", attr));
329     }
330
331     // Don't inject `extern crate std` because it's already injected by the
332     // compiler.
333     if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
334         if let Some(cratename) = cratename {
335             if s.contains(cratename) {
336                 prog.push_str(&format!("extern crate {};\n", cratename));
337             }
338         }
339     }
340     if dont_insert_main || s.contains("fn main") {
341         prog.push_str(&everything_else);
342     } else {
343         prog.push_str("fn main() {\n    ");
344         prog.push_str(&everything_else.replace("\n", "\n    "));
345         prog = prog.trim().into();
346         prog.push_str("\n}");
347     }
348
349     info!("final test program: {}", prog);
350
351     return prog
352 }
353
354 fn partition_source(s: &str) -> (String, String) {
355     use rustc_unicode::str::UnicodeStr;
356
357     let mut after_header = false;
358     let mut before = String::new();
359     let mut after = String::new();
360
361     for line in s.lines() {
362         let trimline = line.trim();
363         let header = trimline.is_whitespace() ||
364             trimline.starts_with("#![feature");
365         if !header || after_header {
366             after_header = true;
367             after.push_str(line);
368             after.push_str("\n");
369         } else {
370             before.push_str(line);
371             before.push_str("\n");
372         }
373     }
374
375     return (before, after);
376 }
377
378 pub struct Collector {
379     pub tests: Vec<testing::TestDescAndFn>,
380     names: Vec<String>,
381     cfgs: Vec<String>,
382     libs: SearchPaths,
383     externs: core::Externs,
384     cnt: usize,
385     use_headers: bool,
386     current_header: Option<String>,
387     cratename: String,
388     opts: TestOptions,
389 }
390
391 impl Collector {
392     pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: core::Externs,
393                use_headers: bool, opts: TestOptions) -> Collector {
394         Collector {
395             tests: Vec::new(),
396             names: Vec::new(),
397             cfgs: cfgs,
398             libs: libs,
399             externs: externs,
400             cnt: 0,
401             use_headers: use_headers,
402             current_header: None,
403             cratename: cratename,
404             opts: opts,
405         }
406     }
407
408     pub fn add_test(&mut self, test: String,
409                     should_panic: bool, no_run: bool, should_ignore: bool,
410                     as_test_harness: bool, compile_fail: bool) {
411         let name = if self.use_headers {
412             let s = self.current_header.as_ref().map(|s| &**s).unwrap_or("");
413             format!("{}_{}", s, self.cnt)
414         } else {
415             format!("{}_{}", self.names.join("::"), self.cnt)
416         };
417         self.cnt += 1;
418         let cfgs = self.cfgs.clone();
419         let libs = self.libs.clone();
420         let externs = self.externs.clone();
421         let cratename = self.cratename.to_string();
422         let opts = self.opts.clone();
423         debug!("Creating test {}: {}", name, test);
424         self.tests.push(testing::TestDescAndFn {
425             desc: testing::TestDesc {
426                 name: testing::DynTestName(name),
427                 ignore: should_ignore,
428                 // compiler failures are test failures
429                 should_panic: testing::ShouldPanic::No,
430             },
431             testfn: testing::DynTestFn(Box::new(move|| {
432                 runtest(&test,
433                         &cratename,
434                         cfgs,
435                         libs,
436                         externs,
437                         should_panic,
438                         no_run,
439                         as_test_harness,
440                         compile_fail,
441                         &opts);
442             }))
443         });
444     }
445
446     pub fn register_header(&mut self, name: &str, level: u32) {
447         if self.use_headers && level == 1 {
448             // we use these headings as test names, so it's good if
449             // they're valid identifiers.
450             let name = name.chars().enumerate().map(|(i, c)| {
451                     if (i == 0 && c.is_xid_start()) ||
452                         (i != 0 && c.is_xid_continue()) {
453                         c
454                     } else {
455                         '_'
456                     }
457                 }).collect::<String>();
458
459             // new header => reset count.
460             self.cnt = 0;
461             self.current_header = Some(name);
462         }
463     }
464 }
465
466 impl DocFolder for Collector {
467     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
468         let current_name = match item.name {
469             Some(ref name) if !name.is_empty() => Some(name.clone()),
470             _ => typename_if_impl(&item)
471         };
472
473         let pushed = current_name.map(|name| self.names.push(name)).is_some();
474
475         if let Some(doc) = item.doc_value() {
476             self.cnt = 0;
477             markdown::find_testable_code(doc, &mut *self);
478         }
479
480         let ret = self.fold_item_recur(item);
481         if pushed {
482             self.names.pop();
483         }
484
485         return ret;
486
487         // FIXME: it would be better to not have the escaped version in the first place
488         fn unescape_for_testname(mut s: String) -> String {
489             // for refs `&foo`
490             if s.contains("&amp;") {
491                 s = s.replace("&amp;", "&");
492
493                 // `::&'a mut Foo::` looks weird, let's make it `::<&'a mut Foo>`::
494                 if let Some('&') = s.chars().nth(0) {
495                     s = format!("<{}>", s);
496                 }
497             }
498
499             // either `<..>` or `->`
500             if s.contains("&gt;") {
501                 s.replace("&gt;", ">")
502                  .replace("&lt;", "<")
503             } else {
504                 s
505             }
506         }
507
508         fn typename_if_impl(item: &clean::Item) -> Option<String> {
509             if let clean::ItemEnum::ImplItem(ref impl_) = item.inner {
510                 let path = impl_.for_.to_string();
511                 let unescaped_path = unescape_for_testname(path);
512                 Some(unescaped_path)
513             } else {
514                 None
515             }
516         }
517     }
518 }