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