]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/test.rs
New upstream version 1.31.0~beta.4+dfsg1
[rustc.git] / src / librustdoc / test.rs
CommitLineData
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
85aaf69f 11use std::env;
c34b1796
AL
12use std::ffi::OsString;
13use std::io::prelude::*;
14use std::io;
8bb4bdeb 15use std::path::{Path, PathBuf};
54a0048b 16use std::panic::{self, AssertUnwindSafe};
c34b1796 17use std::process::Command;
1a4d82fc 18use std::str;
0531ce1d 19use rustc_data_structures::sync::Lrc;
c34b1796 20use std::sync::{Arc, Mutex};
1a4d82fc 21
1a4d82fc 22use testing;
c34b1796 23use rustc_lint;
476ff2be
SL
24use rustc::hir;
25use rustc::hir::intravisit;
041b39d2 26use rustc::session::{self, CompileIncomplete, config};
83c7162d 27use rustc::session::config::{OutputType, OutputTypes, Externs, CodegenOptions};
1a4d82fc 28use rustc::session::search_paths::{SearchPaths, PathKind};
ff7c6d11 29use rustc_metadata::dynamic_lib::DynamicLibrary;
8faf50e0 30use tempfile::Builder as TempFileBuilder;
83c7162d 31use rustc_driver::{self, driver, target_features, Compilation};
3157f602 32use rustc_driver::driver::phase_2_configure_and_expand;
92a42be0 33use rustc_metadata::cstore::CStore;
3157f602 34use rustc_resolve::MakeGlobMap;
476ff2be 35use syntax::ast;
b7449926 36use syntax::source_map::SourceMap;
0531ce1d 37use syntax::edition::Edition;
9e0c209e 38use syntax::feature_gate::UnstableFeatures;
0531ce1d 39use syntax::with_globals;
b7449926 40use syntax_pos::{BytePos, DUMMY_SP, Pos, Span, FileName};
3157f602
XL
41use errors;
42use errors::emitter::ColorConfig;
1a4d82fc 43
476ff2be 44use clean::Attributes;
b7449926 45use html::markdown::{self, ErrorCodes, LangString};
1a4d82fc 46
9346a6ac
AL
47#[derive(Clone, Default)]
48pub struct TestOptions {
83c7162d 49 /// Whether to disable the default `extern crate my_crate;` when creating doctests.
9346a6ac 50 pub no_crate_inject: bool,
83c7162d
XL
51 /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
52 /// the default `#![allow(unused)]`.
53 pub display_warnings: bool,
54 /// Additional crate-level attributes to add to doctests.
9346a6ac
AL
55 pub attrs: Vec<String>,
56}
57
ff7c6d11 58pub fn run(input_path: &Path,
1a4d82fc
JJ
59 cfgs: Vec<String>,
60 libs: SearchPaths,
5bcae85e 61 externs: Externs,
1a4d82fc 62 mut test_args: Vec<String>,
32a655c1 63 crate_name: Option<String>,
cc61c64b 64 maybe_sysroot: Option<PathBuf>,
abe05a73 65 display_warnings: bool,
0531ce1d 66 linker: Option<PathBuf>,
83c7162d
XL
67 edition: Edition,
68 cg: CodegenOptions)
c34b1796 69 -> isize {
ff7c6d11 70 let input = config::Input::File(input_path.to_owned());
1a4d82fc
JJ
71
72 let sessopts = config::Options {
32a655c1
SL
73 maybe_sysroot: maybe_sysroot.clone().or_else(
74 || Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
1a4d82fc 75 search_paths: libs.clone(),
b7449926 76 crate_types: vec![config::CrateType::Dylib],
83c7162d 77 cg: cg.clone(),
1a4d82fc 78 externs: externs.clone(),
9e0c209e 79 unstable_features: UnstableFeatures::from_environment(),
3b2f2976 80 lint_cap: Some(::rustc::lint::Level::Allow),
32a655c1 81 actually_rustdoc: true,
0531ce1d 82 debugging_opts: config::DebuggingOptions {
0531ce1d
XL
83 ..config::basic_debugging_options()
84 },
83c7162d 85 edition,
b7449926 86 ..config::Options::default()
1a4d82fc 87 };
94b46f34 88 driver::spawn_thread_pool(sessopts, |sessopts| {
b7449926 89 let source_map = Lrc::new(SourceMap::new(sessopts.file_path_mapping()));
94b46f34
XL
90 let handler =
91 errors::Handler::with_tty_emitter(ColorConfig::Auto,
92 true, false,
b7449926 93 Some(source_map.clone()));
94b46f34
XL
94
95 let mut sess = session::build_session_(
b7449926 96 sessopts, Some(input_path.to_owned()), handler, source_map.clone(),
94b46f34
XL
97 );
98 let codegen_backend = rustc_driver::get_codegen_backend(&sess);
99 let cstore = CStore::new(codegen_backend.metadata_loader());
100 rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
101
102 let mut cfg = config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
103 target_features::add_configuration(&mut cfg, &sess, &*codegen_backend);
104 sess.parse_sess.config = cfg;
105
106 let krate = panictry!(driver::phase_1_parse_input(&driver::CompileController::basic(),
107 &sess,
108 &input));
109 let driver::ExpansionResult { defs, mut hir_forest, .. } = {
110 phase_2_configure_and_expand(
111 &sess,
112 &cstore,
113 krate,
114 None,
115 "rustdoc-test",
116 None,
117 MakeGlobMap::No,
118 |_| Ok(()),
119 ).expect("phase_2_configure_and_expand aborted in rustdoc!")
476ff2be 120 };
94b46f34
XL
121
122 let crate_name = crate_name.unwrap_or_else(|| {
123 ::rustc_codegen_utils::link::find_crate_name(None, &hir_forest.krate().attrs, &input)
476ff2be 124 });
94b46f34
XL
125 let mut opts = scrape_test_config(hir_forest.krate());
126 opts.display_warnings |= display_warnings;
127 let mut collector = Collector::new(
128 crate_name,
129 cfgs,
130 libs,
131 cg,
132 externs,
133 false,
134 opts,
135 maybe_sysroot,
b7449926 136 Some(source_map),
94b46f34
XL
137 None,
138 linker,
139 edition
140 );
141
142 {
143 let map = hir::map::map_crate(&sess, &cstore, &mut hir_forest, &defs);
144 let krate = map.krate();
145 let mut hir_collector = HirCollector {
146 sess: &sess,
147 collector: &mut collector,
b7449926
XL
148 map: &map,
149 codes: ErrorCodes::from(sess.opts.unstable_features.is_nightly_build()),
94b46f34
XL
150 };
151 hir_collector.visit_testable("".to_string(), &krate.attrs, |this| {
152 intravisit::walk_crate(this, krate);
153 });
154 }
1a4d82fc 155
94b46f34 156 test_args.insert(0, "rustdoctest".to_string());
1a4d82fc 157
94b46f34
XL
158 testing::test_main(&test_args,
159 collector.tests.into_iter().collect(),
160 testing::Options::new().display_output(display_warnings));
161 0
162 })
1a4d82fc
JJ
163}
164
c34b1796 165// Look for #![doc(test(no_crate_inject))], used by crates in the std facade
54a0048b 166fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions {
b039eaaf 167 use syntax::print::pprust;
c34b1796 168
9346a6ac
AL
169 let mut opts = TestOptions {
170 no_crate_inject: false,
83c7162d 171 display_warnings: false,
9346a6ac
AL
172 attrs: Vec::new(),
173 };
174
cc61c64b
XL
175 let test_attrs: Vec<_> = krate.attrs.iter()
176 .filter(|a| a.check_name("doc"))
177 .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
178 .filter(|a| a.check_name("test"))
179 .collect();
180 let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
181
9346a6ac
AL
182 for attr in attrs {
183 if attr.check_name("no_crate_inject") {
184 opts.no_crate_inject = true;
185 }
186 if attr.check_name("attr") {
187 if let Some(l) = attr.meta_item_list() {
188 for item in l {
9e0c209e 189 opts.attrs.push(pprust::meta_list_item_to_string(item));
c34b1796
AL
190 }
191 }
192 }
193 }
194
c30ab7b3 195 opts
c34b1796
AL
196}
197
2c00a5a8
XL
198fn run_test(test: &str, cratename: &str, filename: &FileName, line: usize,
199 cfgs: Vec<String>, libs: SearchPaths,
83c7162d 200 cg: CodegenOptions, externs: Externs,
041b39d2
XL
201 should_panic: bool, no_run: bool, as_test_harness: bool,
202 compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
0531ce1d 203 maybe_sysroot: Option<PathBuf>, linker: Option<PathBuf>, edition: Edition) {
1a4d82fc
JJ
204 // the test harness wants its own `main` & top level functions, so
205 // never wrap the test in `fn main() { ... }`
2c00a5a8 206 let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts);
ea8adc8c 207 // FIXME(#44940): if doctests ever support path remapping, then this filename
b7449926 208 // needs to be the result of SourceMap::span_to_unmapped_path
54a0048b 209 let input = config::Input::Str {
041b39d2 210 name: filename.to_owned(),
0bf4aa26 211 input: test,
54a0048b 212 };
5bcae85e 213 let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
1a4d82fc
JJ
214
215 let sessopts = config::Options {
32a655c1
SL
216 maybe_sysroot: maybe_sysroot.or_else(
217 || Some(env::current_exe().unwrap().parent().unwrap().parent().unwrap().to_path_buf())),
1a4d82fc 218 search_paths: libs,
b7449926 219 crate_types: vec![config::CrateType::Executable],
b039eaaf 220 output_types: outputs,
3b2f2976 221 externs,
1a4d82fc 222 cg: config::CodegenOptions {
abe05a73 223 linker,
83c7162d 224 ..cg
1a4d82fc
JJ
225 },
226 test: as_test_harness,
9e0c209e 227 unstable_features: UnstableFeatures::from_environment(),
0531ce1d 228 debugging_opts: config::DebuggingOptions {
0531ce1d
XL
229 ..config::basic_debugging_options()
230 },
83c7162d 231 edition,
b7449926 232 ..config::Options::default()
1a4d82fc
JJ
233 };
234
74d20737
XL
235 // Shuffle around a few input and output handles here. We're going to pass
236 // an explicit handle into rustc to collect output messages, but we also
237 // want to catch the error message that rustc prints when it fails.
238 //
239 // We take our thread-local stderr (likely set by the test runner) and replace
240 // it with a sink that is also passed to rustc itself. When this function
241 // returns the output of the sink is copied onto the output of our own thread.
242 //
243 // The basic idea is to not use a default Handler for rustc, and then also
244 // not print things by default to the actual stderr.
245 struct Sink(Arc<Mutex<Vec<u8>>>);
246 impl Write for Sink {
247 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
248 Write::write(&mut *self.0.lock().unwrap(), data)
c34b1796 249 }
74d20737
XL
250 fn flush(&mut self) -> io::Result<()> { Ok(()) }
251 }
8faf50e0 252 struct Bomb(Arc<Mutex<Vec<u8>>>, Box<dyn Write+Send>);
74d20737
XL
253 impl Drop for Bomb {
254 fn drop(&mut self) {
255 let _ = self.1.write_all(&self.0.lock().unwrap());
94b46f34 256 }
74d20737
XL
257 }
258 let data = Arc::new(Mutex::new(Vec::new()));
259
260 let old = io::set_panic(Some(box Sink(data.clone())));
261 let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
262
263 let (libdir, outdir, compile_result) = driver::spawn_thread_pool(sessopts, |sessopts| {
b7449926 264 let source_map = Lrc::new(SourceMap::new_doctest(
94b46f34
XL
265 sessopts.file_path_mapping(), filename.clone(), line as isize - line_offset as isize
266 ));
267 let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
b7449926 268 Some(source_map.clone()),
94b46f34
XL
269 false,
270 false);
94b46f34
XL
271
272 // Compile the code
273 let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
274
275 let mut sess = session::build_session_(
b7449926 276 sessopts, None, diagnostic_handler, source_map,
94b46f34
XL
277 );
278 let codegen_backend = rustc_driver::get_codegen_backend(&sess);
279 let cstore = CStore::new(codegen_backend.metadata_loader());
280 rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
281
8faf50e0
XL
282 let outdir = Mutex::new(
283 TempFileBuilder::new().prefix("rustdoctest").tempdir().expect("rustdoc needs a tempdir")
284 );
94b46f34
XL
285 let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
286 let mut control = driver::CompileController::basic();
287
288 let mut cfg = config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone()));
289 target_features::add_configuration(&mut cfg, &sess, &*codegen_backend);
290 sess.parse_sess.config = cfg;
291
292 let out = Some(outdir.lock().unwrap().path().to_path_buf());
293
294 if no_run {
295 control.after_analysis.stop = Compilation::Stop;
c34b1796 296 }
7453a54e 297
94b46f34
XL
298 let res = panic::catch_unwind(AssertUnwindSafe(|| {
299 driver::compile_input(
300 codegen_backend,
301 &sess,
302 &cstore,
303 &None,
304 &input,
305 &out,
306 &None,
307 None,
308 &control
309 )
310 }));
311
312 let compile_result = match res {
313 Ok(Ok(())) | Ok(Err(CompileIncomplete::Stopped)) => Ok(()),
314 Err(_) | Ok(Err(CompileIncomplete::Errored(_))) => Err(())
315 };
041b39d2 316
74d20737
XL
317 (libdir, outdir, compile_result)
318 });
319
320 match (compile_result, compile_fail) {
321 (Ok(()), true) => {
322 panic!("test compiled while it wasn't supposed to")
323 }
324 (Ok(()), false) => {}
325 (Err(()), true) => {
326 if error_codes.len() > 0 {
327 let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
328 error_codes.retain(|err| !out.contains(err));
3157f602
XL
329 }
330 }
74d20737
XL
331 (Err(()), false) => {
332 panic!("couldn't compile the test")
041b39d2 333 }
74d20737 334 }
3157f602 335
74d20737
XL
336 if error_codes.len() > 0 {
337 panic!("Some expected error codes were not found: {:?}", error_codes);
338 }
1a4d82fc
JJ
339
340 if no_run { return }
341
342 // Run the code!
343 //
344 // We're careful to prepend the *target* dylib search path to the child's
345 // environment to ensure that the target loads the right libraries at
346 // runtime. It would be a sad day if the *host* libraries were loaded as a
347 // mistake.
7453a54e 348 let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
c34b1796 349 let var = DynamicLibrary::envvar();
1a4d82fc 350 let newpath = {
c34b1796
AL
351 let path = env::var_os(var).unwrap_or(OsString::new());
352 let mut path = env::split_paths(&path).collect::<Vec<_>>();
0bf4aa26 353 path.insert(0, libdir);
62682a34 354 env::join_paths(path).unwrap()
1a4d82fc 355 };
c34b1796 356 cmd.env(var, &newpath);
1a4d82fc
JJ
357
358 match cmd.output() {
359 Err(e) => panic!("couldn't run the test: {}{}", e,
c34b1796 360 if e.kind() == io::ErrorKind::PermissionDenied {
1a4d82fc
JJ
361 " - maybe your tempdir is mounted with noexec?"
362 } else { "" }),
363 Ok(out) => {
c34b1796 364 if should_panic && out.status.success() {
1a4d82fc 365 panic!("test executable succeeded when it should have failed");
c34b1796 366 } else if !should_panic && !out.status.success() {
8bb4bdeb 367 panic!("test executable failed:\n{}\n{}\n",
c34b1796
AL
368 str::from_utf8(&out.stdout).unwrap_or(""),
369 str::from_utf8(&out.stderr).unwrap_or(""));
1a4d82fc
JJ
370 }
371 }
372 }
373}
374
2c00a5a8 375/// Makes the test file. Also returns the number of lines before the code begins
041b39d2
XL
376pub fn make_test(s: &str,
377 cratename: Option<&str>,
378 dont_insert_main: bool,
379 opts: &TestOptions)
2c00a5a8 380 -> (String, usize) {
c34b1796 381 let (crate_attrs, everything_else) = partition_source(s);
0531ce1d 382 let everything_else = everything_else.trim();
2c00a5a8 383 let mut line_offset = 0;
1a4d82fc 384 let mut prog = String::new();
c34b1796 385
83c7162d 386 if opts.attrs.is_empty() && !opts.display_warnings {
abe05a73
XL
387 // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
388 // lints that are commonly triggered in doctests. The crate-level test attributes are
389 // commonly used to make tests fail in case they trigger warnings, so having this there in
390 // that case may cause some tests to pass when they shouldn't have.
391 prog.push_str("#![allow(unused)]\n");
2c00a5a8 392 line_offset += 1;
abe05a73 393 }
c34b1796 394
abe05a73 395 // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
9346a6ac
AL
396 for attr in &opts.attrs {
397 prog.push_str(&format!("#![{}]\n", attr));
2c00a5a8 398 line_offset += 1;
1a4d82fc
JJ
399 }
400
abe05a73
XL
401 // Now push any outer attributes from the example, assuming they
402 // are intended to be crate attributes.
403 prog.push_str(&crate_attrs);
404
1a4d82fc
JJ
405 // Don't inject `extern crate std` because it's already injected by the
406 // compiler.
d9579d0f 407 if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
54a0048b
SL
408 if let Some(cratename) = cratename {
409 if s.contains(cratename) {
410 prog.push_str(&format!("extern crate {};\n", cratename));
2c00a5a8 411 line_offset += 1;
1a4d82fc 412 }
1a4d82fc
JJ
413 }
414 }
ea8adc8c
XL
415
416 // FIXME (#21299): prefer libsyntax or some other actual parser over this
417 // best-effort ad hoc approach
418 let already_has_main = s.lines()
419 .map(|line| {
420 let comment = line.find("//");
421 if let Some(comment_begins) = comment {
422 &line[0..comment_begins]
423 } else {
424 line
425 }
426 })
427 .any(|code| code.contains("fn main"));
428
429 if dont_insert_main || already_has_main {
0531ce1d 430 prog.push_str(everything_else);
1a4d82fc 431 } else {
c30ab7b3 432 prog.push_str("fn main() {\n");
2c00a5a8 433 line_offset += 1;
0531ce1d 434 prog.push_str(everything_else);
1a4d82fc
JJ
435 prog.push_str("\n}");
436 }
437
c34b1796
AL
438 info!("final test program: {}", prog);
439
2c00a5a8 440 (prog, line_offset)
1a4d82fc
JJ
441}
442
8bb4bdeb 443// FIXME(aburka): use a real parser to deal with multiline attributes
c34b1796 444fn partition_source(s: &str) -> (String, String) {
c34b1796
AL
445 let mut after_header = false;
446 let mut before = String::new();
447 let mut after = String::new();
448
449 for line in s.lines() {
450 let trimline = line.trim();
0531ce1d
XL
451 let header = trimline.chars().all(|c| c.is_whitespace()) ||
452 trimline.starts_with("#![") ||
453 trimline.starts_with("#[macro_use] extern crate") ||
454 trimline.starts_with("extern crate");
c34b1796
AL
455 if !header || after_header {
456 after_header = true;
457 after.push_str(line);
458 after.push_str("\n");
459 } else {
460 before.push_str(line);
461 before.push_str("\n");
462 }
463 }
464
c30ab7b3 465 (before, after)
c34b1796
AL
466}
467
0bf4aa26
XL
468pub trait Tester {
469 fn add_test(&mut self, test: String, config: LangString, line: usize);
470 fn get_line(&self) -> usize {
471 0
472 }
473 fn register_header(&mut self, _name: &str, _level: u32) {}
474}
475
1a4d82fc
JJ
476pub struct Collector {
477 pub tests: Vec<testing::TestDescAndFn>,
abe05a73
XL
478
479 // The name of the test displayed to the user, separated by `::`.
480 //
481 // In tests from Rust source, this is the path to the item
482 // e.g. `["std", "vec", "Vec", "push"]`.
483 //
484 // In tests from a markdown file, this is the titles of all headers (h1~h6)
485 // of the sections that contain the code block, e.g. if the markdown file is
486 // written as:
487 //
488 // ``````markdown
489 // # Title
490 //
491 // ## Subtitle
492 //
493 // ```rust
494 // assert!(true);
495 // ```
496 // ``````
497 //
498 // the `names` vector of that test will be `["Title", "Subtitle"]`.
1a4d82fc 499 names: Vec<String>,
abe05a73 500
92a42be0 501 cfgs: Vec<String>,
1a4d82fc 502 libs: SearchPaths,
83c7162d 503 cg: CodegenOptions,
5bcae85e 504 externs: Externs,
1a4d82fc 505 use_headers: bool,
1a4d82fc 506 cratename: String,
9346a6ac 507 opts: TestOptions,
32a655c1 508 maybe_sysroot: Option<PathBuf>,
8bb4bdeb 509 position: Span,
b7449926 510 source_map: Option<Lrc<SourceMap>>,
ff7c6d11 511 filename: Option<PathBuf>,
ff7c6d11 512 linker: Option<PathBuf>,
0531ce1d 513 edition: Edition,
1a4d82fc
JJ
514}
515
516impl Collector {
83c7162d
XL
517 pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, cg: CodegenOptions,
518 externs: Externs, use_headers: bool, opts: TestOptions,
b7449926 519 maybe_sysroot: Option<PathBuf>, source_map: Option<Lrc<SourceMap>>,
83c7162d 520 filename: Option<PathBuf>, linker: Option<PathBuf>, edition: Edition) -> Collector {
1a4d82fc
JJ
521 Collector {
522 tests: Vec::new(),
523 names: Vec::new(),
3b2f2976
XL
524 cfgs,
525 libs,
83c7162d 526 cg,
3b2f2976 527 externs,
3b2f2976 528 use_headers,
3b2f2976
XL
529 cratename,
530 opts,
531 maybe_sysroot,
8bb4bdeb 532 position: DUMMY_SP,
b7449926 533 source_map,
3b2f2976 534 filename,
abe05a73 535 linker,
0531ce1d 536 edition,
1a4d82fc
JJ
537 }
538 }
539
ff7c6d11 540 fn generate_name(&self, line: usize, filename: &FileName) -> String {
abe05a73 541 format!("{} - {} (line {})", filename, self.names.join("::"), line)
cc61c64b
XL
542 }
543
0bf4aa26
XL
544 pub fn set_position(&mut self, position: Span) {
545 self.position = position;
546 }
547
548 fn get_filename(&self) -> FileName {
549 if let Some(ref source_map) = self.source_map {
550 let filename = source_map.span_to_filename(self.position);
551 if let FileName::Real(ref filename) = filename {
552 if let Ok(cur_dir) = env::current_dir() {
553 if let Ok(path) = filename.strip_prefix(&cur_dir) {
554 return path.to_owned().into();
555 }
556 }
557 }
558 filename
559 } else if let Some(ref filename) = self.filename {
560 filename.clone().into()
561 } else {
562 FileName::Custom("input".to_owned())
563 }
564 }
565}
566
567impl Tester for Collector {
568 fn add_test(&mut self, test: String, config: LangString, line: usize) {
b7449926 569 let filename = self.get_filename();
cc61c64b 570 let name = self.generate_name(line, &filename);
92a42be0 571 let cfgs = self.cfgs.clone();
1a4d82fc 572 let libs = self.libs.clone();
83c7162d 573 let cg = self.cg.clone();
1a4d82fc
JJ
574 let externs = self.externs.clone();
575 let cratename = self.cratename.to_string();
9346a6ac 576 let opts = self.opts.clone();
32a655c1 577 let maybe_sysroot = self.maybe_sysroot.clone();
abe05a73 578 let linker = self.linker.clone();
0bf4aa26 579 let edition = config.edition.unwrap_or(self.edition);
1a4d82fc
JJ
580 debug!("Creating test {}: {}", name, test);
581 self.tests.push(testing::TestDescAndFn {
582 desc: testing::TestDesc {
74d20737 583 name: testing::DynTestName(name.clone()),
b7449926 584 ignore: config.ignore,
9346a6ac
AL
585 // compiler failures are test failures
586 should_panic: testing::ShouldPanic::No,
b7449926 587 allow_fail: config.allow_fail,
1a4d82fc 588 },
ff7c6d11 589 testfn: testing::DynTestFn(box move || {
32a655c1
SL
590 let panic = io::set_panic(None);
591 let print = io::set_print(None);
592 match {
74d20737 593 rustc_driver::in_named_rustc_thread(name, move || with_globals(move || {
32a655c1
SL
594 io::set_panic(panic);
595 io::set_print(print);
041b39d2
XL
596 run_test(&test,
597 &cratename,
598 &filename,
2c00a5a8 599 line,
041b39d2
XL
600 cfgs,
601 libs,
83c7162d 602 cg,
041b39d2 603 externs,
b7449926
XL
604 config.should_panic,
605 config.no_run,
606 config.test_harness,
607 config.compile_fail,
608 config.error_codes,
041b39d2 609 &opts,
abe05a73 610 maybe_sysroot,
0531ce1d
XL
611 linker,
612 edition)
613 }))
32a655c1
SL
614 } {
615 Ok(()) => (),
616 Err(err) => panic::resume_unwind(err),
617 }
618 }),
1a4d82fc
JJ
619 });
620 }
621
0bf4aa26 622 fn get_line(&self) -> usize {
b7449926 623 if let Some(ref source_map) = self.source_map {
ea8adc8c 624 let line = self.position.lo().to_usize();
b7449926 625 let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
8bb4bdeb
XL
626 if line > 0 { line - 1 } else { line }
627 } else {
628 0
629 }
630 }
631
0bf4aa26 632 fn register_header(&mut self, name: &str, level: u32) {
abe05a73 633 if self.use_headers {
1a4d82fc
JJ
634 // we use these headings as test names, so it's good if
635 // they're valid identifiers.
636 let name = name.chars().enumerate().map(|(i, c)| {
637 if (i == 0 && c.is_xid_start()) ||
638 (i != 0 && c.is_xid_continue()) {
639 c
640 } else {
641 '_'
642 }
643 }).collect::<String>();
644
abe05a73
XL
645 // Here we try to efficiently assemble the header titles into the
646 // test name in the form of `h1::h2::h3::h4::h5::h6`.
647 //
648 // Suppose originally `self.names` contains `[h1, h2, h3]`...
649 let level = level as usize;
650 if level <= self.names.len() {
651 // ... Consider `level == 2`. All headers in the lower levels
652 // are irrelevant in this new level. So we should reset
653 // `self.names` to contain headers until <h2>, and replace that
654 // slot with the new name: `[h1, name]`.
655 self.names.truncate(level);
656 self.names[level - 1] = name;
657 } else {
658 // ... On the other hand, consider `level == 5`. This means we
659 // need to extend `self.names` to contain five headers. We fill
660 // in the missing level (<h4>) with `_`. Thus `self.names` will
661 // become `[h1, h2, h3, "_", name]`.
662 if level - 1 > self.names.len() {
663 self.names.resize(level - 1, "_".to_owned());
664 }
665 self.names.push(name);
666 }
1a4d82fc
JJ
667 }
668 }
669}
670
476ff2be 671struct HirCollector<'a, 'hir: 'a> {
3b2f2976 672 sess: &'a session::Session,
476ff2be 673 collector: &'a mut Collector,
b7449926
XL
674 map: &'a hir::map::Map<'hir>,
675 codes: ErrorCodes,
476ff2be 676}
92a42be0 677
476ff2be
SL
678impl<'a, 'hir> HirCollector<'a, 'hir> {
679 fn visit_testable<F: FnOnce(&mut Self)>(&mut self,
680 name: String,
681 attrs: &[ast::Attribute],
682 nested: F) {
3b2f2976
XL
683 let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
684 if let Some(ref cfg) = attrs.cfg {
0531ce1d 685 if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
3b2f2976
XL
686 return;
687 }
688 }
689
476ff2be
SL
690 let has_name = !name.is_empty();
691 if has_name {
692 self.collector.names.push(name);
693 }
92a42be0 694
476ff2be
SL
695 attrs.collapse_doc_comments();
696 attrs.unindent_doc_comments();
ff7c6d11
XL
697 // the collapse-docs pass won't combine sugared/raw doc attributes, or included files with
698 // anything else, this will combine them for us
699 if let Some(doc) = attrs.collapsed_doc_value() {
b7449926
XL
700 self.collector.set_position(attrs.span.unwrap_or(DUMMY_SP));
701 let res = markdown::find_testable_code(&doc, self.collector, self.codes);
702 if let Err(err) = res {
703 self.sess.diagnostic().span_warn(attrs.span.unwrap_or(DUMMY_SP),
704 &err.to_string());
705 }
1a4d82fc 706 }
92a42be0 707
476ff2be
SL
708 nested(self);
709
710 if has_name {
711 self.collector.names.pop();
1a4d82fc 712 }
476ff2be
SL
713 }
714}
92a42be0 715
476ff2be
SL
716impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> {
717 fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'hir> {
718 intravisit::NestedVisitorMap::All(&self.map)
719 }
92a42be0 720
476ff2be 721 fn visit_item(&mut self, item: &'hir hir::Item) {
8faf50e0 722 let name = if let hir::ItemKind::Impl(.., ref ty, _) = item.node {
32a655c1 723 self.map.node_to_pretty_string(ty.id)
476ff2be
SL
724 } else {
725 item.name.to_string()
726 };
92a42be0 727
476ff2be
SL
728 self.visit_testable(name, &item.attrs, |this| {
729 intravisit::walk_item(this, item);
730 });
731 }
92a42be0 732
476ff2be 733 fn visit_trait_item(&mut self, item: &'hir hir::TraitItem) {
8faf50e0 734 self.visit_testable(item.ident.to_string(), &item.attrs, |this| {
476ff2be
SL
735 intravisit::walk_trait_item(this, item);
736 });
737 }
92a42be0 738
476ff2be 739 fn visit_impl_item(&mut self, item: &'hir hir::ImplItem) {
8faf50e0 740 self.visit_testable(item.ident.to_string(), &item.attrs, |this| {
476ff2be
SL
741 intravisit::walk_impl_item(this, item);
742 });
743 }
744
745 fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem) {
746 self.visit_testable(item.name.to_string(), &item.attrs, |this| {
747 intravisit::walk_foreign_item(this, item);
748 });
749 }
750
751 fn visit_variant(&mut self,
752 v: &'hir hir::Variant,
753 g: &'hir hir::Generics,
754 item_id: ast::NodeId) {
755 self.visit_testable(v.node.name.to_string(), &v.node.attrs, |this| {
756 intravisit::walk_variant(this, v, g, item_id);
757 });
758 }
759
760 fn visit_struct_field(&mut self, f: &'hir hir::StructField) {
94b46f34 761 self.visit_testable(f.ident.to_string(), &f.attrs, |this| {
476ff2be
SL
762 intravisit::walk_struct_field(this, f);
763 });
764 }
765
766 fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef) {
767 self.visit_testable(macro_def.name.to_string(), &macro_def.attrs, |_| ());
1a4d82fc
JJ
768 }
769}
0531ce1d
XL
770
771#[cfg(test)]
772mod tests {
773 use super::{TestOptions, make_test};
774
775 #[test]
776 fn make_test_basic() {
777 //basic use: wraps with `fn main`, adds `#![allow(unused)]`
778 let opts = TestOptions::default();
779 let input =
780"assert_eq!(2+2, 4);";
781 let expected =
782"#![allow(unused)]
783fn main() {
784assert_eq!(2+2, 4);
785}".to_string();
786 let output = make_test(input, None, false, &opts);
0bf4aa26 787 assert_eq!(output, (expected, 2));
0531ce1d
XL
788 }
789
790 #[test]
791 fn make_test_crate_name_no_use() {
792 //if you give a crate name but *don't* use it within the test, it won't bother inserting
793 //the `extern crate` statement
794 let opts = TestOptions::default();
795 let input =
796"assert_eq!(2+2, 4);";
797 let expected =
798"#![allow(unused)]
799fn main() {
800assert_eq!(2+2, 4);
801}".to_string();
802 let output = make_test(input, Some("asdf"), false, &opts);
803 assert_eq!(output, (expected, 2));
804 }
805
806 #[test]
807 fn make_test_crate_name() {
808 //if you give a crate name and use it within the test, it will insert an `extern crate`
809 //statement before `fn main`
810 let opts = TestOptions::default();
811 let input =
812"use asdf::qwop;
813assert_eq!(2+2, 4);";
814 let expected =
815"#![allow(unused)]
816extern crate asdf;
817fn main() {
818use asdf::qwop;
819assert_eq!(2+2, 4);
820}".to_string();
821 let output = make_test(input, Some("asdf"), false, &opts);
822 assert_eq!(output, (expected, 3));
823 }
824
825 #[test]
826 fn make_test_no_crate_inject() {
827 //even if you do use the crate within the test, setting `opts.no_crate_inject` will skip
828 //adding it anyway
829 let opts = TestOptions {
830 no_crate_inject: true,
83c7162d 831 display_warnings: false,
0531ce1d
XL
832 attrs: vec![],
833 };
834 let input =
835"use asdf::qwop;
836assert_eq!(2+2, 4);";
837 let expected =
838"#![allow(unused)]
839fn main() {
840use asdf::qwop;
841assert_eq!(2+2, 4);
842}".to_string();
843 let output = make_test(input, Some("asdf"), false, &opts);
844 assert_eq!(output, (expected, 2));
845 }
846
847 #[test]
848 fn make_test_ignore_std() {
849 //even if you include a crate name, and use it in the doctest, we still won't include an
850 //`extern crate` statement if the crate is "std" - that's included already by the compiler!
851 let opts = TestOptions::default();
852 let input =
853"use std::*;
854assert_eq!(2+2, 4);";
855 let expected =
856"#![allow(unused)]
857fn main() {
858use std::*;
859assert_eq!(2+2, 4);
860}".to_string();
861 let output = make_test(input, Some("std"), false, &opts);
862 assert_eq!(output, (expected, 2));
863 }
864
865 #[test]
866 fn make_test_manual_extern_crate() {
867 //when you manually include an `extern crate` statement in your doctest, make_test assumes
868 //you've included one for your own crate too
869 let opts = TestOptions::default();
870 let input =
871"extern crate asdf;
872use asdf::qwop;
873assert_eq!(2+2, 4);";
874 let expected =
875"#![allow(unused)]
876extern crate asdf;
877fn main() {
878use asdf::qwop;
879assert_eq!(2+2, 4);
880}".to_string();
881 let output = make_test(input, Some("asdf"), false, &opts);
882 assert_eq!(output, (expected, 2));
883 }
884
885 #[test]
886 fn make_test_manual_extern_crate_with_macro_use() {
887 let opts = TestOptions::default();
888 let input =
889"#[macro_use] extern crate asdf;
890use asdf::qwop;
891assert_eq!(2+2, 4);";
892 let expected =
893"#![allow(unused)]
894#[macro_use] extern crate asdf;
895fn main() {
896use asdf::qwop;
897assert_eq!(2+2, 4);
898}".to_string();
899 let output = make_test(input, Some("asdf"), false, &opts);
900 assert_eq!(output, (expected, 2));
901 }
902
903 #[test]
904 fn make_test_opts_attrs() {
905 //if you supplied some doctest attributes with #![doc(test(attr(...)))], it will use those
906 //instead of the stock #![allow(unused)]
907 let mut opts = TestOptions::default();
908 opts.attrs.push("feature(sick_rad)".to_string());
909 let input =
910"use asdf::qwop;
911assert_eq!(2+2, 4);";
912 let expected =
913"#![feature(sick_rad)]
914extern crate asdf;
915fn main() {
916use asdf::qwop;
917assert_eq!(2+2, 4);
918}".to_string();
919 let output = make_test(input, Some("asdf"), false, &opts);
920 assert_eq!(output, (expected, 3));
921
922 //adding more will also bump the returned line offset
923 opts.attrs.push("feature(hella_dope)".to_string());
924 let expected =
925"#![feature(sick_rad)]
926#![feature(hella_dope)]
927extern crate asdf;
928fn main() {
929use asdf::qwop;
930assert_eq!(2+2, 4);
931}".to_string();
932 let output = make_test(input, Some("asdf"), false, &opts);
933 assert_eq!(output, (expected, 4));
934 }
935
936 #[test]
937 fn make_test_crate_attrs() {
938 //including inner attributes in your doctest will apply them to the whole "crate", pasting
939 //them outside the generated main function
940 let opts = TestOptions::default();
941 let input =
942"#![feature(sick_rad)]
943assert_eq!(2+2, 4);";
944 let expected =
945"#![allow(unused)]
946#![feature(sick_rad)]
947fn main() {
948assert_eq!(2+2, 4);
949}".to_string();
950 let output = make_test(input, None, false, &opts);
951 assert_eq!(output, (expected, 2));
952 }
953
954 #[test]
955 fn make_test_with_main() {
956 //including your own `fn main` wrapper lets the test use it verbatim
957 let opts = TestOptions::default();
958 let input =
959"fn main() {
960 assert_eq!(2+2, 4);
961}";
962 let expected =
963"#![allow(unused)]
964fn main() {
965 assert_eq!(2+2, 4);
966}".to_string();
967 let output = make_test(input, None, false, &opts);
968 assert_eq!(output, (expected, 1));
969 }
970
971 #[test]
972 fn make_test_fake_main() {
973 //...but putting it in a comment will still provide a wrapper
974 let opts = TestOptions::default();
975 let input =
976"//Ceci n'est pas une `fn main`
977assert_eq!(2+2, 4);";
978 let expected =
979"#![allow(unused)]
980fn main() {
981//Ceci n'est pas une `fn main`
982assert_eq!(2+2, 4);
983}".to_string();
984 let output = make_test(input, None, false, &opts);
0bf4aa26 985 assert_eq!(output, (expected, 2));
0531ce1d
XL
986 }
987
988 #[test]
989 fn make_test_dont_insert_main() {
990 //even with that, if you set `dont_insert_main`, it won't create the `fn main` wrapper
991 let opts = TestOptions::default();
992 let input =
993"//Ceci n'est pas une `fn main`
994assert_eq!(2+2, 4);";
995 let expected =
996"#![allow(unused)]
997//Ceci n'est pas une `fn main`
998assert_eq!(2+2, 4);".to_string();
999 let output = make_test(input, None, true, &opts);
0bf4aa26 1000 assert_eq!(output, (expected, 1));
0531ce1d 1001 }
83c7162d
XL
1002
1003 #[test]
1004 fn make_test_display_warnings() {
1005 //if the user is asking to display doctest warnings, suppress the default allow(unused)
1006 let mut opts = TestOptions::default();
1007 opts.display_warnings = true;
1008 let input =
1009"assert_eq!(2+2, 4);";
1010 let expected =
1011"fn main() {
1012assert_eq!(2+2, 4);
1013}".to_string();
1014 let output = make_test(input, None, false, &opts);
0bf4aa26 1015 assert_eq!(output, (expected, 1));
83c7162d 1016 }
0531ce1d 1017}