]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/doctest.rs
Use better error message for hard errors in CTFE
[rust.git] / src / librustdoc / doctest.rs
1 use rustc_ast as ast;
2 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
3 use rustc_data_structures::sync::Lrc;
4 use rustc_errors::{ColorConfig, ErrorReported};
5 use rustc_hir as hir;
6 use rustc_hir::intravisit;
7 use rustc_hir::{HirId, CRATE_HIR_ID};
8 use rustc_interface::interface;
9 use rustc_middle::hir::map::Map;
10 use rustc_middle::ty::TyCtxt;
11 use rustc_session::config::{self, CrateType, ErrorOutputType};
12 use rustc_session::{lint, DiagnosticOutput, Session};
13 use rustc_span::edition::Edition;
14 use rustc_span::source_map::SourceMap;
15 use rustc_span::symbol::sym;
16 use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
17 use rustc_target::spec::TargetTriple;
18 use tempfile::Builder as TempFileBuilder;
19
20 use std::env;
21 use std::io::{self, Write};
22 use std::panic;
23 use std::path::PathBuf;
24 use std::process::{self, Command, Stdio};
25 use std::str;
26 use std::sync::atomic::{AtomicUsize, Ordering};
27 use std::sync::{Arc, Mutex};
28
29 use crate::clean::{types::AttributesExt, Attributes};
30 use crate::config::Options;
31 use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
32 use crate::lint::init_lints;
33 use crate::passes::span_of_attrs;
34
35 #[derive(Clone, Default)]
36 crate struct TestOptions {
37     /// Whether to disable the default `extern crate my_crate;` when creating doctests.
38     crate no_crate_inject: bool,
39     /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
40     /// the default `#![allow(unused)]`.
41     crate display_warnings: bool,
42     /// Additional crate-level attributes to add to doctests.
43     crate attrs: Vec<String>,
44 }
45
46 crate fn run(options: Options) -> Result<(), ErrorReported> {
47     let input = config::Input::File(options.input.clone());
48
49     let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
50
51     // See core::create_config for what's going on here.
52     let allowed_lints = vec![
53         invalid_codeblock_attributes_name.to_owned(),
54         lint::builtin::UNKNOWN_LINTS.name.to_owned(),
55         lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_owned(),
56     ];
57
58     let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| {
59         if lint.name == invalid_codeblock_attributes_name {
60             None
61         } else {
62             Some((lint.name_lower(), lint::Allow))
63         }
64     });
65
66     let crate_types =
67         if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
68
69     let sessopts = config::Options {
70         maybe_sysroot: options.maybe_sysroot.clone(),
71         search_paths: options.libs.clone(),
72         crate_types,
73         lint_opts: if !options.display_warnings { lint_opts } else { vec![] },
74         lint_cap: Some(options.lint_cap.clone().unwrap_or_else(|| lint::Forbid)),
75         cg: options.codegen_options.clone(),
76         externs: options.externs.clone(),
77         unstable_features: options.render_options.unstable_features,
78         actually_rustdoc: true,
79         edition: options.edition,
80         target_triple: options.target.clone(),
81         crate_name: options.crate_name.clone(),
82         ..config::Options::default()
83     };
84
85     let mut cfgs = options.cfgs.clone();
86     cfgs.push("doc".to_owned());
87     cfgs.push("doctest".to_owned());
88     let config = interface::Config {
89         opts: sessopts,
90         crate_cfg: interface::parse_cfgspecs(cfgs),
91         input,
92         input_path: None,
93         output_file: None,
94         output_dir: None,
95         file_loader: None,
96         diagnostic_output: DiagnosticOutput::Default,
97         stderr: None,
98         lint_caps,
99         parse_sess_created: None,
100         register_lints: Some(box crate::lint::register_lints),
101         override_queries: None,
102         make_codegen_backend: None,
103         registry: rustc_driver::diagnostics_registry(),
104     };
105
106     let mut test_args = options.test_args.clone();
107     let display_warnings = options.display_warnings;
108     let externs = options.externs.clone();
109     let json_unused_externs = options.json_unused_externs;
110
111     let res = interface::run_compiler(config, |compiler| {
112         compiler.enter(|queries| {
113             let _lower_to_hir = queries.lower_to_hir()?;
114
115             let crate_name = queries.crate_name()?.peek().to_string();
116             let mut global_ctxt = queries.global_ctxt()?.take();
117
118             let collector = global_ctxt.enter(|tcx| {
119                 let krate = tcx.hir().krate();
120                 let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID);
121
122                 let mut opts = scrape_test_config(crate_attrs);
123                 opts.display_warnings |= options.display_warnings;
124                 let enable_per_target_ignores = options.enable_per_target_ignores;
125                 let mut collector = Collector::new(
126                     crate_name,
127                     options,
128                     false,
129                     opts,
130                     Some(compiler.session().parse_sess.clone_source_map()),
131                     None,
132                     enable_per_target_ignores,
133                 );
134
135                 let mut hir_collector = HirCollector {
136                     sess: compiler.session(),
137                     collector: &mut collector,
138                     map: tcx.hir(),
139                     codes: ErrorCodes::from(
140                         compiler.session().opts.unstable_features.is_nightly_build(),
141                     ),
142                     tcx,
143                 };
144                 hir_collector.visit_testable(
145                     "".to_string(),
146                     CRATE_HIR_ID,
147                     krate.item.inner,
148                     |this| {
149                         intravisit::walk_crate(this, krate);
150                     },
151                 );
152
153                 collector
154             });
155             compiler.session().abort_if_errors();
156
157             let unused_extern_reports = collector.unused_extern_reports.clone();
158             let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
159             let ret: Result<_, ErrorReported> =
160                 Ok((collector.tests, unused_extern_reports, compiling_test_count));
161             ret
162         })
163     });
164     let (tests, unused_extern_reports, compiling_test_count) = match res {
165         Ok(res) => res,
166         Err(ErrorReported) => return Err(ErrorReported),
167     };
168
169     test_args.insert(0, "rustdoctest".to_string());
170
171     testing::test_main(
172         &test_args,
173         tests,
174         Some(testing::Options::new().display_output(display_warnings)),
175     );
176
177     // Collect and warn about unused externs, but only if we've gotten
178     // reports for each doctest
179     if json_unused_externs {
180         let unused_extern_reports: Vec<_> =
181             std::mem::take(&mut unused_extern_reports.lock().unwrap());
182         if unused_extern_reports.len() == compiling_test_count {
183             let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>();
184             let mut unused_extern_names = unused_extern_reports
185                 .iter()
186                 .map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>())
187                 .fold(extern_names, |uextsa, uextsb| {
188                     uextsa.intersection(&uextsb).map(|v| *v).collect::<FxHashSet<&String>>()
189                 })
190                 .iter()
191                 .map(|v| (*v).clone())
192                 .collect::<Vec<String>>();
193             unused_extern_names.sort();
194             // Take the most severe lint level
195             let lint_level = unused_extern_reports
196                 .iter()
197                 .map(|uexts| uexts.lint_level.as_str())
198                 .max_by_key(|v| match *v {
199                     "warn" => 1,
200                     "deny" => 2,
201                     "forbid" => 3,
202                     // The allow lint level is not expected,
203                     // as if allow is specified, no message
204                     // is to be emitted.
205                     v => unreachable!("Invalid lint level '{}'", v),
206                 })
207                 .unwrap_or("warn")
208                 .to_string();
209             let uext = UnusedExterns { lint_level, unused_extern_names };
210             let unused_extern_json = serde_json::to_string(&uext).unwrap();
211             eprintln!("{}", unused_extern_json);
212         }
213     }
214
215     Ok(())
216 }
217
218 // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
219 fn scrape_test_config(attrs: &[ast::Attribute]) -> TestOptions {
220     use rustc_ast_pretty::pprust;
221
222     let mut opts =
223         TestOptions { no_crate_inject: false, display_warnings: false, attrs: Vec::new() };
224
225     let test_attrs: Vec<_> = attrs
226         .iter()
227         .filter(|a| a.has_name(sym::doc))
228         .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
229         .filter(|a| a.has_name(sym::test))
230         .collect();
231     let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
232
233     for attr in attrs {
234         if attr.has_name(sym::no_crate_inject) {
235             opts.no_crate_inject = true;
236         }
237         if attr.has_name(sym::attr) {
238             if let Some(l) = attr.meta_item_list() {
239                 for item in l {
240                     opts.attrs.push(pprust::meta_list_item_to_string(item));
241                 }
242             }
243         }
244     }
245
246     opts
247 }
248
249 /// Documentation test failure modes.
250 enum TestFailure {
251     /// The test failed to compile.
252     CompileError,
253     /// The test is marked `compile_fail` but compiled successfully.
254     UnexpectedCompilePass,
255     /// The test failed to compile (as expected) but the compiler output did not contain all
256     /// expected error codes.
257     MissingErrorCodes(Vec<String>),
258     /// The test binary was unable to be executed.
259     ExecutionError(io::Error),
260     /// The test binary exited with a non-zero exit code.
261     ///
262     /// This typically means an assertion in the test failed or another form of panic occurred.
263     ExecutionFailure(process::Output),
264     /// The test is marked `should_panic` but the test binary executed successfully.
265     UnexpectedRunPass,
266 }
267
268 enum DirState {
269     Temp(tempfile::TempDir),
270     Perm(PathBuf),
271 }
272
273 impl DirState {
274     fn path(&self) -> &std::path::Path {
275         match self {
276             DirState::Temp(t) => t.path(),
277             DirState::Perm(p) => p.as_path(),
278         }
279     }
280 }
281
282 // NOTE: Keep this in sync with the equivalent structs in rustc
283 // and cargo.
284 // We could unify this struct the one in rustc but they have different
285 // ownership semantics, so doing so would create wasteful allocations.
286 #[derive(serde::Serialize, serde::Deserialize)]
287 struct UnusedExterns {
288     /// Lint level of the unused_crate_dependencies lint
289     lint_level: String,
290     /// List of unused externs by their names.
291     unused_extern_names: Vec<String>,
292 }
293
294 fn run_test(
295     test: &str,
296     cratename: &str,
297     line: usize,
298     options: Options,
299     should_panic: bool,
300     no_run: bool,
301     as_test_harness: bool,
302     runtool: Option<String>,
303     runtool_args: Vec<String>,
304     target: TargetTriple,
305     compile_fail: bool,
306     mut error_codes: Vec<String>,
307     opts: &TestOptions,
308     edition: Edition,
309     outdir: DirState,
310     path: PathBuf,
311     test_id: &str,
312     report_unused_externs: impl Fn(UnusedExterns),
313 ) -> Result<(), TestFailure> {
314     let (test, line_offset, supports_color) =
315         make_test(test, Some(cratename), as_test_harness, opts, edition, Some(test_id));
316
317     let output_file = outdir.path().join("rust_out");
318
319     let rustc_binary = options
320         .test_builder
321         .as_deref()
322         .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
323     let mut compiler = Command::new(&rustc_binary);
324     compiler.arg("--crate-type").arg("bin");
325     for cfg in &options.cfgs {
326         compiler.arg("--cfg").arg(&cfg);
327     }
328     if let Some(sysroot) = options.maybe_sysroot {
329         compiler.arg("--sysroot").arg(sysroot);
330     }
331     compiler.arg("--edition").arg(&edition.to_string());
332     compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
333     compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
334     compiler.arg("-o").arg(&output_file);
335     if as_test_harness {
336         compiler.arg("--test");
337     }
338     if options.json_unused_externs && !compile_fail {
339         compiler.arg("--error-format=json");
340         compiler.arg("--json").arg("unused-externs");
341         compiler.arg("-Z").arg("unstable-options");
342         compiler.arg("-W").arg("unused_crate_dependencies");
343     }
344     for lib_str in &options.lib_strs {
345         compiler.arg("-L").arg(&lib_str);
346     }
347     for extern_str in &options.extern_strs {
348         compiler.arg("--extern").arg(&extern_str);
349     }
350     compiler.arg("-Ccodegen-units=1");
351     for codegen_options_str in &options.codegen_options_strs {
352         compiler.arg("-C").arg(&codegen_options_str);
353     }
354     for debugging_option_str in &options.debugging_opts_strs {
355         compiler.arg("-Z").arg(&debugging_option_str);
356     }
357     if no_run && !compile_fail {
358         compiler.arg("--emit=metadata");
359     }
360     compiler.arg("--target").arg(match target {
361         TargetTriple::TargetTriple(s) => s,
362         TargetTriple::TargetPath(path) => {
363             path.to_str().expect("target path must be valid unicode").to_string()
364         }
365     });
366     if let ErrorOutputType::HumanReadable(kind) = options.error_format {
367         let (short, color_config) = kind.unzip();
368
369         if short {
370             compiler.arg("--error-format").arg("short");
371         }
372
373         match color_config {
374             ColorConfig::Never => {
375                 compiler.arg("--color").arg("never");
376             }
377             ColorConfig::Always => {
378                 compiler.arg("--color").arg("always");
379             }
380             ColorConfig::Auto => {
381                 compiler.arg("--color").arg(if supports_color { "always" } else { "never" });
382             }
383         }
384     }
385
386     compiler.arg("-");
387     compiler.stdin(Stdio::piped());
388     compiler.stderr(Stdio::piped());
389
390     let mut child = compiler.spawn().expect("Failed to spawn rustc process");
391     {
392         let stdin = child.stdin.as_mut().expect("Failed to open stdin");
393         stdin.write_all(test.as_bytes()).expect("could write out test sources");
394     }
395     let output = child.wait_with_output().expect("Failed to read stdout");
396
397     struct Bomb<'a>(&'a str);
398     impl Drop for Bomb<'_> {
399         fn drop(&mut self) {
400             eprint!("{}", self.0);
401         }
402     }
403     let mut out_lines = str::from_utf8(&output.stderr)
404         .unwrap()
405         .lines()
406         .filter(|l| {
407             if let Ok(uext) = serde_json::from_str::<UnusedExterns>(l) {
408                 report_unused_externs(uext);
409                 false
410             } else {
411                 true
412             }
413         })
414         .collect::<Vec<_>>();
415
416     // Add a \n to the end to properly terminate the last line,
417     // but only if there was output to be printed
418     if out_lines.len() > 0 {
419         out_lines.push("");
420     }
421
422     let out = out_lines.join("\n");
423     let _bomb = Bomb(&out);
424     match (output.status.success(), compile_fail) {
425         (true, true) => {
426             return Err(TestFailure::UnexpectedCompilePass);
427         }
428         (true, false) => {}
429         (false, true) => {
430             if !error_codes.is_empty() {
431                 // We used to check if the output contained "error[{}]: " but since we added the
432                 // colored output, we can't anymore because of the color escape characters before
433                 // the ":".
434                 error_codes.retain(|err| !out.contains(&format!("error[{}]", err)));
435
436                 if !error_codes.is_empty() {
437                     return Err(TestFailure::MissingErrorCodes(error_codes));
438                 }
439             }
440         }
441         (false, false) => {
442             return Err(TestFailure::CompileError);
443         }
444     }
445
446     if no_run {
447         return Ok(());
448     }
449
450     // Run the code!
451     let mut cmd;
452
453     if let Some(tool) = runtool {
454         cmd = Command::new(tool);
455         cmd.args(runtool_args);
456         cmd.arg(output_file);
457     } else {
458         cmd = Command::new(output_file);
459     }
460     if let Some(run_directory) = options.test_run_directory {
461         cmd.current_dir(run_directory);
462     }
463
464     match cmd.output() {
465         Err(e) => return Err(TestFailure::ExecutionError(e)),
466         Ok(out) => {
467             if should_panic && out.status.success() {
468                 return Err(TestFailure::UnexpectedRunPass);
469             } else if !should_panic && !out.status.success() {
470                 return Err(TestFailure::ExecutionFailure(out));
471             }
472         }
473     }
474
475     Ok(())
476 }
477
478 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
479 /// lines before the test code begins as well as if the output stream supports colors or not.
480 crate fn make_test(
481     s: &str,
482     cratename: Option<&str>,
483     dont_insert_main: bool,
484     opts: &TestOptions,
485     edition: Edition,
486     test_id: Option<&str>,
487 ) -> (String, usize, bool) {
488     let (crate_attrs, everything_else, crates) = partition_source(s);
489     let everything_else = everything_else.trim();
490     let mut line_offset = 0;
491     let mut prog = String::new();
492     let mut supports_color = false;
493
494     if opts.attrs.is_empty() && !opts.display_warnings {
495         // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
496         // lints that are commonly triggered in doctests. The crate-level test attributes are
497         // commonly used to make tests fail in case they trigger warnings, so having this there in
498         // that case may cause some tests to pass when they shouldn't have.
499         prog.push_str("#![allow(unused)]\n");
500         line_offset += 1;
501     }
502
503     // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
504     for attr in &opts.attrs {
505         prog.push_str(&format!("#![{}]\n", attr));
506         line_offset += 1;
507     }
508
509     // Now push any outer attributes from the example, assuming they
510     // are intended to be crate attributes.
511     prog.push_str(&crate_attrs);
512     prog.push_str(&crates);
513
514     // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
515     // crate already is included.
516     let result = rustc_driver::catch_fatal_errors(|| {
517         rustc_span::with_session_globals(edition, || {
518             use rustc_errors::emitter::{Emitter, EmitterWriter};
519             use rustc_errors::Handler;
520             use rustc_parse::maybe_new_parser_from_source_str;
521             use rustc_parse::parser::ForceCollect;
522             use rustc_session::parse::ParseSess;
523             use rustc_span::source_map::FilePathMapping;
524
525             let filename = FileName::anon_source_code(s);
526             let source = crates + everything_else;
527
528             // Any errors in parsing should also appear when the doctest is compiled for real, so just
529             // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
530             let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
531             supports_color =
532                 EmitterWriter::stderr(ColorConfig::Auto, None, false, false, Some(80), false)
533                     .supports_color();
534
535             let emitter =
536                 EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
537
538             // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
539             let handler = Handler::with_emitter(false, None, box emitter);
540             let sess = ParseSess::with_span_handler(handler, sm);
541
542             let mut found_main = false;
543             let mut found_extern_crate = cratename.is_none();
544             let mut found_macro = false;
545
546             let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
547                 Ok(p) => p,
548                 Err(errs) => {
549                     for mut err in errs {
550                         err.cancel();
551                     }
552
553                     return (found_main, found_extern_crate, found_macro);
554                 }
555             };
556
557             loop {
558                 match parser.parse_item(ForceCollect::No) {
559                     Ok(Some(item)) => {
560                         if !found_main {
561                             if let ast::ItemKind::Fn(..) = item.kind {
562                                 if item.ident.name == sym::main {
563                                     found_main = true;
564                                 }
565                             }
566                         }
567
568                         if !found_extern_crate {
569                             if let ast::ItemKind::ExternCrate(original) = item.kind {
570                                 // This code will never be reached if `cratename` is none because
571                                 // `found_extern_crate` is initialized to `true` if it is none.
572                                 let cratename = cratename.unwrap();
573
574                                 match original {
575                                     Some(name) => found_extern_crate = name.as_str() == cratename,
576                                     None => found_extern_crate = item.ident.as_str() == cratename,
577                                 }
578                             }
579                         }
580
581                         if !found_macro {
582                             if let ast::ItemKind::MacCall(..) = item.kind {
583                                 found_macro = true;
584                             }
585                         }
586
587                         if found_main && found_extern_crate {
588                             break;
589                         }
590                     }
591                     Ok(None) => break,
592                     Err(mut e) => {
593                         e.cancel();
594                         break;
595                     }
596                 }
597             }
598
599             // Reset errors so that they won't be reported as compiler bugs when dropping the
600             // handler. Any errors in the tests will be reported when the test file is compiled,
601             // Note that we still need to cancel the errors above otherwise `DiagnosticBuilder`
602             // will panic on drop.
603             sess.span_diagnostic.reset_err_count();
604
605             (found_main, found_extern_crate, found_macro)
606         })
607     });
608     let (already_has_main, already_has_extern_crate, found_macro) = match result {
609         Ok(result) => result,
610         Err(ErrorReported) => {
611             // If the parser panicked due to a fatal error, pass the test code through unchanged.
612             // The error will be reported during compilation.
613             return (s.to_owned(), 0, false);
614         }
615     };
616
617     // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
618     // see it. In that case, run the old text-based scan to see if they at least have a main
619     // function written inside a macro invocation. See
620     // https://github.com/rust-lang/rust/issues/56898
621     let already_has_main = if found_macro && !already_has_main {
622         s.lines()
623             .map(|line| {
624                 let comment = line.find("//");
625                 if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
626             })
627             .any(|code| code.contains("fn main"))
628     } else {
629         already_has_main
630     };
631
632     // Don't inject `extern crate std` because it's already injected by the
633     // compiler.
634     if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
635         if let Some(cratename) = cratename {
636             // Don't inject `extern crate` if the crate is never used.
637             // NOTE: this is terribly inaccurate because it doesn't actually
638             // parse the source, but only has false positives, not false
639             // negatives.
640             if s.contains(cratename) {
641                 prog.push_str(&format!("extern crate r#{};\n", cratename));
642                 line_offset += 1;
643             }
644         }
645     }
646
647     // FIXME: This code cannot yet handle no_std test cases yet
648     if dont_insert_main || already_has_main || prog.contains("![no_std]") {
649         prog.push_str(everything_else);
650     } else {
651         let returns_result = everything_else.trim_end().ends_with("(())");
652         // Give each doctest main function a unique name.
653         // This is for example needed for the tooling around `-Z instrument-coverage`.
654         let inner_fn_name = if let Some(test_id) = test_id {
655             format!("_doctest_main_{}", test_id)
656         } else {
657             "_inner".into()
658         };
659         let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
660         let (main_pre, main_post) = if returns_result {
661             (
662                 format!(
663                     "fn main() {{ {}fn {}() -> Result<(), impl core::fmt::Debug> {{\n",
664                     inner_attr, inner_fn_name
665                 ),
666                 format!("\n}} {}().unwrap() }}", inner_fn_name),
667             )
668         } else if test_id.is_some() {
669             (
670                 format!("fn main() {{ {}fn {}() {{\n", inner_attr, inner_fn_name),
671                 format!("\n}} {}() }}", inner_fn_name),
672             )
673         } else {
674             ("fn main() {\n".into(), "\n}".into())
675         };
676         // Note on newlines: We insert a line/newline *before*, and *after*
677         // the doctest and adjust the `line_offset` accordingly.
678         // In the case of `-Z instrument-coverage`, this means that the generated
679         // inner `main` function spans from the doctest opening codeblock to the
680         // closing one. For example
681         // /// ``` <- start of the inner main
682         // /// <- code under doctest
683         // /// ``` <- end of the inner main
684         line_offset += 1;
685
686         prog.extend([&main_pre, everything_else, &main_post].iter().cloned());
687     }
688
689     debug!("final doctest:\n{}", prog);
690
691     (prog, line_offset, supports_color)
692 }
693
694 // FIXME(aburka): use a real parser to deal with multiline attributes
695 fn partition_source(s: &str) -> (String, String, String) {
696     #[derive(Copy, Clone, PartialEq)]
697     enum PartitionState {
698         Attrs,
699         Crates,
700         Other,
701     }
702     let mut state = PartitionState::Attrs;
703     let mut before = String::new();
704     let mut crates = String::new();
705     let mut after = String::new();
706
707     for line in s.lines() {
708         let trimline = line.trim();
709
710         // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
711         // shunted into "everything else"
712         match state {
713             PartitionState::Attrs => {
714                 state = if trimline.starts_with("#![")
715                     || trimline.chars().all(|c| c.is_whitespace())
716                     || (trimline.starts_with("//") && !trimline.starts_with("///"))
717                 {
718                     PartitionState::Attrs
719                 } else if trimline.starts_with("extern crate")
720                     || trimline.starts_with("#[macro_use] extern crate")
721                 {
722                     PartitionState::Crates
723                 } else {
724                     PartitionState::Other
725                 };
726             }
727             PartitionState::Crates => {
728                 state = if trimline.starts_with("extern crate")
729                     || trimline.starts_with("#[macro_use] extern crate")
730                     || trimline.chars().all(|c| c.is_whitespace())
731                     || (trimline.starts_with("//") && !trimline.starts_with("///"))
732                 {
733                     PartitionState::Crates
734                 } else {
735                     PartitionState::Other
736                 };
737             }
738             PartitionState::Other => {}
739         }
740
741         match state {
742             PartitionState::Attrs => {
743                 before.push_str(line);
744                 before.push('\n');
745             }
746             PartitionState::Crates => {
747                 crates.push_str(line);
748                 crates.push('\n');
749             }
750             PartitionState::Other => {
751                 after.push_str(line);
752                 after.push('\n');
753             }
754         }
755     }
756
757     debug!("before:\n{}", before);
758     debug!("crates:\n{}", crates);
759     debug!("after:\n{}", after);
760
761     (before, after, crates)
762 }
763
764 crate trait Tester {
765     fn add_test(&mut self, test: String, config: LangString, line: usize);
766     fn get_line(&self) -> usize {
767         0
768     }
769     fn register_header(&mut self, _name: &str, _level: u32) {}
770 }
771
772 crate struct Collector {
773     crate tests: Vec<testing::TestDescAndFn>,
774
775     // The name of the test displayed to the user, separated by `::`.
776     //
777     // In tests from Rust source, this is the path to the item
778     // e.g., `["std", "vec", "Vec", "push"]`.
779     //
780     // In tests from a markdown file, this is the titles of all headers (h1~h6)
781     // of the sections that contain the code block, e.g., if the markdown file is
782     // written as:
783     //
784     // ``````markdown
785     // # Title
786     //
787     // ## Subtitle
788     //
789     // ```rust
790     // assert!(true);
791     // ```
792     // ``````
793     //
794     // the `names` vector of that test will be `["Title", "Subtitle"]`.
795     names: Vec<String>,
796
797     options: Options,
798     use_headers: bool,
799     enable_per_target_ignores: bool,
800     cratename: String,
801     opts: TestOptions,
802     position: Span,
803     source_map: Option<Lrc<SourceMap>>,
804     filename: Option<PathBuf>,
805     visited_tests: FxHashMap<(String, usize), usize>,
806     unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
807     compiling_test_count: AtomicUsize,
808 }
809
810 impl Collector {
811     crate fn new(
812         cratename: String,
813         options: Options,
814         use_headers: bool,
815         opts: TestOptions,
816         source_map: Option<Lrc<SourceMap>>,
817         filename: Option<PathBuf>,
818         enable_per_target_ignores: bool,
819     ) -> Collector {
820         Collector {
821             tests: Vec::new(),
822             names: Vec::new(),
823             options,
824             use_headers,
825             enable_per_target_ignores,
826             cratename,
827             opts,
828             position: DUMMY_SP,
829             source_map,
830             filename,
831             visited_tests: FxHashMap::default(),
832             unused_extern_reports: Default::default(),
833             compiling_test_count: AtomicUsize::new(0),
834         }
835     }
836
837     fn generate_name(&self, line: usize, filename: &FileName) -> String {
838         let mut item_path = self.names.join("::");
839         if !item_path.is_empty() {
840             item_path.push(' ');
841         }
842         format!("{} - {}(line {})", filename.prefer_local(), item_path, line)
843     }
844
845     crate fn set_position(&mut self, position: Span) {
846         self.position = position;
847     }
848
849     fn get_filename(&self) -> FileName {
850         if let Some(ref source_map) = self.source_map {
851             let filename = source_map.span_to_filename(self.position);
852             if let FileName::Real(ref filename) = filename {
853                 if let Ok(cur_dir) = env::current_dir() {
854                     if let Some(local_path) = filename.local_path() {
855                         if let Ok(path) = local_path.strip_prefix(&cur_dir) {
856                             return path.to_owned().into();
857                         }
858                     }
859                 }
860             }
861             filename
862         } else if let Some(ref filename) = self.filename {
863             filename.clone().into()
864         } else {
865             FileName::Custom("input".to_owned())
866         }
867     }
868 }
869
870 impl Tester for Collector {
871     fn add_test(&mut self, test: String, config: LangString, line: usize) {
872         let filename = self.get_filename();
873         let name = self.generate_name(line, &filename);
874         let cratename = self.cratename.to_string();
875         let opts = self.opts.clone();
876         let edition = config.edition.unwrap_or(self.options.edition);
877         let options = self.options.clone();
878         let runtool = self.options.runtool.clone();
879         let runtool_args = self.options.runtool_args.clone();
880         let target = self.options.target.clone();
881         let target_str = target.to_string();
882         let unused_externs = self.unused_extern_reports.clone();
883         let no_run = config.no_run || options.no_run;
884         if !config.compile_fail {
885             self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
886         }
887
888         let path = match &filename {
889             FileName::Real(path) => {
890                 if let Some(local_path) = path.local_path() {
891                     local_path.to_path_buf()
892                 } else {
893                     // Somehow we got the filename from the metadata of another crate, should never happen
894                     unreachable!("doctest from a different crate");
895                 }
896             }
897             _ => PathBuf::from(r"doctest.rs"),
898         };
899
900         // For example `module/file.rs` would become `module_file_rs`
901         let file = filename
902             .prefer_local()
903             .to_string_lossy()
904             .chars()
905             .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
906             .collect::<String>();
907         let test_id = format!(
908             "{file}_{line}_{number}",
909             file = file,
910             line = line,
911             number = {
912                 // Increases the current test number, if this file already
913                 // exists or it creates a new entry with a test number of 0.
914                 self.visited_tests.entry((file.clone(), line)).and_modify(|v| *v += 1).or_insert(0)
915             },
916         );
917         let outdir = if let Some(mut path) = options.persist_doctests.clone() {
918             path.push(&test_id);
919
920             std::fs::create_dir_all(&path)
921                 .expect("Couldn't create directory for doctest executables");
922
923             DirState::Perm(path)
924         } else {
925             DirState::Temp(
926                 TempFileBuilder::new()
927                     .prefix("rustdoctest")
928                     .tempdir()
929                     .expect("rustdoc needs a tempdir"),
930             )
931         };
932
933         debug!("creating test {}: {}", name, test);
934         self.tests.push(testing::TestDescAndFn {
935             desc: testing::TestDesc {
936                 name: testing::DynTestName(name),
937                 ignore: match config.ignore {
938                     Ignore::All => true,
939                     Ignore::None => false,
940                     Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
941                 },
942                 // compiler failures are test failures
943                 should_panic: testing::ShouldPanic::No,
944                 allow_fail: config.allow_fail,
945                 #[cfg(not(bootstrap))]
946                 compile_fail: config.compile_fail,
947                 #[cfg(not(bootstrap))]
948                 no_run,
949                 test_type: testing::TestType::DocTest,
950             },
951             testfn: testing::DynTestFn(box move || {
952                 let report_unused_externs = |uext| {
953                     unused_externs.lock().unwrap().push(uext);
954                 };
955                 let res = run_test(
956                     &test,
957                     &cratename,
958                     line,
959                     options,
960                     config.should_panic,
961                     no_run,
962                     config.test_harness,
963                     runtool,
964                     runtool_args,
965                     target,
966                     config.compile_fail,
967                     config.error_codes,
968                     &opts,
969                     edition,
970                     outdir,
971                     path,
972                     &test_id,
973                     report_unused_externs,
974                 );
975
976                 if let Err(err) = res {
977                     match err {
978                         TestFailure::CompileError => {
979                             eprint!("Couldn't compile the test.");
980                         }
981                         TestFailure::UnexpectedCompilePass => {
982                             eprint!("Test compiled successfully, but it's marked `compile_fail`.");
983                         }
984                         TestFailure::UnexpectedRunPass => {
985                             eprint!("Test executable succeeded, but it's marked `should_panic`.");
986                         }
987                         TestFailure::MissingErrorCodes(codes) => {
988                             eprint!("Some expected error codes were not found: {:?}", codes);
989                         }
990                         TestFailure::ExecutionError(err) => {
991                             eprint!("Couldn't run the test: {}", err);
992                             if err.kind() == io::ErrorKind::PermissionDenied {
993                                 eprint!(" - maybe your tempdir is mounted with noexec?");
994                             }
995                         }
996                         TestFailure::ExecutionFailure(out) => {
997                             let reason = if let Some(code) = out.status.code() {
998                                 format!("exit code {}", code)
999                             } else {
1000                                 String::from("terminated by signal")
1001                             };
1002
1003                             eprintln!("Test executable failed ({}).", reason);
1004
1005                             // FIXME(#12309): An unfortunate side-effect of capturing the test
1006                             // executable's output is that the relative ordering between the test's
1007                             // stdout and stderr is lost. However, this is better than the
1008                             // alternative: if the test executable inherited the parent's I/O
1009                             // handles the output wouldn't be captured at all, even on success.
1010                             //
1011                             // The ordering could be preserved if the test process' stderr was
1012                             // redirected to stdout, but that functionality does not exist in the
1013                             // standard library, so it may not be portable enough.
1014                             let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
1015                             let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
1016
1017                             if !stdout.is_empty() || !stderr.is_empty() {
1018                                 eprintln!();
1019
1020                                 if !stdout.is_empty() {
1021                                     eprintln!("stdout:\n{}", stdout);
1022                                 }
1023
1024                                 if !stderr.is_empty() {
1025                                     eprintln!("stderr:\n{}", stderr);
1026                                 }
1027                             }
1028                         }
1029                     }
1030
1031                     panic::resume_unwind(box ());
1032                 }
1033             }),
1034         });
1035     }
1036
1037     fn get_line(&self) -> usize {
1038         if let Some(ref source_map) = self.source_map {
1039             let line = self.position.lo().to_usize();
1040             let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
1041             if line > 0 { line - 1 } else { line }
1042         } else {
1043             0
1044         }
1045     }
1046
1047     fn register_header(&mut self, name: &str, level: u32) {
1048         if self.use_headers {
1049             // We use these headings as test names, so it's good if
1050             // they're valid identifiers.
1051             let name = name
1052                 .chars()
1053                 .enumerate()
1054                 .map(|(i, c)| {
1055                     if (i == 0 && rustc_lexer::is_id_start(c))
1056                         || (i != 0 && rustc_lexer::is_id_continue(c))
1057                     {
1058                         c
1059                     } else {
1060                         '_'
1061                     }
1062                 })
1063                 .collect::<String>();
1064
1065             // Here we try to efficiently assemble the header titles into the
1066             // test name in the form of `h1::h2::h3::h4::h5::h6`.
1067             //
1068             // Suppose that originally `self.names` contains `[h1, h2, h3]`...
1069             let level = level as usize;
1070             if level <= self.names.len() {
1071                 // ... Consider `level == 2`. All headers in the lower levels
1072                 // are irrelevant in this new level. So we should reset
1073                 // `self.names` to contain headers until <h2>, and replace that
1074                 // slot with the new name: `[h1, name]`.
1075                 self.names.truncate(level);
1076                 self.names[level - 1] = name;
1077             } else {
1078                 // ... On the other hand, consider `level == 5`. This means we
1079                 // need to extend `self.names` to contain five headers. We fill
1080                 // in the missing level (<h4>) with `_`. Thus `self.names` will
1081                 // become `[h1, h2, h3, "_", name]`.
1082                 if level - 1 > self.names.len() {
1083                     self.names.resize(level - 1, "_".to_owned());
1084                 }
1085                 self.names.push(name);
1086             }
1087         }
1088     }
1089 }
1090
1091 struct HirCollector<'a, 'hir, 'tcx> {
1092     sess: &'a Session,
1093     collector: &'a mut Collector,
1094     map: Map<'hir>,
1095     codes: ErrorCodes,
1096     tcx: TyCtxt<'tcx>,
1097 }
1098
1099 impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
1100     fn visit_testable<F: FnOnce(&mut Self)>(
1101         &mut self,
1102         name: String,
1103         hir_id: HirId,
1104         sp: Span,
1105         nested: F,
1106     ) {
1107         let ast_attrs = self.tcx.hir().attrs(hir_id);
1108         let mut attrs = Attributes::from_ast(ast_attrs, None);
1109
1110         if let Some(ref cfg) = ast_attrs.cfg(self.sess) {
1111             if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
1112                 return;
1113             }
1114         }
1115
1116         let has_name = !name.is_empty();
1117         if has_name {
1118             self.collector.names.push(name);
1119         }
1120
1121         attrs.unindent_doc_comments();
1122         // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
1123         // anything else, this will combine them for us.
1124         if let Some(doc) = attrs.collapsed_doc_value() {
1125             // Use the outermost invocation, so that doctest names come from where the docs were written.
1126             let span = ast_attrs
1127                 .span()
1128                 .map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
1129                 .unwrap_or(DUMMY_SP);
1130             self.collector.set_position(span);
1131             markdown::find_testable_code(
1132                 &doc,
1133                 self.collector,
1134                 self.codes,
1135                 self.collector.enable_per_target_ignores,
1136                 Some(&crate::html::markdown::ExtraInfo::new(
1137                     self.tcx,
1138                     hir_id,
1139                     span_of_attrs(&attrs).unwrap_or(sp),
1140                 )),
1141             );
1142         }
1143
1144         nested(self);
1145
1146         if has_name {
1147             self.collector.names.pop();
1148         }
1149     }
1150 }
1151
1152 impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> {
1153     type Map = Map<'hir>;
1154
1155     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
1156         intravisit::NestedVisitorMap::All(self.map)
1157     }
1158
1159     fn visit_item(&mut self, item: &'hir hir::Item<'_>) {
1160         let name = if let hir::ItemKind::Impl(impl_) = &item.kind {
1161             rustc_hir_pretty::id_to_string(&self.map, impl_.self_ty.hir_id)
1162         } else {
1163             item.ident.to_string()
1164         };
1165
1166         self.visit_testable(name, item.hir_id(), item.span, |this| {
1167             intravisit::walk_item(this, item);
1168         });
1169     }
1170
1171     fn visit_trait_item(&mut self, item: &'hir hir::TraitItem<'_>) {
1172         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
1173             intravisit::walk_trait_item(this, item);
1174         });
1175     }
1176
1177     fn visit_impl_item(&mut self, item: &'hir hir::ImplItem<'_>) {
1178         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
1179             intravisit::walk_impl_item(this, item);
1180         });
1181     }
1182
1183     fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem<'_>) {
1184         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
1185             intravisit::walk_foreign_item(this, item);
1186         });
1187     }
1188
1189     fn visit_variant(
1190         &mut self,
1191         v: &'hir hir::Variant<'_>,
1192         g: &'hir hir::Generics<'_>,
1193         item_id: hir::HirId,
1194     ) {
1195         self.visit_testable(v.ident.to_string(), v.id, v.span, |this| {
1196             intravisit::walk_variant(this, v, g, item_id);
1197         });
1198     }
1199
1200     fn visit_field_def(&mut self, f: &'hir hir::FieldDef<'_>) {
1201         self.visit_testable(f.ident.to_string(), f.hir_id, f.span, |this| {
1202             intravisit::walk_field_def(this, f);
1203         });
1204     }
1205
1206     fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef<'_>) {
1207         self.visit_testable(
1208             macro_def.ident.to_string(),
1209             macro_def.hir_id(),
1210             macro_def.span,
1211             |_| (),
1212         );
1213     }
1214 }
1215
1216 #[cfg(test)]
1217 mod tests;