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