(index<- ) ./librustdoc/test.rs
git branch: * master 5200215 auto merge of #14035 : alexcrichton/rust/experimental, r=huonw
modified: Fri May 9 13:02:28 2014
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
11 use std::cell::RefCell;
12 use std::char;
13 use std::io;
14 use std::io::{Process, TempDir};
15 use std::os;
16 use std::str;
17 use std::strbuf::StrBuf;
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::ast;
26 use syntax::codemap::{CodeMap, dummy_spanned};
27 use syntax::diagnostic;
28 use syntax::parse::token;
29
30 use core;
31 use clean;
32 use clean::Clean;
33 use fold::DocFolder;
34 use html::markdown;
35 use passes;
36 use visit_ast::RustdocVisitor;
37
38 pub fn run(input: &str,
39 cfgs: Vec<~str>,
40 libs: HashSet<Path>,
41 mut test_args: Vec<~str>)
42 -> int {
43 let input_path = Path::new(input);
44 let input = driver::FileInput(input_path.clone());
45
46 let sessopts = session::Options {
47 maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
48 addl_lib_search_paths: RefCell::new(libs.clone()),
49 crate_types: vec!(session::CrateTypeDylib),
50 ..session::basic_options().clone()
51 };
52
53
54 let codemap = CodeMap::new();
55 let diagnostic_handler = diagnostic::default_handler();
56 let span_diagnostic_handler =
57 diagnostic::mk_span_handler(diagnostic_handler, codemap);
58
59 let sess = driver::build_session_(sessopts,
60 Some(input_path.clone()),
61 span_diagnostic_handler);
62
63 let mut cfg = driver::build_configuration(&sess);
64 cfg.extend(cfgs.move_iter().map(|cfg_| {
65 let cfg_ = token::intern_and_get_ident(cfg_);
66 @dummy_spanned(ast::MetaWord(cfg_))
67 }));
68 let krate = driver::phase_1_parse_input(&sess, cfg, &input);
69 let (krate, _) = driver::phase_2_configure_and_expand(&sess, &mut Loader::new(&sess), krate,
70 &from_str("rustdoc-test").unwrap());
71
72 let ctx = @core::DocContext {
73 krate: krate,
74 maybe_typed: core::NotTyped(sess),
75 src: input_path,
76 };
77 super::ctxtkey.replace(Some(ctx));
78
79 let mut v = RustdocVisitor::new(ctx, None);
80 v.visit(&ctx.krate);
81 let krate = v.clean();
82 let (krate, _) = passes::unindent_comments(krate);
83 let (krate, _) = passes::collapse_docs(krate);
84
85 let mut collector = Collector::new(krate.name.to_owned(),
86 libs,
87 false,
88 false);
89 collector.fold_crate(krate);
90
91 test_args.unshift("rustdoctest".to_owned());
92
93 testing::test_main(test_args.as_slice(),
94 collector.tests.move_iter().collect());
95 0
96 }
97
98 fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
99 no_run: bool, loose_feature_gating: bool) {
100 let test = maketest(test, cratename, loose_feature_gating);
101 let input = driver::StrInput(test);
102
103 let sessopts = session::Options {
104 maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()),
105 addl_lib_search_paths: RefCell::new(libs),
106 crate_types: vec!(session::CrateTypeExecutable),
107 output_types: vec!(link::OutputTypeExe),
108 no_trans: no_run,
109 cg: session::CodegenOptions {
110 prefer_dynamic: true,
111 .. session::basic_codegen_options()
112 },
113 ..session::basic_options().clone()
114 };
115
116 // Shuffle around a few input and output handles here. We're going to pass
117 // an explicit handle into rustc to collect output messages, but we also
118 // want to catch the error message that rustc prints when it fails.
119 //
120 // We take our task-local stderr (likely set by the test runner), and move
121 // it into another task. This helper task then acts as a sink for both the
122 // stderr of this task and stderr of rustc itself, copying all the info onto
123 // the stderr channel we originally started with.
124 //
125 // The basic idea is to not use a default_handler() for rustc, and then also
126 // not print things by default to the actual stderr.
127 let (tx, rx) = channel();
128 let w1 = io::ChanWriter::new(tx);
129 let w2 = w1.clone();
130 let old = io::stdio::set_stderr(box w1);
131 spawn(proc() {
132 let mut p = io::ChanReader::new(rx);
133 let mut err = old.unwrap_or(box io::stderr() as Box<Writer:Send>);
134 io::util::copy(&mut p, &mut err).unwrap();
135 });
136 let emitter = diagnostic::EmitterWriter::new(box w2);
137
138 // Compile the code
139 let codemap = CodeMap::new();
140 let diagnostic_handler = diagnostic::mk_handler(box emitter);
141 let span_diagnostic_handler =
142 diagnostic::mk_span_handler(diagnostic_handler, codemap);
143
144 let sess = driver::build_session_(sessopts,
145 None,
146 span_diagnostic_handler);
147
148 let outdir = TempDir::new("rustdoctest").expect("rustdoc needs a tempdir");
149 let out = Some(outdir.path().clone());
150 let cfg = driver::build_configuration(&sess);
151 driver::compile_input(sess, cfg, &input, &out, &None);
152
153 if no_run { return }
154
155 // Run the code!
156 let exe = outdir.path().join("rust_out");
157 let out = Process::output(exe.as_str().unwrap(), []);
158 match out {
159 Err(e) => fail!("couldn't run the test: {}{}", e,
160 if e.kind == io::PermissionDenied {
161 " - maybe your tempdir is mounted with noexec?"
162 } else { "" }),
163 Ok(out) => {
164 if should_fail && out.status.success() {
165 fail!("test executable succeeded when it should have failed");
166 } else if !should_fail && !out.status.success() {
167 fail!("test executable failed:\n{}",
168 str::from_utf8(out.error.as_slice()));
169 }
170 }
171 }
172 }
173
174 fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> ~str {
175 let mut prog = StrBuf::from_str(r"
176 #![deny(warnings)]
177 #![allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)]
178 ");
179
180 if loose_feature_gating {
181 // FIXME #12773: avoid inserting these when the tutorial & manual
182 // etc. have been updated to not use them so prolifically.
183 prog.push_str("#![feature(macro_rules, globs, struct_variant, managed_boxes) ]\n");
184 }
185
186 if !s.contains("extern crate") {
187 if s.contains(cratename) {
188 prog.push_str(format!("extern crate {};\n", cratename));
189 }
190 }
191 if s.contains("fn main") {
192 prog.push_str(s);
193 } else {
194 prog.push_str("fn main() {\n");
195 prog.push_str(s);
196 prog.push_str("\n}");
197 }
198
199 return prog.into_owned();
200 }
201
202 pub struct Collector {
203 pub tests: Vec<testing::TestDescAndFn>,
204 names: Vec<~str>,
205 libs: HashSet<Path>,
206 cnt: uint,
207 use_headers: bool,
208 current_header: Option<~str>,
209 cratename: ~str,
210
211 loose_feature_gating: bool
212 }
213
214 impl Collector {
215 pub fn new(cratename: ~str, libs: HashSet<Path>,
216 use_headers: bool, loose_feature_gating: bool) -> Collector {
217 Collector {
218 tests: Vec::new(),
219 names: Vec::new(),
220 libs: libs,
221 cnt: 0,
222 use_headers: use_headers,
223 current_header: None,
224 cratename: cratename,
225
226 loose_feature_gating: loose_feature_gating
227 }
228 }
229
230 pub fn add_test(&mut self, test: ~str, should_fail: bool, no_run: bool, should_ignore: bool) {
231 let name = if self.use_headers {
232 let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
233 format!("{}_{}", s, self.cnt)
234 } else {
235 format!("{}_{}", self.names.connect("::"), self.cnt)
236 };
237 self.cnt += 1;
238 let libs = self.libs.clone();
239 let cratename = self.cratename.to_owned();
240 let loose_feature_gating = self.loose_feature_gating;
241 debug!("Creating test {}: {}", name, test);
242 self.tests.push(testing::TestDescAndFn {
243 desc: testing::TestDesc {
244 name: testing::DynTestName(name),
245 ignore: should_ignore,
246 should_fail: false, // compiler failures are test failures
247 },
248 testfn: testing::DynTestFn(proc() {
249 runtest(test, cratename, libs, should_fail, no_run, loose_feature_gating);
250 }),
251 });
252 }
253
254 pub fn register_header(&mut self, name: &str, level: u32) {
255 if self.use_headers && level == 1 {
256 // we use these headings as test names, so it's good if
257 // they're valid identifiers.
258 let name = name.chars().enumerate().map(|(i, c)| {
259 if (i == 0 && char::is_XID_start(c)) ||
260 (i != 0 && char::is_XID_continue(c)) {
261 c
262 } else {
263 '_'
264 }
265 }).collect::<~str>();
266
267 // new header => reset count.
268 self.cnt = 0;
269 self.current_header = Some(name);
270 }
271 }
272 }
273
274 impl DocFolder for Collector {
275 fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
276 let pushed = match item.name {
277 Some(ref name) if name.len() == 0 => false,
278 Some(ref name) => { self.names.push(name.to_owned()); true }
279 None => false
280 };
281 match item.doc_value() {
282 Some(doc) => {
283 self.cnt = 0;
284 markdown::find_testable_code(doc, &mut *self);
285 }
286 None => {}
287 }
288 let ret = self.fold_item_recur(item);
289 if pushed {
290 self.names.pop();
291 }
292 return ret;
293 }
294 }