]>
Commit | Line | Data |
---|---|---|
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 | ||
d9579d0f | 11 | use std::cell::{RefCell, Cell}; |
c34b1796 | 12 | use std::collections::{HashSet, HashMap}; |
1a4d82fc | 13 | use std::dynamic_lib::DynamicLibrary; |
85aaf69f | 14 | use std::env; |
c34b1796 AL |
15 | use std::ffi::OsString; |
16 | use std::io::prelude::*; | |
17 | use std::io; | |
18 | use std::path::PathBuf; | |
19 | use std::process::Command; | |
1a4d82fc | 20 | use std::str; |
c34b1796 | 21 | use std::sync::{Arc, Mutex}; |
1a4d82fc | 22 | |
1a4d82fc | 23 | use testing; |
c34b1796 | 24 | use rustc_lint; |
1a4d82fc | 25 | use rustc::session::{self, config}; |
85aaf69f | 26 | use rustc::session::config::get_unstable_features_setting; |
1a4d82fc | 27 | use rustc::session::search_paths::{SearchPaths, PathKind}; |
c34b1796 | 28 | use rustc_back::tempdir::TempDir; |
85aaf69f SL |
29 | use rustc_driver::{driver, Compilation}; |
30 | use syntax::codemap::CodeMap; | |
1a4d82fc | 31 | use syntax::diagnostic; |
1a4d82fc JJ |
32 | |
33 | use core; | |
34 | use clean; | |
35 | use clean::Clean; | |
36 | use fold::DocFolder; | |
37 | use html::markdown; | |
38 | use passes; | |
39 | use visit_ast::RustdocVisitor; | |
40 | ||
9346a6ac AL |
41 | #[derive(Clone, Default)] |
42 | pub struct TestOptions { | |
43 | pub no_crate_inject: bool, | |
44 | pub attrs: Vec<String>, | |
45 | } | |
46 | ||
1a4d82fc JJ |
47 | pub fn run(input: &str, |
48 | cfgs: Vec<String>, | |
49 | libs: SearchPaths, | |
50 | externs: core::Externs, | |
51 | mut test_args: Vec<String>, | |
52 | crate_name: Option<String>) | |
c34b1796 AL |
53 | -> isize { |
54 | let input_path = PathBuf::from(input); | |
1a4d82fc JJ |
55 | let input = config::Input::File(input_path.clone()); |
56 | ||
57 | let sessopts = config::Options { | |
c34b1796 AL |
58 | maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap() |
59 | .parent().unwrap().to_path_buf()), | |
1a4d82fc JJ |
60 | search_paths: libs.clone(), |
61 | crate_types: vec!(config::CrateTypeDylib), | |
62 | externs: externs.clone(), | |
63 | unstable_features: get_unstable_features_setting(), | |
64 | ..config::basic_options().clone() | |
65 | }; | |
66 | ||
67 | let codemap = CodeMap::new(); | |
62682a34 | 68 | let diagnostic_handler = diagnostic::Handler::new(diagnostic::Auto, None, true); |
1a4d82fc | 69 | let span_diagnostic_handler = |
62682a34 | 70 | diagnostic::SpanHandler::new(diagnostic_handler, codemap); |
1a4d82fc JJ |
71 | |
72 | let sess = session::build_session_(sessopts, | |
73 | Some(input_path.clone()), | |
74 | span_diagnostic_handler); | |
c34b1796 | 75 | rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); |
1a4d82fc JJ |
76 | |
77 | let mut cfg = config::build_configuration(&sess); | |
62682a34 | 78 | cfg.extend(config::parse_cfgspecs(cfgs)); |
1a4d82fc JJ |
79 | let krate = driver::phase_1_parse_input(&sess, cfg, &input); |
80 | let krate = driver::phase_2_configure_and_expand(&sess, krate, | |
81 | "rustdoc-test", None) | |
82 | .expect("phase_2_configure_and_expand aborted in rustdoc!"); | |
83 | ||
9346a6ac | 84 | let opts = scrape_test_config(&krate); |
c34b1796 | 85 | |
1a4d82fc JJ |
86 | let ctx = core::DocContext { |
87 | krate: &krate, | |
88 | maybe_typed: core::NotTyped(sess), | |
85aaf69f | 89 | input: input, |
1a4d82fc JJ |
90 | external_paths: RefCell::new(Some(HashMap::new())), |
91 | external_traits: RefCell::new(None), | |
92 | external_typarams: RefCell::new(None), | |
93 | inlined: RefCell::new(None), | |
94 | populated_crate_impls: RefCell::new(HashSet::new()), | |
d9579d0f | 95 | deref_trait_did: Cell::new(None), |
1a4d82fc JJ |
96 | }; |
97 | ||
98 | let mut v = RustdocVisitor::new(&ctx, None); | |
99 | v.visit(ctx.krate); | |
100 | let mut krate = v.clean(&ctx); | |
101 | match crate_name { | |
102 | Some(name) => krate.name = name, | |
103 | None => {} | |
104 | } | |
105 | let (krate, _) = passes::collapse_docs(krate); | |
106 | let (krate, _) = passes::unindent_comments(krate); | |
107 | ||
108 | let mut collector = Collector::new(krate.name.to_string(), | |
109 | libs, | |
110 | externs, | |
c34b1796 | 111 | false, |
9346a6ac | 112 | opts); |
1a4d82fc JJ |
113 | collector.fold_crate(krate); |
114 | ||
115 | test_args.insert(0, "rustdoctest".to_string()); | |
116 | ||
85aaf69f | 117 | testing::test_main(&test_args, |
1a4d82fc JJ |
118 | collector.tests.into_iter().collect()); |
119 | 0 | |
120 | } | |
121 | ||
c34b1796 | 122 | // Look for #![doc(test(no_crate_inject))], used by crates in the std facade |
9346a6ac | 123 | fn scrape_test_config(krate: &::syntax::ast::Crate) -> TestOptions { |
c34b1796 | 124 | use syntax::attr::AttrMetaMethods; |
9346a6ac | 125 | use syntax::print::pprust; |
c34b1796 | 126 | |
9346a6ac AL |
127 | let mut opts = TestOptions { |
128 | no_crate_inject: false, | |
129 | attrs: Vec::new(), | |
130 | }; | |
131 | ||
62682a34 SL |
132 | let attrs = krate.attrs.iter() |
133 | .filter(|a| a.check_name("doc")) | |
9346a6ac | 134 | .filter_map(|a| a.meta_item_list()) |
62682a34 | 135 | .flat_map(|l| l) |
9346a6ac AL |
136 | .filter(|a| a.check_name("test")) |
137 | .filter_map(|a| a.meta_item_list()) | |
62682a34 | 138 | .flat_map(|l| l); |
9346a6ac AL |
139 | for attr in attrs { |
140 | if attr.check_name("no_crate_inject") { | |
141 | opts.no_crate_inject = true; | |
142 | } | |
143 | if attr.check_name("attr") { | |
144 | if let Some(l) = attr.meta_item_list() { | |
145 | for item in l { | |
146 | opts.attrs.push(pprust::meta_item_to_string(item)); | |
c34b1796 AL |
147 | } |
148 | } | |
149 | } | |
150 | } | |
151 | ||
9346a6ac | 152 | return opts; |
c34b1796 AL |
153 | } |
154 | ||
1a4d82fc JJ |
155 | fn runtest(test: &str, cratename: &str, libs: SearchPaths, |
156 | externs: core::Externs, | |
c34b1796 | 157 | should_panic: bool, no_run: bool, as_test_harness: bool, |
9346a6ac | 158 | opts: &TestOptions) { |
1a4d82fc JJ |
159 | // the test harness wants its own `main` & top level functions, so |
160 | // never wrap the test in `fn main() { ... }` | |
9346a6ac | 161 | let test = maketest(test, Some(cratename), as_test_harness, opts); |
1a4d82fc JJ |
162 | let input = config::Input::Str(test.to_string()); |
163 | ||
164 | let sessopts = config::Options { | |
c34b1796 AL |
165 | maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap() |
166 | .parent().unwrap().to_path_buf()), | |
1a4d82fc JJ |
167 | search_paths: libs, |
168 | crate_types: vec!(config::CrateTypeExecutable), | |
169 | output_types: vec!(config::OutputTypeExe), | |
1a4d82fc JJ |
170 | externs: externs, |
171 | cg: config::CodegenOptions { | |
172 | prefer_dynamic: true, | |
173 | .. config::basic_codegen_options() | |
174 | }, | |
175 | test: as_test_harness, | |
176 | unstable_features: get_unstable_features_setting(), | |
177 | ..config::basic_options().clone() | |
178 | }; | |
179 | ||
180 | // Shuffle around a few input and output handles here. We're going to pass | |
181 | // an explicit handle into rustc to collect output messages, but we also | |
182 | // want to catch the error message that rustc prints when it fails. | |
183 | // | |
bd371182 | 184 | // We take our thread-local stderr (likely set by the test runner) and replace |
c34b1796 | 185 | // it with a sink that is also passed to rustc itself. When this function |
bd371182 | 186 | // returns the output of the sink is copied onto the output of our own thread. |
1a4d82fc | 187 | // |
62682a34 | 188 | // The basic idea is to not use a default Handler for rustc, and then also |
1a4d82fc | 189 | // not print things by default to the actual stderr. |
c34b1796 AL |
190 | struct Sink(Arc<Mutex<Vec<u8>>>); |
191 | impl Write for Sink { | |
192 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
193 | Write::write(&mut *self.0.lock().unwrap(), data) | |
194 | } | |
195 | fn flush(&mut self) -> io::Result<()> { Ok(()) } | |
196 | } | |
197 | struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>); | |
198 | impl Drop for Bomb { | |
199 | fn drop(&mut self) { | |
200 | let _ = self.1.write_all(&self.0.lock().unwrap()); | |
201 | } | |
202 | } | |
203 | let data = Arc::new(Mutex::new(Vec::new())); | |
204 | let emitter = diagnostic::EmitterWriter::new(box Sink(data.clone()), None); | |
205 | let old = io::set_panic(box Sink(data.clone())); | |
206 | let _bomb = Bomb(data, old.unwrap_or(box io::stdout())); | |
1a4d82fc JJ |
207 | |
208 | // Compile the code | |
209 | let codemap = CodeMap::new(); | |
62682a34 | 210 | let diagnostic_handler = diagnostic::Handler::with_emitter(true, box emitter); |
1a4d82fc | 211 | let span_diagnostic_handler = |
62682a34 | 212 | diagnostic::SpanHandler::new(diagnostic_handler, codemap); |
1a4d82fc JJ |
213 | |
214 | let sess = session::build_session_(sessopts, | |
85aaf69f SL |
215 | None, |
216 | span_diagnostic_handler); | |
c34b1796 | 217 | rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); |
1a4d82fc JJ |
218 | |
219 | let outdir = TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir"); | |
c34b1796 | 220 | let out = Some(outdir.path().to_path_buf()); |
1a4d82fc JJ |
221 | let cfg = config::build_configuration(&sess); |
222 | let libdir = sess.target_filesearch(PathKind::All).get_lib_path(); | |
85aaf69f SL |
223 | let mut control = driver::CompileController::basic(); |
224 | if no_run { | |
225 | control.after_analysis.stop = Compilation::Stop; | |
226 | } | |
227 | driver::compile_input(sess, cfg, &input, &out, &None, None, control); | |
1a4d82fc JJ |
228 | |
229 | if no_run { return } | |
230 | ||
231 | // Run the code! | |
232 | // | |
233 | // We're careful to prepend the *target* dylib search path to the child's | |
234 | // environment to ensure that the target loads the right libraries at | |
235 | // runtime. It would be a sad day if the *host* libraries were loaded as a | |
236 | // mistake. | |
c34b1796 AL |
237 | let mut cmd = Command::new(&outdir.path().join("rust_out")); |
238 | let var = DynamicLibrary::envvar(); | |
1a4d82fc | 239 | let newpath = { |
c34b1796 AL |
240 | let path = env::var_os(var).unwrap_or(OsString::new()); |
241 | let mut path = env::split_paths(&path).collect::<Vec<_>>(); | |
1a4d82fc | 242 | path.insert(0, libdir.clone()); |
62682a34 | 243 | env::join_paths(path).unwrap() |
1a4d82fc | 244 | }; |
c34b1796 | 245 | cmd.env(var, &newpath); |
1a4d82fc JJ |
246 | |
247 | match cmd.output() { | |
248 | Err(e) => panic!("couldn't run the test: {}{}", e, | |
c34b1796 | 249 | if e.kind() == io::ErrorKind::PermissionDenied { |
1a4d82fc JJ |
250 | " - maybe your tempdir is mounted with noexec?" |
251 | } else { "" }), | |
252 | Ok(out) => { | |
c34b1796 | 253 | if should_panic && out.status.success() { |
1a4d82fc | 254 | panic!("test executable succeeded when it should have failed"); |
c34b1796 AL |
255 | } else if !should_panic && !out.status.success() { |
256 | panic!("test executable failed:\n{}\n{}", | |
257 | str::from_utf8(&out.stdout).unwrap_or(""), | |
258 | str::from_utf8(&out.stderr).unwrap_or("")); | |
1a4d82fc JJ |
259 | } |
260 | } | |
261 | } | |
262 | } | |
263 | ||
9346a6ac AL |
264 | pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool, |
265 | opts: &TestOptions) -> String { | |
c34b1796 AL |
266 | let (crate_attrs, everything_else) = partition_source(s); |
267 | ||
1a4d82fc | 268 | let mut prog = String::new(); |
c34b1796 AL |
269 | |
270 | // First push any outer attributes from the example, assuming they | |
271 | // are intended to be crate attributes. | |
272 | prog.push_str(&crate_attrs); | |
273 | ||
9346a6ac AL |
274 | // Next, any attributes for other aspects such as lints. |
275 | for attr in &opts.attrs { | |
276 | prog.push_str(&format!("#![{}]\n", attr)); | |
1a4d82fc JJ |
277 | } |
278 | ||
279 | // Don't inject `extern crate std` because it's already injected by the | |
280 | // compiler. | |
d9579d0f | 281 | if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") { |
1a4d82fc JJ |
282 | match cratename { |
283 | Some(cratename) => { | |
284 | if s.contains(cratename) { | |
9346a6ac | 285 | prog.push_str(&format!("extern crate {};\n", cratename)); |
1a4d82fc JJ |
286 | } |
287 | } | |
288 | None => {} | |
289 | } | |
290 | } | |
291 | if dont_insert_main || s.contains("fn main") { | |
c34b1796 | 292 | prog.push_str(&everything_else); |
1a4d82fc JJ |
293 | } else { |
294 | prog.push_str("fn main() {\n "); | |
c34b1796 | 295 | prog.push_str(&everything_else.replace("\n", "\n ")); |
1a4d82fc JJ |
296 | prog.push_str("\n}"); |
297 | } | |
298 | ||
c34b1796 AL |
299 | info!("final test program: {}", prog); |
300 | ||
1a4d82fc JJ |
301 | return prog |
302 | } | |
303 | ||
c34b1796 | 304 | fn partition_source(s: &str) -> (String, String) { |
d9579d0f | 305 | use rustc_unicode::str::UnicodeStr; |
c34b1796 AL |
306 | |
307 | let mut after_header = false; | |
308 | let mut before = String::new(); | |
309 | let mut after = String::new(); | |
310 | ||
311 | for line in s.lines() { | |
312 | let trimline = line.trim(); | |
313 | let header = trimline.is_whitespace() || | |
314 | trimline.starts_with("#![feature"); | |
315 | if !header || after_header { | |
316 | after_header = true; | |
317 | after.push_str(line); | |
318 | after.push_str("\n"); | |
319 | } else { | |
320 | before.push_str(line); | |
321 | before.push_str("\n"); | |
322 | } | |
323 | } | |
324 | ||
325 | return (before, after); | |
326 | } | |
327 | ||
1a4d82fc JJ |
328 | pub struct Collector { |
329 | pub tests: Vec<testing::TestDescAndFn>, | |
330 | names: Vec<String>, | |
331 | libs: SearchPaths, | |
332 | externs: core::Externs, | |
c34b1796 | 333 | cnt: usize, |
1a4d82fc JJ |
334 | use_headers: bool, |
335 | current_header: Option<String>, | |
336 | cratename: String, | |
9346a6ac | 337 | opts: TestOptions, |
1a4d82fc JJ |
338 | } |
339 | ||
340 | impl Collector { | |
341 | pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs, | |
9346a6ac | 342 | use_headers: bool, opts: TestOptions) -> Collector { |
1a4d82fc JJ |
343 | Collector { |
344 | tests: Vec::new(), | |
345 | names: Vec::new(), | |
346 | libs: libs, | |
347 | externs: externs, | |
348 | cnt: 0, | |
349 | use_headers: use_headers, | |
350 | current_header: None, | |
351 | cratename: cratename, | |
9346a6ac | 352 | opts: opts, |
1a4d82fc JJ |
353 | } |
354 | } | |
355 | ||
356 | pub fn add_test(&mut self, test: String, | |
c34b1796 AL |
357 | should_panic: bool, no_run: bool, should_ignore: bool, |
358 | as_test_harness: bool) { | |
1a4d82fc | 359 | let name = if self.use_headers { |
85aaf69f | 360 | let s = self.current_header.as_ref().map(|s| &**s).unwrap_or(""); |
1a4d82fc JJ |
361 | format!("{}_{}", s, self.cnt) |
362 | } else { | |
c1a9b12d | 363 | format!("{}_{}", self.names.join("::"), self.cnt) |
1a4d82fc JJ |
364 | }; |
365 | self.cnt += 1; | |
366 | let libs = self.libs.clone(); | |
367 | let externs = self.externs.clone(); | |
368 | let cratename = self.cratename.to_string(); | |
9346a6ac | 369 | let opts = self.opts.clone(); |
1a4d82fc JJ |
370 | debug!("Creating test {}: {}", name, test); |
371 | self.tests.push(testing::TestDescAndFn { | |
372 | desc: testing::TestDesc { | |
373 | name: testing::DynTestName(name), | |
374 | ignore: should_ignore, | |
9346a6ac AL |
375 | // compiler failures are test failures |
376 | should_panic: testing::ShouldPanic::No, | |
1a4d82fc | 377 | }, |
c34b1796 | 378 | testfn: testing::DynTestFn(Box::new(move|| { |
85aaf69f SL |
379 | runtest(&test, |
380 | &cratename, | |
1a4d82fc JJ |
381 | libs, |
382 | externs, | |
c34b1796 | 383 | should_panic, |
1a4d82fc | 384 | no_run, |
c34b1796 | 385 | as_test_harness, |
9346a6ac | 386 | &opts); |
1a4d82fc JJ |
387 | })) |
388 | }); | |
389 | } | |
390 | ||
391 | pub fn register_header(&mut self, name: &str, level: u32) { | |
392 | if self.use_headers && level == 1 { | |
393 | // we use these headings as test names, so it's good if | |
394 | // they're valid identifiers. | |
395 | let name = name.chars().enumerate().map(|(i, c)| { | |
396 | if (i == 0 && c.is_xid_start()) || | |
397 | (i != 0 && c.is_xid_continue()) { | |
398 | c | |
399 | } else { | |
400 | '_' | |
401 | } | |
402 | }).collect::<String>(); | |
403 | ||
404 | // new header => reset count. | |
405 | self.cnt = 0; | |
406 | self.current_header = Some(name); | |
407 | } | |
408 | } | |
409 | } | |
410 | ||
411 | impl DocFolder for Collector { | |
412 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
413 | let pushed = match item.name { | |
9346a6ac | 414 | Some(ref name) if name.is_empty() => false, |
1a4d82fc JJ |
415 | Some(ref name) => { self.names.push(name.to_string()); true } |
416 | None => false | |
417 | }; | |
418 | match item.doc_value() { | |
419 | Some(doc) => { | |
420 | self.cnt = 0; | |
421 | markdown::find_testable_code(doc, &mut *self); | |
422 | } | |
423 | None => {} | |
424 | } | |
425 | let ret = self.fold_item_recur(item); | |
426 | if pushed { | |
427 | self.names.pop(); | |
428 | } | |
429 | return ret; | |
430 | } | |
431 | } |