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