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.
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.
12 use errors::emitter::ColorConfig;
13 use rustc_data_structures::sync::Lrc;
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;
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};
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;
35 use std::ffi::OsString;
36 use std::io::prelude::*;
38 use std::path::PathBuf;
39 use std::panic::{self, AssertUnwindSafe};
40 use std::process::Command;
42 use std::sync::{Arc, Mutex};
44 use clean::Attributes;
46 use html::markdown::{self, ErrorCodes, LangString};
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>,
59 pub fn run(mut options: Options) -> isize {
60 let input = config::Input::File(options.input.clone());
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()
75 edition: options.edition,
76 ..config::Options::default()
78 driver::spawn_thread_pool(sessopts, |sessopts| {
79 let source_map = Lrc::new(SourceMap::new(sessopts.file_path_mapping()));
81 errors::Handler::with_tty_emitter(ColorConfig::Auto,
83 Some(source_map.clone()));
85 let mut sess = session::build_session_(
86 sessopts, Some(options.input), handler, source_map.clone(),
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));
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;
97 let krate = panictry!(driver::phase_1_parse_input(&driver::CompileController::basic(),
100 let driver::ExpansionResult { defs, mut hir_forest, .. } = {
101 phase_2_configure_and_expand(
110 ).expect("phase_2_configure_and_expand aborted in rustdoc!")
113 let crate_name = options.crate_name.unwrap_or_else(|| {
114 ::rustc_codegen_utils::link::find_crate_name(None, &hir_forest.krate().attrs, &input)
116 let mut opts = scrape_test_config(hir_forest.krate());
117 opts.display_warnings |= options.display_warnings;
118 let mut collector = Collector::new(
122 options.codegen_options,
126 options.maybe_sysroot,
134 let map = hir::map::map_crate(&sess, &cstore, &mut hir_forest, &defs);
135 let krate = map.krate();
136 let mut hir_collector = HirCollector {
138 collector: &mut collector,
140 codes: ErrorCodes::from(sess.opts.unstable_features.is_nightly_build()),
142 hir_collector.visit_testable("".to_string(), &krate.attrs, |this| {
143 intravisit::walk_crate(this, krate);
147 options.test_args.insert(0, "rustdoctest".to_string());
149 testing::test_main(&options.test_args,
150 collector.tests.into_iter().collect(),
151 testing::Options::new().display_output(options.display_warnings));
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;
160 let mut opts = TestOptions {
161 no_crate_inject: false,
162 display_warnings: false,
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"))
171 let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
174 if attr.check_name("no_crate_inject") {
175 opts.no_crate_inject = true;
177 if attr.check_name("attr") {
178 if let Some(l) = attr.meta_item_list() {
180 opts.attrs.push(pprust::meta_list_item_to_string(item));
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"),
205 let input = config::Input::Str {
206 name: FileName::DocTest(path, line as isize - line_offset as isize),
209 let outputs = OutputTypes::new(&[(OutputType::Exe, None)]);
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())),
215 crate_types: vec![config::CrateType::Executable],
216 output_types: outputs,
218 cg: config::CodegenOptions {
222 test: as_test_harness,
223 unstable_features: UnstableFeatures::from_environment(),
224 debugging_opts: config::DebuggingOptions {
225 ..config::basic_debugging_options()
228 ..config::Options::default()
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.
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.
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)
246 fn flush(&mut self) -> io::Result<()> { Ok(()) }
248 struct Bomb(Arc<Mutex<Vec<u8>>>, Box<dyn Write+Send>);
251 let _ = self.1.write_all(&self.0.lock().unwrap());
254 let data = Arc::new(Mutex::new(Vec::new()));
256 let old = io::set_panic(Some(box Sink(data.clone())));
257 let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
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()),
267 let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
269 let mut sess = session::build_session_(
270 sessopts, None, diagnostic_handler, source_map,
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));
276 let outdir = Mutex::new(
277 TempFileBuilder::new().prefix("rustdoctest").tempdir().expect("rustdoc needs a tempdir")
279 let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
280 let mut control = driver::CompileController::basic();
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;
286 let out = Some(outdir.lock().unwrap().path().to_path_buf());
289 control.after_analysis.stop = Compilation::Stop;
292 let res = panic::catch_unwind(AssertUnwindSafe(|| {
293 driver::compile_input(
306 let compile_result = match res {
307 Ok(Ok(())) | Ok(Err(CompileIncomplete::Stopped)) => Ok(()),
308 Err(_) | Ok(Err(CompileIncomplete::Errored(_))) => Err(())
311 (libdir, outdir, compile_result)
314 match (compile_result, compile_fail) {
316 panic!("test compiled while it wasn't supposed to")
318 (Ok(()), false) => {}
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));
325 (Err(()), false) => {
326 panic!("couldn't compile the test")
330 if error_codes.len() > 0 {
331 panic!("Some expected error codes were not found: {:?}", error_codes);
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
342 let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
343 let var = DynamicLibrary::envvar();
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()
350 cmd.env(var, &newpath);
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?"
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(""));
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,
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();
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");
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));
395 // Now push any outer attributes from the example, assuming they
396 // are intended to be crate attributes.
397 prog.push_str(&crate_attrs);
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;
407 let filename = FileName::anon_source_code(s);
408 let source = crates + &everything_else;
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);
417 let mut found_main = false;
418 let mut found_extern_crate = cratename.is_none();
420 let mut parser = match parse::maybe_new_parser_from_source_str(&sess, filename, source) {
423 for mut err in errs {
427 return (found_main, found_extern_crate);
432 match parser.parse_item() {
435 if let ast::ItemKind::Fn(..) = item.node {
436 if item.ident.as_str() == "main" {
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();
449 Some(name) => found_extern_crate = name.as_str() == cratename,
450 None => found_extern_crate = item.ident.as_str() == cratename,
455 if found_main && found_extern_crate {
467 (found_main, found_extern_crate)
470 // Don't inject `extern crate std` because it's already injected by the
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));
482 if dont_insert_main || already_has_main {
483 prog.push_str(everything_else);
485 prog.push_str("fn main() {\n");
487 prog.push_str(everything_else);
488 prog.push_str("\n}");
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();
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 {
509 after.push_str(line);
510 after.push_str("\n");
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");
517 before.push_str(line);
518 before.push_str("\n");
522 (before, after, crates)
526 fn add_test(&mut self, test: String, config: LangString, line: usize);
527 fn get_line(&self) -> usize {
530 fn register_header(&mut self, _name: &str, _level: u32) {}
533 pub struct Collector {
534 pub tests: Vec<testing::TestDescAndFn>,
536 // The name of the test displayed to the user, separated by `::`.
538 // In tests from Rust source, this is the path to the item
539 // e.g., `["std", "vec", "Vec", "push"]`.
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
555 // the `names` vector of that test will be `["Title", "Subtitle"]`.
565 maybe_sysroot: Option<PathBuf>,
567 source_map: Option<Lrc<SourceMap>>,
568 filename: Option<PathBuf>,
569 linker: Option<PathBuf>,
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 {
597 fn generate_name(&self, line: usize, filename: &FileName) -> String {
598 format!("{} - {} (line {})", filename, self.names.join("::"), line)
601 pub fn set_position(&mut self, position: Span) {
602 self.position = position;
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();
616 } else if let Some(ref filename) = self.filename {
617 filename.clone().into()
619 FileName::Custom("input".to_owned())
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,
646 testfn: testing::DynTestFn(box move || {
647 let panic = io::set_panic(None);
648 let print = io::set_print(None);
650 rustc_driver::in_named_rustc_thread(name, move || with_globals(move || {
651 io::set_panic(panic);
652 io::set_print(print);
673 Err(err) => panic::resume_unwind(err),
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 }
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()) {
700 }).collect::<String>();
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`.
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;
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());
722 self.names.push(name);
728 struct HirCollector<'a, 'hir: 'a> {
729 sess: &'a session::Session,
730 collector: &'a mut Collector,
731 map: &'a hir::map::Map<'hir>,
735 impl<'a, 'hir> HirCollector<'a, 'hir> {
736 fn visit_testable<F: FnOnce(&mut Self)>(&mut self,
738 attrs: &[ast::Attribute],
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())) {
747 let has_name = !name.is_empty();
749 self.collector.names.push(name);
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),
768 self.collector.names.pop();
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)
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)
782 item.name.to_string()
785 self.visit_testable(name, &item.attrs, |this| {
786 intravisit::walk_item(this, item);
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);
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);
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);
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);
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);
823 fn visit_macro_def(&mut self, macro_def: &'hir hir::MacroDef) {
824 self.visit_testable(macro_def.name.to_string(), ¯o_def.attrs, |_| ());
830 use super::{TestOptions, make_test};
833 fn make_test_basic() {
834 //basic use: wraps with `fn main`, adds `#![allow(unused)]`
835 let opts = TestOptions::default();
837 "assert_eq!(2+2, 4);";
843 let output = make_test(input, None, false, &opts);
844 assert_eq!(output, (expected, 2));
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();
853 "assert_eq!(2+2, 4);";
859 let output = make_test(input, Some("asdf"), false, &opts);
860 assert_eq!(output, (expected, 2));
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();
870 assert_eq!(2+2, 4);";
878 let output = make_test(input, Some("asdf"), false, &opts);
879 assert_eq!(output, (expected, 3));
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
886 let opts = TestOptions {
887 no_crate_inject: true,
888 display_warnings: false,
893 assert_eq!(2+2, 4);";
900 let output = make_test(input, Some("asdf"), false, &opts);
901 assert_eq!(output, (expected, 2));
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
909 let opts = TestOptions::default();
912 assert_eq!(2+2, 4);";
919 let output = make_test(input, Some("std"), false, &opts);
920 assert_eq!(output, (expected, 2));
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();
931 assert_eq!(2+2, 4);";
939 let output = make_test(input, Some("asdf"), false, &opts);
940 assert_eq!(output, (expected, 2));
944 fn make_test_manual_extern_crate_with_macro_use() {
945 let opts = TestOptions::default();
947 "#[macro_use] extern crate asdf;
949 assert_eq!(2+2, 4);";
952 #[macro_use] extern crate asdf;
957 let output = make_test(input, Some("asdf"), false, &opts);
958 assert_eq!(output, (expected, 2));
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());
969 assert_eq!(2+2, 4);";
971 "#![feature(sick_rad)]
977 let output = make_test(input, Some("asdf"), false, &opts);
978 assert_eq!(output, (expected, 3));
980 // Adding more will also bump the returned line offset.
981 opts.attrs.push("feature(hella_dope)".to_string());
983 "#![feature(sick_rad)]
984 #![feature(hella_dope)]
990 let output = make_test(input, Some("asdf"), false, &opts);
991 assert_eq!(output, (expected, 4));
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();
1000 "#![feature(sick_rad)]
1001 assert_eq!(2+2, 4);";
1004 #![feature(sick_rad)]
1008 let output = make_test(input, None, false, &opts);
1009 assert_eq!(output, (expected, 2));
1013 fn make_test_with_main() {
1014 // Including your own `fn main` wrapper lets the test use it verbatim.
1015 let opts = TestOptions::default();
1025 let output = make_test(input, None, false, &opts);
1026 assert_eq!(output, (expected, 1));
1030 fn make_test_fake_main() {
1031 // ... but putting it in a comment will still provide a wrapper.
1032 let opts = TestOptions::default();
1034 "//Ceci n'est pas une `fn main`
1035 assert_eq!(2+2, 4);";
1039 //Ceci n'est pas une `fn main`
1042 let output = make_test(input, None, false, &opts);
1043 assert_eq!(output, (expected, 2));
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();
1051 "//Ceci n'est pas une `fn main`
1052 assert_eq!(2+2, 4);";
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));
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;
1067 "assert_eq!(2+2, 4);";
1072 let output = make_test(input, None, false, &opts);
1073 assert_eq!(output, (expected, 1));
1077 fn make_test_issues_21299_33731() {
1078 let opts = TestOptions::default();
1082 assert_eq!(2+2, 4);";
1091 let output = make_test(input, None, false, &opts);
1092 assert_eq!(output, (expected, 2));
1095 "extern crate hella_qwop;
1096 assert_eq!(asdf::foo, 4);";
1100 extern crate hella_qwop;
1103 assert_eq!(asdf::foo, 4);
1106 let output = make_test(input, Some("asdf"), false, &opts);
1107 assert_eq!(output, (expected, 3));