about summary refs log tree commit diff
path: root/src/librustdoc/test.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/test.rs')
-rw-r--r--src/librustdoc/test.rs143
1 files changed, 91 insertions, 52 deletions
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 7fa1b38bdad..74a16cb867d 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -27,11 +27,10 @@ use rustc::hir::intravisit;
 use rustc::session::{self, CompileIncomplete, config};
 use rustc::session::config::{OutputType, OutputTypes, Externs};
 use rustc::session::search_paths::{SearchPaths, PathKind};
-use rustc_back::dynamic_lib::DynamicLibrary;
-use rustc_back::tempdir::TempDir;
+use rustc_metadata::dynamic_lib::DynamicLibrary;
+use tempdir::TempDir;
 use rustc_driver::{self, driver, Compilation};
 use rustc_driver::driver::phase_2_configure_and_expand;
-use rustc_driver::pretty::ReplaceBodyWithLoop;
 use rustc_metadata::cstore::CStore;
 use rustc_resolve::MakeGlobMap;
 use rustc_trans;
@@ -39,7 +38,6 @@ use rustc_trans::back::link;
 use syntax::ast;
 use syntax::codemap::CodeMap;
 use syntax::feature_gate::UnstableFeatures;
-use syntax::fold::Folder;
 use syntax_pos::{BytePos, DUMMY_SP, Pos, Span};
 use errors;
 use errors::emitter::ColorConfig;
