1 // Copyright 2013 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;
14 use std::io::{Process, TempDir};
19 use collections::HashSet;
21 use rustc::back::link;
22 use rustc::driver::driver;
23 use rustc::driver::session;
24 use rustc::metadata::creader::Loader;
25 use syntax::diagnostic;
26 use syntax::codemap::CodeMap;
34 use visit_ast::RustdocVisitor;
36 pub fn run(input: &str, libs: HashSet<Path>, mut test_args: ~[~str]) -> int {
37 let input_path = Path::new(input);
38 let input = driver::FileInput(input_path.clone());
40 let sessopts = session::Options {
41 maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
42 addl_lib_search_paths: RefCell::new(libs.clone()),
43 crate_types: vec!(session::CrateTypeDylib),
44 ..session::basic_options().clone()
48 let codemap = CodeMap::new();
49 let diagnostic_handler = diagnostic::default_handler();
50 let span_diagnostic_handler =
51 diagnostic::mk_span_handler(diagnostic_handler, codemap);
53 let sess = driver::build_session_(sessopts,
55 span_diagnostic_handler);
57 let cfg = driver::build_configuration(&sess);
58 let krate = driver::phase_1_parse_input(&sess, cfg, &input);
59 let (krate, _) = driver::phase_2_configure_and_expand(&sess, &mut Loader::new(&sess), krate,
60 &from_str("rustdoc-test").unwrap());
62 let ctx = @core::DocContext {
64 maybe_typed: core::NotTyped(sess),
66 local_data::set(super::ctxtkey, ctx);
68 let mut v = RustdocVisitor::new(ctx, None);
70 let krate = v.clean();
71 let (krate, _) = passes::unindent_comments(krate);
72 let (krate, _) = passes::collapse_docs(krate);
74 let mut collector = Collector::new(krate.name.to_owned(), libs, false, false);
75 collector.fold_crate(krate);
77 test_args.unshift(~"rustdoctest");
79 testing::test_main(test_args, collector.tests);
84 fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
85 no_run: bool, loose_feature_gating: bool) {
86 let test = maketest(test, cratename, loose_feature_gating);
87 let input = driver::StrInput(test);
89 let sessopts = session::Options {
90 maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
91 addl_lib_search_paths: RefCell::new(libs),
92 crate_types: vec!(session::CrateTypeExecutable),
93 output_types: vec!(link::OutputTypeExe),
94 cg: session::CodegenOptions {
96 .. session::basic_codegen_options()
98 ..session::basic_options().clone()
101 // Shuffle around a few input and output handles here. We're going to pass
102 // an explicit handle into rustc to collect output messages, but we also
103 // want to catch the error message that rustc prints when it fails.
105 // We take our task-local stderr (likely set by the test runner), and move
106 // it into another task. This helper task then acts as a sink for both the
107 // stderr of this task and stderr of rustc itself, copying all the info onto
108 // the stderr channel we originally started with.
110 // The basic idea is to not use a default_handler() for rustc, and then also
111 // not print things by default to the actual stderr.
112 let (tx, rx) = channel();
113 let w1 = io::ChanWriter::new(tx);
115 let old = io::stdio::set_stderr(~w1);
117 let mut p = io::ChanReader::new(rx);
118 let mut err = old.unwrap_or(~io::stderr() as ~Writer);
119 io::util::copy(&mut p, &mut err).unwrap();
121 let emitter = diagnostic::EmitterWriter::new(~w2);
124 let codemap = CodeMap::new();
125 let diagnostic_handler = diagnostic::mk_handler(~emitter);
126 let span_diagnostic_handler =
127 diagnostic::mk_span_handler(diagnostic_handler, codemap);
129 let sess = driver::build_session_(sessopts,
131 span_diagnostic_handler);
133 let outdir = TempDir::new("rustdoctest").expect("rustdoc needs a tempdir");
134 let out = Some(outdir.path().clone());
135 let cfg = driver::build_configuration(&sess);
136 driver::compile_input(sess, cfg, &input, &out, &None);
141 let exe = outdir.path().join("rust_out");
142 let out = Process::output(exe.as_str().unwrap(), []);
144 Err(e) => fail!("couldn't run the test: {}{}", e,
145 if e.kind == io::PermissionDenied {
146 " - maybe your tempdir is mounted with noexec?"
149 if should_fail && out.status.success() {
150 fail!("test executable succeeded when it should have failed");
151 } else if !should_fail && !out.status.success() {
152 fail!("test executable failed:\n{}", str::from_utf8(out.error));
158 fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> ~str {
161 #[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
163 // FIXME: remove when ~[] disappears from tests.
164 #[allow(deprecated_owned_vector)];
167 if loose_feature_gating {
168 // FIXME #12773: avoid inserting these when the tutorial & manual
169 // etc. have been updated to not use them so prolifically.
170 prog.push_str("#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n");
173 if !s.contains("extern crate") {
174 if s.contains(cratename) {
175 prog.push_str(format!("extern crate {};\n", cratename));
178 if s.contains("fn main") {
181 prog.push_str("fn main() {\n");
183 prog.push_str("\n}");
189 pub struct Collector {
190 tests: ~[testing::TestDescAndFn],
192 priv libs: HashSet<Path>,
194 priv use_headers: bool,
195 priv current_header: Option<~str>,
196 priv cratename: ~str,
198 priv loose_feature_gating: bool
202 pub fn new(cratename: ~str, libs: HashSet<Path>,
203 use_headers: bool, loose_feature_gating: bool) -> Collector {
209 use_headers: use_headers,
210 current_header: None,
211 cratename: cratename,
213 loose_feature_gating: loose_feature_gating
217 pub fn add_test(&mut self, test: ~str, should_fail: bool, no_run: bool) {
218 let name = if self.use_headers {
219 let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
220 format!("{}_{}", s, self.cnt)
222 format!("{}_{}", self.names.connect("::"), self.cnt)
225 let libs = self.libs.clone();
226 let cratename = self.cratename.to_owned();
227 let loose_feature_gating = self.loose_feature_gating;
228 debug!("Creating test {}: {}", name, test);
229 self.tests.push(testing::TestDescAndFn {
230 desc: testing::TestDesc {
231 name: testing::DynTestName(name),
233 should_fail: false, // compiler failures are test failures
235 testfn: testing::DynTestFn(proc() {
236 runtest(test, cratename, libs, should_fail, no_run, loose_feature_gating);
241 pub fn register_header(&mut self, name: &str, level: u32) {
242 if self.use_headers && level == 1 {
243 // we use these headings as test names, so it's good if
244 // they're valid identifiers.
245 let name = name.chars().enumerate().map(|(i, c)| {
246 if (i == 0 && char::is_XID_start(c)) ||
247 (i != 0 && char::is_XID_continue(c)) {
252 }).collect::<~str>();
254 // new header => reset count.
256 self.current_header = Some(name);
261 impl DocFolder for Collector {
262 fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
263 let pushed = match item.name {
264 Some(ref name) if name.len() == 0 => false,
265 Some(ref name) => { self.names.push(name.to_owned()); true }
268 match item.doc_value() {
271 markdown::find_testable_code(doc, &mut *self);
275 let ret = self.fold_item_recur(item);