]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/test.rs
rustdoc: Suck in all impls from external crates
[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;
12 use std::char;
13 use std::io;
14 use std::io::{Command, TempDir};
15 use std::os;
16 use std::str;
17 use std::string::String;
18 use std::unstable::dynamic_lib::DynamicLibrary;
19
20 use collections::{HashSet, HashMap};
21 use testing;
22 use rustc::back::link;
23 use rustc::driver::config;
24 use rustc::driver::driver;
25 use rustc::driver::session;
26 use rustc::metadata::creader::Loader;
27 use syntax::ast;
28 use syntax::codemap::{CodeMap, dummy_spanned};
29 use syntax::diagnostic;
30 use syntax::parse::token;
31
32 use core;
33 use clean;
34 use clean::Clean;
35 use fold::DocFolder;
36 use html::markdown;
37 use passes;
38 use visit_ast::RustdocVisitor;
39
40 pub fn run(input: &str,
41            cfgs: Vec<String>,
42            libs: HashSet<Path>,
43            mut test_args: Vec<String>)
44            -> int {
45     let input_path = Path::new(input);
46     let input = driver::FileInput(input_path.clone());
47
48     let sessopts = config::Options {
49         maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
50         addl_lib_search_paths: RefCell::new(libs.clone()),
51         crate_types: vec!(config::CrateTypeDylib),
52         ..config::basic_options().clone()
53     };
54
55
56     let codemap = CodeMap::new();
57     let diagnostic_handler = diagnostic::default_handler(diagnostic::Auto);
58     let span_diagnostic_handler =
59     diagnostic::mk_span_handler(diagnostic_handler, codemap);
60
61     let sess = session::build_session_(sessopts,
62                                       Some(input_path.clone()),
63                                       span_diagnostic_handler);
64
65     let mut cfg = config::build_configuration(&sess);
66     cfg.extend(cfgs.move_iter().map(|cfg_| {
67         let cfg_ = token::intern_and_get_ident(cfg_.as_slice());
68         @dummy_spanned(ast::MetaWord(cfg_))
69     }));
70     let krate = driver::phase_1_parse_input(&sess, cfg, &input);
71     let (krate, _) = driver::phase_2_configure_and_expand(&sess, &mut Loader::new(&sess), krate,
72                                                           &from_str("rustdoc-test").unwrap());
73
74     let ctx = @core::DocContext {
75         krate: krate,
76         maybe_typed: core::NotTyped(sess),
77         src: input_path,
78         external_paths: RefCell::new(Some(HashMap::new())),
79         external_traits: RefCell::new(None),
80         external_typarams: RefCell::new(None),
81         inlined: RefCell::new(None),
82         populated_crate_impls: RefCell::new(HashSet::new()),
83     };
84     super::ctxtkey.replace(Some(ctx));
85
86     let mut v = RustdocVisitor::new(ctx, None);
87     v.visit(&ctx.krate);
88     let krate = v.clean();
89     let (krate, _) = passes::unindent_comments(krate);
90     let (krate, _) = passes::collapse_docs(krate);
91
92     let mut collector = Collector::new(krate.name.to_string(),
93                                        libs,
94                                        false,
95                                        false);
96     collector.fold_crate(krate);
97
98     test_args.unshift("rustdoctest".to_string());
99
100     testing::test_main(test_args.as_slice(),
101                        collector.tests.move_iter().collect());
102     0
103 }
104
105 fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
106            no_run: bool, loose_feature_gating: bool) {
107     let test = maketest(test, cratename, loose_feature_gating);
108     let input = driver::StrInput(test.to_string());
109
110     let sessopts = config::Options {
111         maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
112         addl_lib_search_paths: RefCell::new(libs),
113         crate_types: vec!(config::CrateTypeExecutable),
114         output_types: vec!(link::OutputTypeExe),
115         no_trans: no_run,
116         cg: config::CodegenOptions {
117             prefer_dynamic: true,
118             .. config::basic_codegen_options()
119         },
120         ..config::basic_options().clone()
121     };
122
123     // Shuffle around a few input and output handles here. We're going to pass
124     // an explicit handle into rustc to collect output messages, but we also
125     // want to catch the error message that rustc prints when it fails.
126     //
127     // We take our task-local stderr (likely set by the test runner), and move
128     // it into another task. This helper task then acts as a sink for both the
129     // stderr of this task and stderr of rustc itself, copying all the info onto
130     // the stderr channel we originally started with.
131     //
132     // The basic idea is to not use a default_handler() for rustc, and then also
133     // not print things by default to the actual stderr.
134     let (tx, rx) = channel();
135     let w1 = io::ChanWriter::new(tx);
136     let w2 = w1.clone();
137     let old = io::stdio::set_stderr(box w1);
138     spawn(proc() {
139         let mut p = io::ChanReader::new(rx);
140         let mut err = old.unwrap_or(box io::stderr() as Box<Writer:Send>);
141         io::util::copy(&mut p, &mut err).unwrap();
142     });
143     let emitter = diagnostic::EmitterWriter::new(box w2);
144
145     // Compile the code
146     let codemap = CodeMap::new();
147     let diagnostic_handler = diagnostic::mk_handler(box emitter);
148     let span_diagnostic_handler =
149         diagnostic::mk_span_handler(diagnostic_handler, codemap);
150
151     let sess = session::build_session_(sessopts,
152                                       None,
153                                       span_diagnostic_handler);
154
155     let outdir = TempDir::new("rustdoctest").expect("rustdoc needs a tempdir");
156     let out = Some(outdir.path().clone());
157     let cfg = config::build_configuration(&sess);
158     let libdir = sess.target_filesearch().get_lib_path();
159     driver::compile_input(sess, cfg, &input, &out, &None);
160
161     if no_run { return }
162
163     // Run the code!
164     //
165     // We're careful to prepend the *target* dylib search path to the child's
166     // environment to ensure that the target loads the right libraries at
167     // runtime. It would be a sad day if the *host* libraries were loaded as a
168     // mistake.
169     let exe = outdir.path().join("rust_out");
170     let env = {
171         let mut path = DynamicLibrary::search_path();
172         path.insert(0, libdir.clone());
173
174         // Remove the previous dylib search path var
175         let var = DynamicLibrary::envvar();
176         let mut env: Vec<(String,String)> = os::env().move_iter().collect();
177         match env.iter().position(|&(ref k, _)| k.as_slice() == var) {
178             Some(i) => { env.remove(i); }
179             None => {}
180         };
181
182         // Add the new dylib search path var
183         let newpath = DynamicLibrary::create_path(path.as_slice());
184         env.push((var.to_string(),
185                   str::from_utf8(newpath.as_slice()).unwrap().to_string()));
186         env
187     };
188     match Command::new(exe).env(env.as_slice()).output() {
189         Err(e) => fail!("couldn't run the test: {}{}", e,
190                         if e.kind == io::PermissionDenied {
191                             " - maybe your tempdir is mounted with noexec?"
192                         } else { "" }),
193         Ok(out) => {
194             if should_fail && out.status.success() {
195                 fail!("test executable succeeded when it should have failed");
196             } else if !should_fail && !out.status.success() {
197                 fail!("test executable failed:\n{}",
198                       str::from_utf8(out.error.as_slice()));
199             }
200         }
201     }
202 }
203
204 fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> String {
205     let mut prog = String::from_str(r"
206 #![deny(warnings)]
207 #![allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)]
208 ");
209
210     if loose_feature_gating {
211         // FIXME #12773: avoid inserting these when the tutorial & manual
212         // etc. have been updated to not use them so prolifically.
213         prog.push_str("#![feature(macro_rules, globs, struct_variant, managed_boxes) ]\n");
214     }
215
216     if !s.contains("extern crate") {
217         if s.contains(cratename) {
218             prog.push_str(format!("extern crate {};\n",
219                                   cratename).as_slice());
220         }
221     }
222     if s.contains("fn main") {
223         prog.push_str(s);
224     } else {
225         prog.push_str("fn main() {\n");
226         prog.push_str(s);
227         prog.push_str("\n}");
228     }
229
230     return prog
231 }
232
233 pub struct Collector {
234     pub tests: Vec<testing::TestDescAndFn>,
235     names: Vec<String>,
236     libs: HashSet<Path>,
237     cnt: uint,
238     use_headers: bool,
239     current_header: Option<String>,
240     cratename: String,
241
242     loose_feature_gating: bool
243 }
244
245 impl Collector {
246     pub fn new(cratename: String, libs: HashSet<Path>,
247                use_headers: bool, loose_feature_gating: bool) -> Collector {
248         Collector {
249             tests: Vec::new(),
250             names: Vec::new(),
251             libs: libs,
252             cnt: 0,
253             use_headers: use_headers,
254             current_header: None,
255             cratename: cratename,
256
257             loose_feature_gating: loose_feature_gating
258         }
259     }
260
261     pub fn add_test(&mut self, test: String, should_fail: bool, no_run: bool, should_ignore: bool) {
262         let name = if self.use_headers {
263             let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
264             format!("{}_{}", s, self.cnt)
265         } else {
266             format!("{}_{}", self.names.connect("::"), self.cnt)
267         };
268         self.cnt += 1;
269         let libs = self.libs.clone();
270         let cratename = self.cratename.to_string();
271         let loose_feature_gating = self.loose_feature_gating;
272         debug!("Creating test {}: {}", name, test);
273         self.tests.push(testing::TestDescAndFn {
274             desc: testing::TestDesc {
275                 name: testing::DynTestName(name),
276                 ignore: should_ignore,
277                 should_fail: false, // compiler failures are test failures
278             },
279             testfn: testing::DynTestFn(proc() {
280                 runtest(test.as_slice(),
281                         cratename.as_slice(),
282                         libs,
283                         should_fail,
284                         no_run,
285                         loose_feature_gating);
286             }),
287         });
288     }
289
290     pub fn register_header(&mut self, name: &str, level: u32) {
291         if self.use_headers && level == 1 {
292             // we use these headings as test names, so it's good if
293             // they're valid identifiers.
294             let name = name.chars().enumerate().map(|(i, c)| {
295                     if (i == 0 && char::is_XID_start(c)) ||
296                         (i != 0 && char::is_XID_continue(c)) {
297                         c
298                     } else {
299                         '_'
300                     }
301                 }).collect::<String>();
302
303             // new header => reset count.
304             self.cnt = 0;
305             self.current_header = Some(name);
306         }
307     }
308 }
309
310 impl DocFolder for Collector {
311     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
312         let pushed = match item.name {
313             Some(ref name) if name.len() == 0 => false,
314             Some(ref name) => { self.names.push(name.to_string()); true }
315             None => false
316         };
317         match item.doc_value() {
318             Some(doc) => {
319                 self.cnt = 0;
320                 markdown::find_testable_code(doc, &mut *self);
321             }
322             None => {}
323         }
324         let ret = self.fold_item_recur(item);
325         if pushed {
326             self.names.pop();
327         }
328         return ret;
329     }
330 }