about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-04-06 18:39:39 -0700
committerAlex Crichton <alex@alexcrichton.com>2015-04-07 17:54:34 -0700
commit179719d45023e549a62ec7a584d554408c6d241d (patch)
treed63e2ae397eecbdd30f86936c8ca0bb6f9eb86e2
parent641bca06c82e2fa744e7b14bc45cfa501baf57e6 (diff)
downloadrust-179719d45023e549a62ec7a584d554408c6d241d.tar.gz
rust-179719d45023e549a62ec7a584d554408c6d241d.zip
rustdoc: Allowing specifying attrs for doctests
This adds support in rustdoc to blanket apply crate attributes to all doc tests
for a crate at once. The syntax for doing this is:

    #![doc(test(attr(...)))]

Each meta item in `...` will be applied to each doctest as a crate attribute.

cc #18199
-rw-r--r--src/librustdoc/clean/mod.rs3
-rw-r--r--src/librustdoc/html/markdown.rs6
-rw-r--r--src/librustdoc/markdown.rs10
-rw-r--r--src/librustdoc/test.rs84
-rw-r--r--src/test/rustdoc/issue-18199.rs19
5 files changed, 78 insertions, 44 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 3248bb4d41d..002c6d7460b 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -199,7 +199,8 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
             module: Some(module),
             externs: externs,
             primitives: primitives,
-            external_traits: cx.external_traits.borrow_mut().take().unwrap(),
+            external_traits: cx.external_traits.borrow_mut().take()
+                               .unwrap_or(HashMap::new()),
         }
     }
 }
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 49f6107869e..215f83ff87e 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -29,9 +29,10 @@
 
 use libc;
 use std::ascii::AsciiExt;
-use std::ffi::CString;
 use std::cell::RefCell;
 use std::collections::HashMap;
+use std::default::Default;
+use std::ffi::CString;
 use std::fmt;
 use std::slice;
 use std::str;
@@ -244,7 +245,8 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
                         stripped_filtered_line(l).unwrap_or(l)
                     }).collect::<Vec<&str>>().connect("\n");
                     let krate = krate.as_ref().map(|s| &**s);
