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