@@ -61,7 +59,8 @@ pub fn run(input: &str,
            crate_name: Option<String>,
            maybe_sysroot: Option<PathBuf>,
            render_type: RenderType,
-           display_warnings: bool)
+           display_warnings: bool,
+           linker: Option<String>)
            -> isize {
     let input_path = PathBuf::from(input);
     let input = config::Input::File(input_path.clone());
@@ -80,7 +79,9 @@ pub fn run(input: &str,
 
     let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
     let handler =
-        errors::Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
+        errors::Handler::with_tty_emitter(ColorConfig::Auto,
+                                          true, false,
+                                          Some(codemap.clone()));
 
     let cstore = Rc::new(CStore::new(box rustc_trans::LlvmMetadataLoader));
     let mut sess = session::build_session_(
@@ -94,7 +95,6 @@ pub fn run(input: &str,
     let krate = panictry!(driver::phase_1_parse_input(&driver::CompileController::basic(),
                                                       &sess,
                                                       &input));
-    let krate = ReplaceBodyWithLoop::new().fold_crate(krate);
     let driver::ExpansionResult { defs, mut hir_forest, .. } = {
         phase_2_configure_and_expand(
             &sess,
@@ -121,7 +121,8 @@ pub fn run(input: &str,
                                        maybe_sysroot,
                                        Some(codemap),
                                        None,
-                                       render_type);
+                                       render_type,
+                                       linker);
 
     {
         let map = hir::map::map_crate(&sess, &*cstore, &mut hir_forest, &defs);
@@ -180,10 +181,13 @@ fn run_test(test: &str, cratename: &str, filename: &str, cfgs: Vec<String>, libs
             externs: Externs,
             should_panic: bool, no_run: bool, as_test_harness: bool,
             compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
-            maybe_sysroot: Option<PathBuf>) {
+            maybe_sysroot: Option<PathBuf>,
+            linker: Option<String>) {
     // the test harness wants its own `main` & top level functions, so
     // never wrap the test in `fn main() { ... }`
     let test = make_test(test, Some(cratename), as_test_harness, opts);
+    // FIXME(#44940): if doctests ever support path remapping, then this filename
+    // needs to be the result of CodeMap::span_to_unmapped_path
     let input = config::Input::Str {
         name: filename.to_owned(),
         input: test.to_owned(),
@@ -199,6 +203,7 @@ fn run_test(test: &str, cratename: &str, filename: &str, cfgs: Vec<String>, libs
         externs,
         cg: config::CodegenOptions {
             prefer_dynamic: true,
+            linker,
             .. config::basic_codegen_options()
         },
         test: as_test_harness,
@@ -232,7 +237,8 @@ fn run_test(test: &str, cratename: &str, filename: &str, cfgs: Vec<String>, libs
     let data = Arc::new(Mutex::new(Vec::new()));
     let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
     let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
-                                                      Some(codemap.clone()));
+                                                      Some(codemap.clone()),
+                                                      false);
     let old = io::set_panic(Some(box Sink(data.clone())));
     let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
 
@@ -330,15 +336,23 @@ pub fn make_test(s: &str,
 
     let mut prog = String::new();
 
-    // First push any outer attributes from the example, assuming they
-    // are intended to be crate attributes.
-    prog.push_str(&crate_attrs);
+    if opts.attrs.is_empty() {
+        // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
+        // lints that are commonly triggered in doctests. The crate-level test attributes are
+        // commonly used to make tests fail in case they trigger warnings, so having this there in
+        // that case may cause some tests to pass when they shouldn't have.
+        prog.push_str("#![allow(unused)]\n");
+    }
 
-    // Next, any attributes for other aspects such as lints.
+    // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
     for attr in &opts.attrs {
         prog.push_str(&format!("#![{}]\n", attr));
     }
 
+    // Now push any outer attributes from the example, assuming they
+    // are intended to be crate attributes.
+    prog.push_str(&crate_attrs);
+
     // Don't inject `extern crate std` because it's already injected by the
     // compiler.
     if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
@@ -405,13 +419,33 @@ pub struct Collector {
     pub tests: Vec<testing::TestDescAndFn>,
     // to be removed when hoedown will be definitely gone
     pub old_tests: HashMap<String, Vec<String>>,
+
+    // The name of the test displayed to the user, separated by `::`.
+    //
+    // In tests from Rust source, this is the path to the item
+    // e.g. `["std", "vec", "Vec", "push"]`.
+    //
+    // In tests from a markdown file, this is the titles of all headers (h1~h6)
+    // of the sections that contain the code block, e.g. if the markdown file is
+    // written as:
+    //
+    // ``````markdown
+    // # Title
+    //
+    // ## Subtitle
+    //
+    // ```rust
+    // assert!(true);
+    // ```
+    // ``````
+    //
+    // the `names` vector of that test will be `["Title", "Subtitle"]`.
     names: Vec<String>,
+
     cfgs: Vec<String>,
     libs: SearchPaths,
     externs: Externs,
-    cnt: usize,
     use_headers: bool,
-    current_header: Option<String>,
     cratename: String,
     opts: TestOptions,
     maybe_sysroot: Option<PathBuf>,
@@ -420,13 +454,14 @@ pub struct Collector {
     filename: Option<String>,
     // to be removed when hoedown will be removed as well
     pub render_type: RenderType,
+    linker: Option<String>,
 }
 
 impl Collector {
     pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
                use_headers: bool, opts: TestOptions, maybe_sysroot: Option<PathBuf>,
                codemap: Option<Rc<CodeMap>>, filename: Option<String>,
-               render_type: RenderType) -> Collector {
+               render_type: RenderType, linker: Option<String>) -> Collector {
         Collector {
             tests: Vec::new(),
             old_tests: HashMap::new(),
@@ -434,9 +469,7 @@ impl Collector {
             cfgs,
             libs,
             externs,
-            cnt: 0,
             use_headers,
-            current_header: None,
             cratename,
             opts,
             maybe_sysroot,
@@ -444,32 +477,17 @@ impl Collector {
             codemap,
             filename,
             render_type,
+            linker,
         }
     }
 
     fn generate_name(&self, line: usize, filename: &str) -> String {
-        if self.use_headers {
-            if let Some(ref header) = self.current_header {
-                format!("{} - {} (line {})", filename, header, line)
-            } else {
-                format!("{} - (line {})", filename, line)
-            }
-        } else {
-            format!("{} - {} (line {})", filename, self.names.join("::"), line)
-        }
+        format!("{} - {} (line {})", filename, self.names.join("::"), line)
     }
 
     // to be removed once hoedown is gone
     fn generate_name_beginning(&self, filename: &str) -> String {
-        if self.use_headers {
-            if let Some(ref header) = self.current_header {
-                format!("{} - {} (line", filename, header)
-            } else {
-                format!("{} - (line", filename)
-            }
-        } else {
-            format!("{} - {} (line", filename, self.names.join("::"))
-        }
+        format!("{} - {} (line", filename, self.names.join("::"))
     }
 
     pub fn add_old_test(&mut self, test: String, filename: String) {
@@ -493,11 +511,10 @@ impl Collector {
                 found = entry.remove_item(&test).is_some();
             }
             if !found {
-                let _ = writeln!(&mut io::stderr(),
-                                 "WARNING: {} Code block is not currently run as a test, but will \
-                                  in future versions of rustdoc. Please ensure this code block is \
-                                  a runnable test, or use the `ignore` directive.",
-                                 name);
+                eprintln!("WARNING: {} Code block is not currently run as a test, but will \
+                           in future versions of rustdoc. Please ensure this code block is \
+                           a runnable test, or use the `ignore` directive.",
+                          name);
                 return
             }
         }
@@ -507,6 +524,7 @@ impl Collector {
         let cratename = self.cratename.to_string();
         let opts = self.opts.clone();
         let maybe_sysroot = self.maybe_sysroot.clone();
+        let linker = self.linker.clone();
         debug!("Creating test {}: {}", name, test);
         self.tests.push(testing::TestDescAndFn {
             desc: testing::TestDesc {
@@ -535,7 +553,8 @@ impl Collector {
                                  compile_fail,
                                  error_codes,
                                  &opts,
-                                 maybe_sysroot)
+                                 maybe_sysroot,
+                                 linker)
                     })
                 } {
                     Ok(()) => (),
@@ -578,7 +597,7 @@ impl Collector {
     }
 
     pub fn register_header(&mut self, name: &str, level: u32) {
-        if self.use_headers && level == 1 {
+        if self.use_headers {
             // we use these headings as test names, so it's good if
             // they're valid identifiers.
             let name = name.chars().enumerate().map(|(i, c)| {
@@ -590,9 +609,28 @@ impl Collector {
                     }
                 }).collect::<String>();
 
-            // new header => reset count.
-            self.cnt = 0;
-            self.current_header = Some(name);
+            // Here we try to efficiently assemble the header titles into the
+            // test name in the form of `h1::h2::h3::h4::h5::h6`.
+            //
+            // Suppose originally `self.names` contains `[h1, h2, h3]`...
+            let level = level as usize;
+            if level <= self.names.len() {
+                // ... Consider `level == 2`. All headers in the lower levels
+                // are irrelevant in this new level. So we should reset
+                // `self.names` to contain headers until <h2>, and replace that
+                // slot with the new name: `[h1, name]`.
+                self.names.truncate(level);
+                self.names[level - 1] = name;
+            } else {
+                // ... On the other hand, consider `level == 5`. This means we
+                // need to extend `self.names` to contain five headers. We fill
+                // in the missing level (<h4>) with `_`. Thus `self.names` will
+                // become `[h1, h2, h3, "_", name]`.
+                if level - 1 > self.names.len() {
+                    self.names.resize(level - 1, "_".to_owned());
+                }
+                self.names.push(name);
+            }
         }
     }
 }
@@ -622,15 +660,16 @@ impl<'a, 'hir> HirCollector<'a, 'hir> {
 
         attrs.collapse_doc_comments();
         attrs.unindent_doc_comments();
-        if let Some(doc) = attrs.doc_value() {
-            self.collector.cnt = 0;
+        // the collapse-docs pass won't combine sugared/raw doc attributes, or included files with
+        // anything else, this will combine them for us
+        if let Some(doc) = attrs.collapsed_doc_value() {
             if self.collector.render_type == RenderType::Pulldown {
-                markdown::old_find_testable_code(doc, self.collector,
+                markdown::old_find_testable_code(&doc, self.collector,
                                                  attrs.span.unwrap_or(DUMMY_SP));
-                markdown::find_testable_code(doc, self.collector,
+                markdown::find_testable_code(&doc, self.collector,
                                              attrs.span.unwrap_or(DUMMY_SP));
             } else {
-                markdown::old_find_testable_code(doc, self.collector,
+                markdown::old_find_testable_code(&doc, self.collector,
                                                  attrs.span.unwrap_or(DUMMY_SP));
             }
         }