]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/test.rs
Add missing lifetime specifier
[rust.git] / src / librustdoc / test.rs
1 use rustc_data_structures::sync::Lrc;
2 use rustc_interface::interface;
3 use rustc::hir;
4 use rustc::hir::intravisit;
5 use rustc::hir::def_id::LOCAL_CRATE;
6 use rustc::session::{self, config, DiagnosticOutput};
7 use rustc::session::config::{OutputType, OutputTypes, Externs, CodegenOptions};
8 use rustc::session::search_paths::SearchPath;
9 use rustc::util::common::ErrorReported;
10 use syntax::ast;
11 use syntax::with_globals;
12 use syntax::source_map::SourceMap;
13 use syntax::edition::Edition;
14 use syntax::feature_gate::UnstableFeatures;
15 use std::env;
16 use std::io::prelude::*;
17 use std::io;
18 use std::panic::{self, AssertUnwindSafe};
19 use std::path::PathBuf;
20 use std::process::{self, Command};
21 use std::str;
22 use std::sync::{Arc, Mutex};
23 use syntax::symbol::sym;
24 use syntax_pos::{BytePos, DUMMY_SP, Pos, Span, FileName};
25 use tempfile::Builder as TempFileBuilder;
26 use testing;
27
28 use crate::clean::Attributes;
29 use crate::config::Options;
30 use crate::html::markdown::{self, ErrorCodes, LangString};
31
32 #[derive(Clone, Default)]
33 pub struct TestOptions {
34     /// Whether to disable the default `extern crate my_crate;` when creating doctests.
35     pub no_crate_inject: bool,
36     /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
37     /// the default `#![allow(unused)]`.
38     pub display_warnings: bool,
39     /// Additional crate-level attributes to add to doctests.
40     pub attrs: Vec<String>,
41 }
42
43 pub fn run(options: Options) -> i32 {
44     let input = config::Input::File(options.input.clone());
45
46     let sessopts = config::Options {
47         maybe_sysroot: options.maybe_sysroot.clone(),
48         search_paths: options.libs.clone(),
49         crate_types: vec![config::CrateType::Dylib],
50         cg: options.codegen_options.clone(),
51         externs: options.externs.clone(),
52         unstable_features: UnstableFeatures::from_environment(),
53         lint_cap: Some(::rustc::lint::Level::Allow),
54         actually_rustdoc: true,
55         debugging_opts: config::DebuggingOptions {
56             ..config::basic_debugging_options()
57         },
58         edition: options.edition,
59         ..config::Options::default()
60     };
61
62     let config = interface::Config {
63         opts: sessopts,
64         crate_cfg: config::parse_cfgspecs(options.cfgs.clone()),
65         input,
66         input_path: None,
67         output_file: None,
68         output_dir: None,
69         file_loader: None,
70         diagnostic_output: DiagnosticOutput::Default,
71         stderr: None,
72         crate_name: options.crate_name.clone(),
73         lint_caps: Default::default(),
74     };
75
76     let mut test_args = options.test_args.clone();
77     let display_warnings = options.display_warnings;
78
79     let tests = interface::run_compiler(config, |compiler| -> Result<_, ErrorReported> {
80         let lower_to_hir = compiler.lower_to_hir()?;
81
82         let mut opts = scrape_test_config(lower_to_hir.peek().0.borrow().krate());
83         opts.display_warnings |= options.display_warnings;
84         let mut collector = Collector::new(
85             compiler.crate_name()?.peek().to_string(),
86             options.cfgs,
87             options.libs,
88             options.codegen_options,
89             options.externs,
90             false,
91             opts,
92             options.maybe_sysroot,
93             Some(compiler.source_map().clone()),
94             None,
95             options.linker,
96             options.edition,
97             options.persist_doctests,
98         );
99
100         let mut global_ctxt = compiler.global_ctxt()?.take();
101         global_ctxt.enter(|tcx| {
102             let krate = tcx.hir().krate();
103             let mut hir_collector = HirCollector {
104                 sess: compiler.session(),
105                 collector: &mut collector,
106                 map: tcx.hir(),
107                 codes: ErrorCodes::from(compiler.session().opts
108                                                 .unstable_features.is_nightly_build()),
109             };
110             hir_collector.visit_testable("".to_string(), &krate.attrs, |this| {
111                 intravisit::walk_crate(this, krate);
112             });
113         });
114
115         Ok(collector.tests)
116     }).expect("compiler aborted in rustdoc!");
117
118     test_args.insert(0, "rustdoctest".to_string());
119
120     testing::test_main(
121         &test_args,
122         tests,
123         testing::Options::new().display_output(display_warnings)
124     );
125
126     0
127 }
128
129 // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
130 fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions {
131     use syntax::print::pprust;
132
133     let mut opts = TestOptions {
134         no_crate_inject: false,
135         display_warnings: false,
136         attrs: Vec::new(),
137     };
138
139     let test_attrs: Vec<_> = krate.attrs.iter()
140         .filter(|a| a.check_name(sym::doc))
141         .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
142         .filter(|a| a.check_name(sym::test))
143         .collect();
144     let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
145
146     for attr in attrs {
147         if attr.check_name(sym::no_crate_inject) {
148             opts.no_crate_inject = true;
149         }
150         if attr.check_name(sym::attr) {
151             if let Some(l) = attr.meta_item_list() {
152                 for item in l {
153                     opts.attrs.push(pprust::meta_list_item_to_string(item));
154                 }
155             }
156         }
157     }
158
159     opts
160 }
161
162 /// Documentation test failure modes.
163 enum TestFailure {
164     /// The test failed to compile.
165     CompileError,
166     /// The test is marked `compile_fail` but compiled successfully.
167     UnexpectedCompilePass,
168     /// The test failed to compile (as expected) but the compiler output did not contain all
169     /// expected error codes.
170     MissingErrorCodes(Vec<String>),
171     /// The test binary was unable to be executed.
172     ExecutionError(io::Error),
173     /// The test binary exited with a non-zero exit code.
174     ///
175     /// This typically means an assertion in the test failed or another form of panic occurred.
176     ExecutionFailure(process::Output),
177     /// The test is marked `should_panic` but the test binary executed successfully.
178     UnexpectedRunPass,
179 }
180
181 fn run_test(
182     test: &str,
183     cratename: &str,
184     filename: &FileName,
185     line: usize,
186     cfgs: Vec<String>,
187     libs: Vec<SearchPath>,
188     cg: CodegenOptions,
189     externs: Externs,
190     should_panic: bool,
191     no_run: bool,
192     as_test_harness: bool,
193     compile_fail: bool,
194     mut error_codes: Vec<String>,
195     opts: &TestOptions,
196     maybe_sysroot: Option<PathBuf>,
197     linker: Option<PathBuf>,
198     edition: Edition,
199     persist_doctests: Option<PathBuf>,
200 ) -> Result<(), TestFailure> {
201     let (test, line_offset) = match panic::catch_unwind(|| {
202         make_test(test, Some(cratename), as_test_harness, opts, edition)
203     }) {
204         Ok((test, line_offset)) => (test, line_offset),
205         Err(cause) if cause.is::<errors::FatalErrorMarker>() => {
206             // If the parser used by `make_test` panicked due to a fatal error, pass the test code
207             // through unchanged. The error will be reported during compilation.
208             (test.to_owned(), 0)
209         },
210         Err(cause) => panic::resume_unwind(cause),
211     };
212
213     // FIXME(#44940): if doctests ever support path remapping, then this filename
214     // needs to be the result of `SourceMap::span_to_unmapped_path`.
215     let path = match filename {
216         FileName::Real(path) => path.clone(),
217         _ => PathBuf::from(r"doctest.rs"),
218     };
219
220     let input = config::Input::Str {
221         name: FileName::DocTest(path, line as isize - line_offset as isize),
222         input: test,
223     };
224     let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
225
226     let sessopts = config::Options {
227         maybe_sysroot,
228         search_paths: libs,
229         crate_types: vec![config::CrateType::Executable],
230         output_types: outputs,
231         externs,
232         cg: config::CodegenOptions {
233             linker,
234             ..cg
235         },
236         test: as_test_harness,
237         unstable_features: UnstableFeatures::from_environment(),
238         debugging_opts: config::DebuggingOptions {
239             ..config::basic_debugging_options()
240         },
241         edition,
242         ..config::Options::default()
243     };
244
245     // Shuffle around a few input and output handles here. We're going to pass
246     // an explicit handle into rustc to collect output messages, but we also
247     // want to catch the error message that rustc prints when it fails.
248     //
249     // We take our thread-local stderr (likely set by the test runner) and replace
250     // it with a sink that is also passed to rustc itself. When this function
251     // returns the output of the sink is copied onto the output of our own thread.
252     //
253     // The basic idea is to not use a default Handler for rustc, and then also
254     // not print things by default to the actual stderr.
255     struct Sink(Arc<Mutex<Vec<u8>>>);
256     impl Write for Sink {
257         fn write(&mut self, data: &[u8]) -> io::Result<usize> {
258             Write::write(&mut *self.0.lock().unwrap(), data)
259         }
260         fn flush(&mut self) -> io::Result<()> { Ok(()) }
261     }
262     struct Bomb(Arc<Mutex<Vec<u8>>>, Option<Box<dyn Write+Send>>);
263     impl Drop for Bomb {
264         fn drop(&mut self) {
265             let mut old = self.1.take().unwrap();
266             let _ = old.write_all(&self.0.lock().unwrap());
267             io::set_panic(Some(old));
268         }
269     }
270     let data = Arc::new(Mutex::new(Vec::new()));
271
272     let old = io::set_panic(Some(box Sink(data.clone())));
273     let _bomb = Bomb(data.clone(), Some(old.unwrap_or(box io::stdout())));
274
275     enum DirState {
276         Temp(tempfile::TempDir),
277         Perm(PathBuf),
278     }
279
280     impl DirState {
281         fn path(&self) -> &std::path::Path {
282             match self {
283                 DirState::Temp(t) => t.path(),
284                 DirState::Perm(p) => p.as_path(),
285             }
286         }
287     }
288
289     let outdir = if let Some(mut path) = persist_doctests {
290         path.push(format!("{}_{}",
291             filename
292                 .to_string()
293                 .rsplit('/')
294                 .next()
295                 .unwrap()
296                 .replace(".", "_"),
297                 line)
298         );
299         std::fs::create_dir_all(&path)
300             .expect("Couldn't create directory for doctest executables");
301
302         DirState::Perm(path)
303     } else {
304         DirState::Temp(TempFileBuilder::new()
305                         .prefix("rustdoctest")
306                         .tempdir()
307                         .expect("rustdoc needs a tempdir"))
308     };
309     let output_file = outdir.path().join("rust_out");
310
311     let config = interface::Config {
312         opts: sessopts,
313         crate_cfg: config::parse_cfgspecs(cfgs),
314         input,
315         input_path: None,
316         output_file: Some(output_file.clone()),
317         output_dir: None,
318         file_loader: None,
319         diagnostic_output: DiagnosticOutput::Raw(box Sink(data.clone())),
320         stderr: Some(data.clone()),
321         crate_name: None,
322         lint_caps: Default::default(),
323     };
324
325     let compile_result = panic::catch_unwind(AssertUnwindSafe(|| {
326         interface::run_compiler(config, |compiler| {
327             if no_run {
328                 compiler.global_ctxt().and_then(|global_ctxt| global_ctxt.take().enter(|tcx| {
329                     tcx.analysis(LOCAL_CRATE)
330                 })).ok();
331             } else {
332                 compiler.compile().ok();
333             };
334             compiler.session().compile_status()
335         })
336     })).map_err(|_| ()).and_then(|s| s.map_err(|_| ()));
337
338     match (compile_result, compile_fail) {
339         (Ok(()), true) => {
340             return Err(TestFailure::UnexpectedCompilePass);
341         }
342         (Ok(()), false) => {}
343         (Err(_), true) => {
344             if !error_codes.is_empty() {
345                 let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
346                 error_codes.retain(|err| !out.contains(err));
347
348                 if !error_codes.is_empty() {
349                     return Err(TestFailure::MissingErrorCodes(error_codes));
350                 }
351             }
352         }
353         (Err(_), false) => {
354             return Err(TestFailure::CompileError);
355         }
356     }
357
358     if no_run {
359         return Ok(());
360     }
361
362     // Run the code!
363     let mut cmd = Command::new(output_file);
364
365     match cmd.output() {
366         Err(e) => return Err(TestFailure::ExecutionError(e)),
367         Ok(out) => {
368             if should_panic && out.status.success() {
369                 return Err(TestFailure::UnexpectedRunPass);
370             } else if !should_panic && !out.status.success() {
371                 return Err(TestFailure::ExecutionFailure(out));
372             }
373         }
374     }
375
376     Ok(())
377 }
378
379 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
380 /// lines before the test code begins.
381 ///
382 /// # Panics
383 ///
384 /// This function uses the compiler's parser internally. The parser will panic if it encounters a
385 /// fatal error while parsing the test.
386 pub fn make_test(s: &str,
387                  cratename: Option<&str>,
388                  dont_insert_main: bool,
389                  opts: &TestOptions,
390                  edition: Edition)
391                  -> (String, usize) {
392     let (crate_attrs, everything_else, crates) = partition_source(s);
393     let everything_else = everything_else.trim();
394     let mut line_offset = 0;
395     let mut prog = String::new();
396
397     if opts.attrs.is_empty() && !opts.display_warnings {
398         // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
399         // lints that are commonly triggered in doctests. The crate-level test attributes are
400         // commonly used to make tests fail in case they trigger warnings, so having this there in
401         // that case may cause some tests to pass when they shouldn't have.
402         prog.push_str("#![allow(unused)]\n");
403         line_offset += 1;
404     }
405
406     // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
407     for attr in &opts.attrs {
408         prog.push_str(&format!("#![{}]\n", attr));
409         line_offset += 1;
410     }
411
412     // Now push any outer attributes from the example, assuming they
413     // are intended to be crate attributes.
414     prog.push_str(&crate_attrs);
415     prog.push_str(&crates);
416
417     // Uses libsyntax to parse the doctest and find if there's a main fn and the extern
418     // crate already is included.
419     let (already_has_main, already_has_extern_crate, found_macro) = with_globals(edition, || {
420         use crate::syntax::{parse::{self, ParseSess}, source_map::FilePathMapping};
421         use errors::emitter::EmitterWriter;
422         use errors::Handler;
423
424         let filename = FileName::anon_source_code(s);
425         let source = crates + &everything_else;
426
427         // Any errors in parsing should also appear when the doctest is compiled for real, so just
428         // send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
429         let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
430         let emitter = EmitterWriter::new(box io::sink(), None, false, false, false);
431         // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
432         let handler = Handler::with_emitter(false, None, box emitter);
433         let sess = ParseSess::with_span_handler(handler, cm);
434
435         let mut found_main = false;
436         let mut found_extern_crate = cratename.is_none();
437         let mut found_macro = false;
438
439         let mut parser = match parse::maybe_new_parser_from_source_str(&sess, filename, source) {
440             Ok(p) => p,
441             Err(errs) => {
442                 for mut err in errs {
443                     err.cancel();
444                 }
445
446                 return (found_main, found_extern_crate, found_macro);
447             }
448         };
449
450         loop {
451             match parser.parse_item() {
452                 Ok(Some(item)) => {
453                     if !found_main {
454                         if let ast::ItemKind::Fn(..) = item.node {
455                             if item.ident.as_str() == "main" {
456                                 found_main = true;
457                             }
458                         }
459                     }
460
461                     if !found_extern_crate {
462                         if let ast::ItemKind::ExternCrate(original) = item.node {
463                             // This code will never be reached if `cratename` is none because
464                             // `found_extern_crate` is initialized to `true` if it is none.
465                             let cratename = cratename.unwrap();
466
467                             match original {
468                                 Some(name) => found_extern_crate = name.as_str() == cratename,
469                                 None => found_extern_crate = item.ident.as_str() == cratename,
470                             }
471                         }
472                     }
473
474                     if !found_macro {
475                         if let ast::ItemKind::Mac(..) = item.node {
476                             found_macro = true;
477                         }
478                     }
479
480                     if found_main && found_extern_crate {
481                         break;
482                     }
483                 }
484                 Ok(None) => break,
485                 Err(mut e) => {
486                     e.cancel();
487                     break;
488                 }
489             }
490         }
491
492         (found_main, found_extern_crate, found_macro)
493     });
494
495     // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
496     // see it. In that case, run the old text-based scan to see if they at least have a main
497     // function written inside a macro invocation. See
498     // https://github.com/rust-lang/rust/issues/56898
499     let already_has_main = if found_macro && !already_has_main {
500         s.lines()
501             .map(|line| {
502                 let comment = line.find("//");
503                 if let Some(comment_begins) = comment {
504                     &line[0..comment_begins]
505                 } else {
506                     line
507                 }
508             })
509             .any(|code| code.contains("fn main"))
510     } else {
511         already_has_main
512     };
513
514     // Don't inject `extern crate std` because it's already injected by the
515     // compiler.
516     if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
517         if let Some(cratename) = cratename {
518             // Make sure its actually used if not included.
519             if s.contains(cratename) {
520                 prog.push_str(&format!("extern crate {};\n", cratename));
521                 line_offset += 1;
522             }
523         }
524     }
525
526     // FIXME: This code cannot yet handle no_std test cases yet
527     if dont_insert_main || already_has_main || prog.contains("![no_std]") {
528         prog.push_str(everything_else);
529     } else {
530         let returns_result = everything_else.trim_end().ends_with("(())");
531         let (main_pre, main_post) = if returns_result {
532             ("fn main() { fn _inner() -> Result<(), impl core::fmt::Debug> {",
533              "}\n_inner().unwrap() }")
534         } else {
535             ("fn main() {\n", "\n}")
536         };
537         prog.extend([main_pre, everything_else, main_post].iter().cloned());
538         line_offset += 1;
539     }
540
541     debug!("final doctest:\n{}", prog);
542
543     (prog, line_offset)
544 }
545
546 // FIXME(aburka): use a real parser to deal with multiline attributes
547 fn partition_source(s: &str) -> (String, String, String) {
548     #[derive(Copy, Clone, PartialEq)]
549     enum PartitionState {
550         Attrs,
551         Crates,
552         Other,
553     }
554     let mut state = PartitionState::Attrs;
555     let mut before = String::new();
556     let mut crates = String::new();
557     let mut after = String::new();
558
559     for line in s.lines() {
560         let trimline = line.trim();
561
562         // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
563         // shunted into "everything else"
564         match state {
565             PartitionState::Attrs => {
566                 state = if trimline.starts_with("#![") ||
567                     trimline.chars().all(|c| c.is_whitespace()) ||
568                     (trimline.starts_with("//") && !trimline.starts_with("///"))
569                 {
570                     PartitionState::Attrs
571                 } else if trimline.starts_with("extern crate") ||
572                     trimline.starts_with("#[macro_use] extern crate")
573                 {
574                     PartitionState::Crates
575                 } else {
576                     PartitionState::Other
577                 };
578             }
579             PartitionState::Crates => {
580                 state = if trimline.starts_with("extern crate") ||
581                     trimline.starts_with("#[macro_use] extern crate") ||
582                     trimline.chars().all(|c| c.is_whitespace()) ||
583                     (trimline.starts_with("//") && !trimline.starts_with("///"))
584                 {
585                     PartitionState::Crates
586                 } else {
587                     PartitionState::Other
588                 };
589             }
590             PartitionState::Other => {}
591         }
592
593         match state {
594             PartitionState::Attrs => {
595                 before.push_str(line);
596                 before.push_str("\n");
597             }
598             PartitionState::Crates => {
599                 crates.push_str(line);
600                 crates.push_str("\n");
601             }
602             PartitionState::Other => {
603                 after.push_str(line);
604                 after.push_str("\n");
605             }
606         }
607     }
608
609     debug!("before:\n{}", before);
610     debug!("crates:\n{}", crates);
611     debug!("after:\n{}", after);
612
613     (before, after, crates)
614 }
615
616 pub trait Tester {
617     fn add_test(&mut self, test: String, config: LangString, line: usize);
618     fn get_line(&self) -> usize {
619         0
620     }
621     fn register_header(&mut self, _name: &str, _level: u32) {}
622 }
623
624 pub struct Collector {
625     pub tests: Vec<testing::TestDescAndFn>,
626
627     // The name of the test displayed to the user, separated by `::`.
628     //
629     // In tests from Rust source, this is the path to the item
630     // e.g., `["std", "vec", "Vec", "push"]`.
631     //
632     // In tests from a markdown file, this is the titles of all headers (h1~h6)
633     // of the sections that contain the code block, e.g., if the markdown file is
634     // written as:
635     //
636     // ``````markdown
637     // # Title
638     //
639     // ## Subtitle
640     //
641     // ```rust
642     // assert!(true);
643     // ```
644     // ``````
645     //
646     // the `names` vector of that test will be `["Title", "Subtitle"]`.
647     names: Vec<String>,
648
649     cfgs: Vec<String>,
650     libs: Vec<SearchPath>,
651     cg: CodegenOptions,
652     externs: Externs,
653     use_headers: bool,
654     cratename: String,
655     opts: TestOptions,
656     maybe_sysroot: Option<PathBuf>,
657     position: Span,
658     source_map: Option<Lrc<SourceMap>>,
659     filename: Option<PathBuf>,
660     linker: Option<PathBuf>,
661     edition: Edition,
662     persist_doctests: Option<PathBuf>,
663 }
664
665 impl Collector {
666     pub fn new(cratename: String, cfgs: Vec<String>, libs: Vec<SearchPath>, cg: CodegenOptions,
667                externs: Externs, use_headers: bool, opts: TestOptions,
668                maybe_sysroot: Option<PathBuf>, source_map: Option<Lrc<SourceMap>>,
669                filename: Option<PathBuf>, linker: Option<PathBuf>, edition: Edition,
670                persist_doctests: Option<PathBuf>) -> Collector {
671         Collector {
672             tests: Vec::new(),
673             names: Vec::new(),
674             cfgs,
675             libs,
676             cg,
677             externs,
678             use_headers,
679             cratename,
680             opts,
681             maybe_sysroot,
682             position: DUMMY_SP,
683             source_map,
684             filename,
685             linker,
686             edition,
687             persist_doctests,
688         }
689     }
690
691     fn generate_name(&self, line: usize, filename: &FileName) -> String {
692         format!("{} - {} (line {})", filename, self.names.join("::"), line)
693     }
694
695     pub fn set_position(&mut self, position: Span) {
696         self.position = position;
697     }
698
699     fn get_filename(&self) -> FileName {
700         if let Some(ref source_map) = self.source_map {
701             let filename = source_map.span_to_filename(self.position);
702             if let FileName::Real(ref filename) = filename {
703                 if let Ok(cur_dir) = env::current_dir() {
704                     if let Ok(path) = filename.strip_prefix(&cur_dir) {
705                         return path.to_owned().into();
706                     }
707                 }
708             }
709             filename
710         } else if let Some(ref filename) = self.filename {
711             filename.clone().into()
712         } else {
713             FileName::Custom("input".to_owned())
714         }
715     }
716 }
717
718 impl Tester for Collector {
719     fn add_test(&mut self, test: String, config: LangString, line: usize) {
720         let filename = self.get_filename();
721         let name = self.generate_name(line, &filename);
722         let cfgs = self.cfgs.clone();
723         let libs = self.libs.clone();
724         let cg = self.cg.clone();
725         let externs = self.externs.clone();
726         let cratename = self.cratename.to_string();
727         let opts = self.opts.clone();
728         let maybe_sysroot = self.maybe_sysroot.clone();
729         let linker = self.linker.clone();
730         let edition = config.edition.unwrap_or(self.edition);
731         let persist_doctests = self.persist_doctests.clone();
732
733         debug!("Creating test {}: {}", name, test);
734         self.tests.push(testing::TestDescAndFn {
735             desc: testing::TestDesc {
736                 name: testing::DynTestName(name),
737                 ignore: config.ignore,
738                 // compiler failures are test failures
739                 should_panic: testing::ShouldPanic::No,
740                 allow_fail: config.allow_fail,
741             },
742             testfn: testing::DynTestFn(box move || {
743                 let res = run_test(
744                     &test,
745                     &cratename,
746                     &filename,
747                     line,
748                     cfgs,
749                     libs,
750                     cg,
751                     externs,
752                     config.should_panic,
753                     config.no_run,
754                     config.test_harness,
755                     config.compile_fail,
756                     config.error_codes,
757                     &opts,
758                     maybe_sysroot,
759                     linker,
760                     edition,
761                     persist_doctests
762                 );
763
764                 if let Err(err) = res {
765                     match err {
766                         TestFailure::CompileError => {
767                             eprint!("Couldn't compile the test.");
768                         }
769                         TestFailure::UnexpectedCompilePass => {
770                             eprint!("Test compiled successfully, but it's marked `compile_fail`.");
771                         }
772                         TestFailure::UnexpectedRunPass => {
773                             eprint!("Test executable succeeded, but it's marked `should_panic`.");
774                         }
775                         TestFailure::MissingErrorCodes(codes) => {
776                             eprint!("Some expected error codes were not found: {:?}", codes);
777                         }
778                         TestFailure::ExecutionError(err) => {
779                             eprint!("Couldn't run the test: {}", err);
780                             if err.kind() == io::ErrorKind::PermissionDenied {
781                                 eprint!(" - maybe your tempdir is mounted with noexec?");
782                             }
783                         }
784                         TestFailure::ExecutionFailure(out) => {
785                             let reason = if let Some(code) = out.status.code() {
786                                 format!("exit code {}", code)
787                             } else {
788                                 String::from("terminated by signal")
789                             };
790
791                             eprintln!("Test executable failed ({}).", reason);
792
793                             // FIXME(#12309): An unfortunate side-effect of capturing the test
794                             // executable's output is that the relative ordering between the test's
795                             // stdout and stderr is lost. However, this is better than the
796                             // alternative: if the test executable inherited the parent's I/O
797                             // handles the output wouldn't be captured at all, even on success.
798                             //
799                             // The ordering could be preserved if the test process' stderr was
800                             // redirected to stdout, but that functionality does not exist in the
801                             // standard library, so it may not be portable enough.
802                             let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
803                             let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
804
805                             if !stdout.is_empty() || !stderr.is_empty() {
806                                 eprintln!();
807
808                                 if !stdout.is_empty() {
809                                     eprintln!("stdout:\n{}", stdout);
810                                 }
811
812                                 if !stderr.is_empty() {
813                                     eprintln!("stderr:\n{}", stderr);
814                                 }
815                             }
816                         }
817                     }
818
819                     panic::resume_unwind(box ());
820                 }
821             }),
822         });
823     }
824
825     fn get_line(&self) -> usize {
826         if let Some(ref source_map) = self.source_map {
827             let line = self.position.lo().to_usize();
828             let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
829             if line > 0 { line - 1 } else { line }
830         } else {
831             0
832         }
833     }
834
835     fn register_header(&mut self, name: &str, level: u32) {
836         if self.use_headers {
837             // We use these headings as test names, so it's good if
838             // they're valid identifiers.
839             let name = name.chars().enumerate().map(|(i, c)| {
840                     if (i == 0 && c.is_xid_start()) ||
841                         (i != 0 && c.is_xid_continue()) {
842                         c
843                     } else {
844                         '_'
845                     }
846                 }).collect::<String>();
847
848             // Here we try to efficiently assemble the header titles into the
849             // test name in the form of `h1::h2::h3::h4::h5::h6`.
850             //
851             // Suppose that originally `self.names` contains `[h1, h2, h3]`...
852             let level = level as usize;
853             if level <= self.names.len() {
854                 // ... Consider `level == 2`. All headers in the lower levels
855                 // are irrelevant in this new level. So we should reset
856                 // `self.names` to contain headers until <h2>, and replace that
857                 // slot with the new name: `[h1, name]`.
858                 self.names.truncate(level);
859                 self.names[level - 1] = name;
860             } else {
861                 // ... On the other hand, consider `level == 5`. This means we
862                 // need to extend `self.names` to contain five headers. We fill
863                 // in the missing level (<h4>) with `_`. Thus `self.names` will
864                 // become `[h1, h2, h3, "_", name]`.
865                 if level - 1 > self.names.len() {
866                     self.names.resize(level - 1, "_".to_owned());
867                 }
868                 self.names.push(name);
869             }
870         }
871     }
872 }
873
874 struct HirCollector<'a, 'hir> {
875     sess: &'a session::Session,
876     collector: &'a mut Collector,
877     map: &'a hir::map::Map<'hir>,
878     codes: ErrorCodes,
879 }
880
881 impl<'a, 'hir> HirCollector<'a, 'hir> {
882     fn visit_testable<F: FnOnce(&mut Self)>(&mut self,
883                                             name: String,
884                                             attrs: &[ast::Attribute],
885                                             nested: F) {
886         let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
887         if let Some(ref cfg) = attrs.cfg {
888             if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
889                 return;
890             }
891         }
892
893         let has_name = !name.is_empty();
894         if has_name {
895             self.collector.names.push(name);
896         }
897
898         attrs.collapse_doc_comments();
899         attrs.unindent_doc_comments();
900         // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
901         // anything else, this will combine them for us.
902         if let Some(doc) = attrs.collapsed_doc_value() {
903             self.collector.set_position(attrs.span.unwrap_or(DUMMY_SP));
904             markdown::find_testable_code(&doc, self.collector, self.codes);
905         }
906
907         nested(self);
908
909         if has_name {
910             self.collector.names.pop();
911         }
912     }
913 }
914
915 impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> {
916     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'hir> {
917         intravisit::NestedVisitorMap::All(&self.map)
918     }
919
920     fn visit_item(&mut self, item: &'hir hir::Item) {
921         let name = if let hir::ItemKind::Impl(.., ref ty, _) = item.node {
922             self.map.hir_to_pretty_string(ty.hir_id)
923         } else {
924             item.ident.to_string()
925         };
926
927         self.visit_testable(name, &item.attrs, |this| {
928             intravisit::walk_item(this, item);
929         });
930     }
931
932     fn visit_trait_item(&mut self, item: &'hir hir::TraitItem) {
933         self.visit_testable(item.ident.to_string(), &item.attrs, |this| {
934             intravisit::walk_trait_item(this, item);
935         });
936     }
937
938     fn visit_impl_item(&mut self, item: &'hir hir::ImplItem) {
939         self.visit_testable(item.ident.to_string(), &item.attrs, |this| {
940             intravisit::walk_impl_item(this, item);
941         });
942     }
943
944     fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem) {
945         self.visit_testable(item.ident.to_string(), &item.attrs, |this| {
946             intravisit::walk_foreign_item(this, item);
947         });
948     }
949
950     fn visit_variant(&mut self,
951                      v: &'hir hir::Variant,
952                      g: &'hir hir::Generics,
953                      item_id: hir::HirId) {
954         self.visit_testable(v.node.ident.to_string(), &v.node.attrs, |this| {
955             intravisit::walk_variant(this, v, g, item_id);
956         });
957     }
958
959     fn visit_struct_field(&mut self, f: &'hir hir::StructField) {
960         self.visit_testable(f.ident.to_string(), &f.attrs, |this| {
961             intravisit::walk_struct_field(this, f);
962         });
963     }
964
965     fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef) {
966         self.visit_testable(macro_def.name.to_string(), &macro_def.attrs, |_| ());
967     }
968 }
969
970 #[cfg(test)]
971 mod tests;