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.
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.
11 use std::cell::RefCell;
12 use std::collections::{HashSet, HashMap};
13 use std::dynamic_lib::DynamicLibrary;
15 use std::ffi::OsString;
18 use std::path::PathBuf;
19 use std::process::Command;
21 use std::sync::mpsc::channel;
23 use std::thunk::Thunk;
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;
41 use visit_ast::RustdocVisitor;
43 pub fn run(input: &str,
46 externs: core::Externs,
47 mut test_args: Vec<String>,
48 crate_name: Option<String>)
50 let input_path = PathBuf::new(input);
51 let input = config::Input::File(input_path.clone());
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()
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);
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));
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,
78 .expect("phase_2_configure_and_expand aborted in rustdoc!");
80 let ctx = core::DocContext {
82 maybe_typed: core::NotTyped(sess),
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()),
91 let mut v = RustdocVisitor::new(&ctx, None);
93 let mut krate = v.clean(&ctx);
95 Some(name) => krate.name = name,
98 let (krate, _) = passes::collapse_docs(krate);
99 let (krate, _) = passes::unindent_comments(krate);
101 let mut collector = Collector::new(krate.name.to_string(),
105 collector.fold_crate(krate);
107 test_args.insert(0, "rustdoctest".to_string());
109 testing::test_main(&test_args,
110 collector.tests.into_iter().collect());
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());
123 let sessopts = config::Options {
124 maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap()
125 .parent().unwrap().to_path_buf()),
127 crate_types: vec!(config::CrateTypeExecutable),
128 output_types: vec!(config::OutputTypeExe),
130 cg: config::CodegenOptions {
131 prefer_dynamic: true,
132 .. config::basic_codegen_options()
134 test: as_test_harness,
135 unstable_features: get_unstable_features_setting(),
136 ..config::basic_options().clone()
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.
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.
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);
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 {
158 // Chop off the `Send` bound.
159 let old: Box<Writer> = old;
162 None => box old_io::stderr() as Box<Writer>,
164 old_io::util::copy(&mut p, &mut err).unwrap();
166 let emitter = diagnostic::EmitterWriter::new(box w2, None);
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);
174 let sess = session::build_session_(sessopts,
176 span_diagnostic_handler);
177 rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
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();
185 control.after_analysis.stop = Compilation::Stop;
187 driver::compile_input(sess, cfg, &input, &out, &None, None, control);
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
197 let mut cmd = Command::new(&outdir.path().join("rust-out"));
198 let var = DynamicLibrary::envvar();
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()
205 cmd.env(var, &newpath);
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?"
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));
223 pub fn maketest(s: &str, cratename: Option<&str>, lints: bool, dont_insert_main: bool) -> String {
224 let mut prog = String::new();
227 #![allow(unused_variables, unused_assignments, unused_mut, unused_attributes, dead_code)]
231 // Don't inject `extern crate std` because it's already injected by the
233 if !s.contains("extern crate") && cratename != Some("std") {
236 if s.contains(cratename) {
237 prog.push_str(&format!("extern crate {};\n",
244 if dont_insert_main || s.contains("fn main") {
247 prog.push_str("fn main() {\n ");
248 prog.push_str(&s.replace("\n", "\n "));
249 prog.push_str("\n}");
255 pub struct Collector {
256 pub tests: Vec<testing::TestDescAndFn>,
259 externs: core::Externs,
262 current_header: Option<String>,
267 pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs,
268 use_headers: bool) -> Collector {
275 use_headers: use_headers,
276 current_header: None,
277 cratename: cratename,
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)
287 format!("{}_{}", self.names.connect("::"), self.cnt)
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
300 testfn: testing::DynTestFn(Thunk::new(move|| {
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()) {
323 }).collect::<String>();
325 // new header => reset count.
327 self.current_header = Some(name);
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 }
339 match item.doc_value() {
342 markdown::find_testable_code(doc, &mut *self);
346 let ret = self.fold_item_recur(item);