-                    let test = test::maketest(&test, krate, false, false, true);
+                    let test = test::maketest(&test, krate, false,
+                                              &Default::default());
                     s.push_str(&format!("<span class='rusttest'>{}</span>", Escape(&test)));
                 });
                 s.push_str(&highlight::highlight(&text,
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index a84da60b018..8c75364d941 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -8,9 +8,10 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use std::default::Default;
 use std::fs::File;
-use std::io;
 use std::io::prelude::*;
+use std::io;
 use std::path::{PathBuf, Path};
 
 use core;
@@ -23,7 +24,7 @@ use externalfiles::ExternalHtml;
 use html::escape::Escape;
 use html::markdown;
 use html::markdown::{Markdown, MarkdownWithToc, find_testable_code, reset_headers};
-use test::Collector;
+use test::{TestOptions, Collector};
 
 /// Separate any lines at the start of the file that begin with `%`.
 fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
@@ -143,7 +144,10 @@ pub fn test(input: &str, libs: SearchPaths, externs: core::Externs,
             mut test_args: Vec<String>) -> isize {
     let input_str = load_or_return!(input, 1, 2);
 
-    let mut collector = Collector::new(input.to_string(), libs, externs, true, false);
+    let mut opts = TestOptions::default();
+    opts.no_crate_inject = true;
+    let mut collector = Collector::new(input.to_string(), libs, externs,
+                                       true, opts);
     find_testable_code(&input_str, &mut collector);
     test_args.insert(0, "rustdoctest".to_string());
     testing::test_main(&test_args, collector.tests);
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index f5bee6240d4..ffb2bb12540 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -38,6 +38,12 @@ use html::markdown;
 use passes;
 use visit_ast::RustdocVisitor;
 
+#[derive(Clone, Default)]
+pub struct TestOptions {
+    pub no_crate_inject: bool,
+    pub attrs: Vec<String>,
+}
+
 pub fn run(input: &str,
            cfgs: Vec<String>,
            libs: SearchPaths,
@@ -75,7 +81,7 @@ pub fn run(input: &str,
                                                      "rustdoc-test", None)
         .expect("phase_2_configure_and_expand aborted in rustdoc!");
 
-    let inject_crate = should_inject_crate(&krate);
+    let opts = scrape_test_config(&krate);
 
     let ctx = core::DocContext {
         krate: &krate,
@@ -102,7 +108,7 @@ pub fn run(input: &str,
                                        libs,
                                        externs,
                                        false,
-                                       inject_crate);
+                                       opts);
     collector.fold_crate(krate);
 
     test_args.insert(0, "rustdoctest".to_string());
@@ -113,41 +119,44 @@ pub fn run(input: &str,
 }
 
 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
-fn should_inject_crate(krate: &::syntax::ast::Crate) -> bool {
+fn scrape_test_config(krate: &::syntax::ast::Crate) -> TestOptions {
     use syntax::attr::AttrMetaMethods;
+    use syntax::print::pprust;
 
-    let mut inject_crate = true;
-
-    for attr in &krate.attrs {
-        if attr.check_name("doc") {
-            for list in attr.meta_item_list().into_iter() {
-                for attr in list {
-                    if attr.check_name("test") {
-                        for list in attr.meta_item_list().into_iter() {
-                            for attr in list {
-                                if attr.check_name("no_crate_inject") {
-                                    inject_crate = false;
-                                }
-                            }
-                        }
-                    }
+    let mut opts = TestOptions {
+        no_crate_inject: true,
+        attrs: Vec::new(),
+    };
+
+    let attrs = krate.attrs.iter().filter(|a| a.check_name("doc"))
+                     .filter_map(|a| a.meta_item_list())
+                     .flat_map(|l| l.iter())
+                     .filter(|a| a.check_name("test"))
+                     .filter_map(|a| a.meta_item_list())
+                     .flat_map(|l| l.iter());
+    for attr in attrs {
+        if attr.check_name("no_crate_inject") {
+            opts.no_crate_inject = true;
+        }
+        if attr.check_name("attr") {
+            if let Some(l) = attr.meta_item_list() {
+                for item in l {
+                    opts.attrs.push(pprust::meta_item_to_string(item));
                 }
             }
         }
     }
 
-    return inject_crate;
+    return opts;
 }
 
-#[allow(deprecated)]
 fn runtest(test: &str, cratename: &str, libs: SearchPaths,
            externs: core::Externs,
            should_panic: bool, no_run: bool, as_test_harness: bool,
-           inject_crate: bool) {
+           opts: &TestOptions) {
     // the test harness wants its own `main` & top level functions, so
     // never wrap the test in `fn main() { ... }`
-    let test = maketest(test, Some(cratename), true, as_test_harness,
-                        inject_crate);
+    let test = maketest(test, Some(cratename), as_test_harness, opts);
     let input = config::Input::Str(test.to_string());
 
     let sessopts = config::Options {
@@ -250,8 +259,8 @@ fn runtest(test: &str, cratename: &str, libs: SearchPaths,
     }
 }
 
-pub fn maketest(s: &str, cratename: Option<&str>, lints: bool,
-                dont_insert_main: bool, inject_crate: bool) -> String {
+pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
+                opts: &TestOptions) -> String {
     let (crate_attrs, everything_else) = partition_source(s);
 
     let mut prog = String::new();
@@ -260,20 +269,18 @@ pub fn maketest(s: &str, cratename: Option<&str>, lints: bool,
     // are intended to be crate attributes.
     prog.push_str(&crate_attrs);
 
-    if lints {
-        prog.push_str(r"
-#![allow(unused_variables, unused_assignments, unused_mut, unused_attributes, dead_code)]
-");
+    // Next, any attributes for other aspects such as lints.
+    for attr in &opts.attrs {
+        prog.push_str(&format!("#![{}]\n", attr));
     }
 
     // Don't inject `extern crate std` because it's already injected by the
     // compiler.
-    if !s.contains("extern crate") && inject_crate {
+    if !s.contains("extern crate") && !opts.no_crate_inject {
         match cratename {
             Some(cratename) => {
                 if s.contains(cratename) {
-                    prog.push_str(&format!("extern crate {};\n",
-                                           cratename));
+                    prog.push_str(&format!("extern crate {};\n", cratename));
                 }
             }
             None => {}
@@ -325,12 +332,12 @@ pub struct Collector {
     use_headers: bool,
     current_header: Option<String>,
     cratename: String,
-    inject_crate: bool
+    opts: TestOptions,
 }
 
 impl Collector {
     pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs,
-               use_headers: bool, inject_crate: bool) -> Collector {
+               use_headers: bool, opts: TestOptions) -> Collector {
         Collector {
             tests: Vec::new(),
             names: Vec::new(),
@@ -340,7 +347,7 @@ impl Collector {
             use_headers: use_headers,
             current_header: None,
             cratename: cratename,
-            inject_crate: inject_crate
+            opts: opts,
         }
     }
 
@@ -357,13 +364,14 @@ impl Collector {
         let libs = self.libs.clone();
         let externs = self.externs.clone();
         let cratename = self.cratename.to_string();
-        let inject_crate = self.inject_crate;
+        let opts = self.opts.clone();
         debug!("Creating test {}: {}", name, test);
         self.tests.push(testing::TestDescAndFn {
             desc: testing::TestDesc {
                 name: testing::DynTestName(name),
                 ignore: should_ignore,
-                should_panic: testing::ShouldPanic::No, // compiler failures are test failures
+                // compiler failures are test failures
+                should_panic: testing::ShouldPanic::No,
             },
             testfn: testing::DynTestFn(Box::new(move|| {
                 runtest(&test,
@@ -373,7 +381,7 @@ impl Collector {
                         should_panic,
                         no_run,
                         as_test_harness,
-                        inject_crate);
+                        &opts);
             }))
         });
     }
diff --git a/src/test/rustdoc/issue-18199.rs b/src/test/rustdoc/issue-18199.rs
new file mode 100644
index 00000000000..46aac8701fd
--- /dev/null
+++ b/src/test/rustdoc/issue-18199.rs
@@ -0,0 +1,19 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// compile-flags:--test
+
+#![doc(test(attr(feature(staged_api))))]
+
+/// ```
+/// #![staged_api]
+/// fn main() {}
+/// ```
+pub fn foo() {}