]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/test.rs
0a472a0ccaf2fe88970cc0eb33e207a3bb24c483
[rust.git] / src / librustdoc / test.rs
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.
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::char;
13 use std::io;
14 use std::io::{Process, TempDir};
15 use std::local_data;
16 use std::os;
17 use std::str;
18
19 use collections::HashSet;
20 use testing;
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;
27
28 use core;
29 use clean;
30 use clean::Clean;
31 use fold::DocFolder;
32 use html::markdown;
33 use passes;
34 use visit_ast::RustdocVisitor;
35
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());
39
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()
45     };
46
47
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);
52
53     let sess = driver::build_session_(sessopts,
54                                       Some(input_path),
55                                       span_diagnostic_handler);
56
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());
61
62     let ctx = @core::DocContext {
63         krate: krate,
64         maybe_typed: core::NotTyped(sess),
65     };
66     local_data::set(super::ctxtkey, ctx);
67
68     let mut v = RustdocVisitor::new(ctx, None);
69     v.visit(&ctx.krate);
70     let krate = v.clean();
71     let (krate, _) = passes::unindent_comments(krate);
72     let (krate, _) = passes::collapse_docs(krate);
73
74     let mut collector = Collector::new(krate.name.to_owned(), libs, false, false);
75     collector.fold_crate(krate);
76
77     test_args.unshift(~"rustdoctest");
78
79     testing::test_main(test_args, collector.tests);
80
81     0
82 }
83
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);
88
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 {
95             prefer_dynamic: true,
96             .. session::basic_codegen_options()
97         },
98         ..session::basic_options().clone()
99     };
100
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.
104     //
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.
109     //
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);
114     let w2 = w1.clone();
115     let old = io::stdio::set_stderr(~w1);
116     spawn(proc() {
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();
120     });
121     let emitter = diagnostic::EmitterWriter::new(~w2);
122
123     // Compile the code
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);
128
129     let sess = driver::build_session_(sessopts,
130                                       None,
131                                       span_diagnostic_handler);
132
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);
137
138     if no_run { return }
139
140     // Run the code!
141     let exe = outdir.path().join("rust_out");
142     let out = Process::output(exe.as_str().unwrap(), []);
143     match out {
144         Err(e) => fail!("couldn't run the test: {}{}", e,
145                         if e.kind == io::PermissionDenied {
146                             " - maybe your tempdir is mounted with noexec?"
147                         } else { "" }),
148         Ok(out) => {
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));
153             }
154         }
155     }
156 }
157
158 fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> ~str {
159     let mut prog = ~r"
160 #[deny(warnings)];
161 #[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
162
163 // FIXME: remove when ~[] disappears from tests.
164 #[allow(deprecated_owned_vector)];
165 ";
166
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");
171     }
172
173     if !s.contains("extern crate") {
174         if s.contains(cratename) {
175             prog.push_str(format!("extern crate {};\n", cratename));
176         }
177     }
178     if s.contains("fn main") {
179         prog.push_str(s);
180     } else {
181         prog.push_str("fn main() {\n");
182         prog.push_str(s);
183         prog.push_str("\n}");
184     }
185
186     return prog;
187 }
188
189 pub struct Collector {
190     tests: ~[testing::TestDescAndFn],
191     priv names: ~[~str],
192     priv libs: HashSet<Path>,
193     priv cnt: uint,
194     priv use_headers: bool,
195     priv current_header: Option<~str>,
196     priv cratename: ~str,
197
198     priv loose_feature_gating: bool
199 }
200
201 impl Collector {
202     pub fn new(cratename: ~str, libs: HashSet<Path>,
203                use_headers: bool, loose_feature_gating: bool) -> Collector {
204         Collector {
205             tests: ~[],
206             names: ~[],
207             libs: libs,
208             cnt: 0,
209             use_headers: use_headers,
210             current_header: None,
211             cratename: cratename,
212
213             loose_feature_gating: loose_feature_gating
214         }
215     }
216
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)
221         } else {
222             format!("{}_{}", self.names.connect("::"), self.cnt)
223         };
224         self.cnt += 1;
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),
232                 ignore: false,
233                 should_fail: false, // compiler failures are test failures
234             },
235             testfn: testing::DynTestFn(proc() {
236                 runtest(test, cratename, libs, should_fail, no_run, loose_feature_gating);
237             }),
238         });
239     }
240
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)) {
248                         c
249                     } else {
250                         '_'
251                     }
252                 }).collect::<~str>();
253
254             // new header => reset count.
255             self.cnt = 0;
256             self.current_header = Some(name);
257         }
258     }
259 }
260
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 }
266             None => false
267         };
268         match item.doc_value() {
269             Some(doc) => {
270                 self.cnt = 0;
271                 markdown::find_testable_code(doc, &mut *self);
272             }
273             None => {}
274         }
275         let ret = self.fold_item_recur(item);
276         if pushed {
277             self.names.pop();
278         }
279         return ret;
280     }
281 }