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
This commit is contained in:
parent
641bca06c8
commit
179719d450
5 changed files with 77 additions and 43 deletions
|
@ -199,7 +199,8 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
|
||||||
module: Some(module),
|
module: Some(module),
|
||||||
externs: externs,
|
externs: externs,
|
||||||
primitives: primitives,
|
primitives: primitives,
|
||||||
external_traits: cx.external_traits.borrow_mut().take().unwrap(),
|
external_traits: cx.external_traits.borrow_mut().take()
|
||||||
|
.unwrap_or(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,10 @@
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::ffi::CString;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::default::Default;
|
||||||
|
use std::ffi::CString;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::str;
|
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)
|
stripped_filtered_line(l).unwrap_or(l)
|
||||||
}).collect::<Vec<&str>>().connect("\n");
|
}).collect::<Vec<&str>>().connect("\n");
|
||||||
let krate = krate.as_ref().map(|s| &**s);
|
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(&format!("<span class='rusttest'>{}</span>", Escape(&test)));
|
||||||
});
|
});
|
||||||
s.push_str(&highlight::highlight(&text,
|
s.push_str(&highlight::highlight(&text,
|
||||||
|
|
|
@ -8,9 +8,10 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
|
use std::default::Default;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::io;
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
|
|
||||||
use core;
|
use core;
|
||||||
|
@ -23,7 +24,7 @@ use externalfiles::ExternalHtml;
|
||||||
use html::escape::Escape;
|
use html::escape::Escape;
|
||||||
use html::markdown;
|
use html::markdown;
|
||||||
use html::markdown::{Markdown, MarkdownWithToc, find_testable_code, reset_headers};
|
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 `%`.
|
/// 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) {
|
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 {
|
mut test_args: Vec<String>) -> isize {
|
||||||
let input_str = load_or_return!(input, 1, 2);
|
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);
|
find_testable_code(&input_str, &mut collector);
|
||||||
test_args.insert(0, "rustdoctest".to_string());
|
test_args.insert(0, "rustdoctest".to_string());
|
||||||
testing::test_main(&test_args, collector.tests);
|
testing::test_main(&test_args, collector.tests);
|
||||||
|
|
|
@ -38,6 +38,12 @@ use html::markdown;
|
||||||
use passes;
|
use passes;
|
||||||
use visit_ast::RustdocVisitor;
|
use visit_ast::RustdocVisitor;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct TestOptions {
|
||||||
|
pub no_crate_inject: bool,
|
||||||
|
pub attrs: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(input: &str,
|
pub fn run(input: &str,
|
||||||
cfgs: Vec<String>,
|
cfgs: Vec<String>,
|
||||||
libs: SearchPaths,
|
libs: SearchPaths,
|
||||||
|
@ -75,7 +81,7 @@ pub fn run(input: &str,
|
||||||
"rustdoc-test", None)
|
"rustdoc-test", None)
|
||||||
.expect("phase_2_configure_and_expand aborted in rustdoc!");
|
.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 {
|
let ctx = core::DocContext {
|
||||||
krate: &krate,
|
krate: &krate,
|
||||||
|
@ -102,7 +108,7 @@ pub fn run(input: &str,
|
||||||
libs,
|
libs,
|
||||||
externs,
|
externs,
|
||||||
false,
|
false,
|
||||||
inject_crate);
|
opts);
|
||||||
collector.fold_crate(krate);
|
collector.fold_crate(krate);
|
||||||
|
|
||||||
test_args.insert(0, "rustdoctest".to_string());
|
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
|
// 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::attr::AttrMetaMethods;
|
||||||
|
use syntax::print::pprust;
|
||||||
|
|
||||||
let mut inject_crate = true;
|
let mut opts = TestOptions {
|
||||||
|
no_crate_inject: true,
|
||||||
|
attrs: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
for attr in &krate.attrs {
|
let attrs = krate.attrs.iter().filter(|a| a.check_name("doc"))
|
||||||
if attr.check_name("doc") {
|
.filter_map(|a| a.meta_item_list())
|
||||||
for list in attr.meta_item_list().into_iter() {
|
.flat_map(|l| l.iter())
|
||||||
for attr in list {
|
.filter(|a| a.check_name("test"))
|
||||||
if attr.check_name("test") {
|
.filter_map(|a| a.meta_item_list())
|
||||||
for list in attr.meta_item_list().into_iter() {
|
.flat_map(|l| l.iter());
|
||||||
for attr in list {
|
for attr in attrs {
|
||||||
if attr.check_name("no_crate_inject") {
|
if attr.check_name("no_crate_inject") {
|
||||||
inject_crate = false;
|
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,
|
fn runtest(test: &str, cratename: &str, libs: SearchPaths,
|
||||||
externs: core::Externs,
|
externs: core::Externs,
|
||||||
should_panic: bool, no_run: bool, as_test_harness: bool,
|
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
|
// the test harness wants its own `main` & top level functions, so
|
||||||
// never wrap the test in `fn main() { ... }`
|
// never wrap the test in `fn main() { ... }`
|
||||||
let test = maketest(test, Some(cratename), true, as_test_harness,
|
let test = maketest(test, Some(cratename), as_test_harness, opts);
|
||||||
inject_crate);
|
|
||||||
let input = config::Input::Str(test.to_string());
|
let input = config::Input::Str(test.to_string());
|
||||||
|
|
||||||
let sessopts = config::Options {
|
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,
|
pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
|
||||||
dont_insert_main: bool, inject_crate: bool) -> String {
|
opts: &TestOptions) -> String {
|
||||||
let (crate_attrs, everything_else) = partition_source(s);
|
let (crate_attrs, everything_else) = partition_source(s);
|
||||||
|
|
||||||
let mut prog = String::new();
|
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.
|
// are intended to be crate attributes.
|
||||||
prog.push_str(&crate_attrs);
|
prog.push_str(&crate_attrs);
|
||||||
|
|
||||||
if lints {
|
// Next, any attributes for other aspects such as lints.
|
||||||
prog.push_str(r"
|
for attr in &opts.attrs {
|
||||||
#![allow(unused_variables, unused_assignments, unused_mut, unused_attributes, dead_code)]
|
prog.push_str(&format!("#![{}]\n", attr));
|
||||||
");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't inject `extern crate std` because it's already injected by the
|
// Don't inject `extern crate std` because it's already injected by the
|
||||||
// compiler.
|
// compiler.
|
||||||
if !s.contains("extern crate") && inject_crate {
|
if !s.contains("extern crate") && !opts.no_crate_inject {
|
||||||
match cratename {
|
match cratename {
|
||||||
Some(cratename) => {
|
Some(cratename) => {
|
||||||
if s.contains(cratename) {
|
if s.contains(cratename) {
|
||||||
prog.push_str(&format!("extern crate {};\n",
|
prog.push_str(&format!("extern crate {};\n", cratename));
|
||||||
cratename));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -325,12 +332,12 @@ pub struct Collector {
|
||||||
use_headers: bool,
|
use_headers: bool,
|
||||||
current_header: Option<String>,
|
current_header: Option<String>,
|
||||||
cratename: String,
|
cratename: String,
|
||||||
inject_crate: bool
|
opts: TestOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collector {
|
impl Collector {
|
||||||
pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs,
|
pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs,
|
||||||
use_headers: bool, inject_crate: bool) -> Collector {
|
use_headers: bool, opts: TestOptions) -> Collector {
|
||||||
Collector {
|
Collector {
|
||||||
tests: Vec::new(),
|
tests: Vec::new(),
|
||||||
names: Vec::new(),
|
names: Vec::new(),
|
||||||
|
@ -340,7 +347,7 @@ impl Collector {
|
||||||
use_headers: use_headers,
|
use_headers: use_headers,
|
||||||
current_header: None,
|
current_header: None,
|
||||||
cratename: cratename,
|
cratename: cratename,
|
||||||
inject_crate: inject_crate
|
opts: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,13 +364,14 @@ impl Collector {
|
||||||
let libs = self.libs.clone();
|
let libs = self.libs.clone();
|
||||||
let externs = self.externs.clone();
|
let externs = self.externs.clone();
|
||||||
let cratename = self.cratename.to_string();
|
let cratename = self.cratename.to_string();
|
||||||
let inject_crate = self.inject_crate;
|
let opts = self.opts.clone();
|
||||||
debug!("Creating test {}: {}", name, test);
|
debug!("Creating test {}: {}", name, test);
|
||||||
self.tests.push(testing::TestDescAndFn {
|
self.tests.push(testing::TestDescAndFn {
|
||||||
desc: testing::TestDesc {
|
desc: testing::TestDesc {
|
||||||
name: testing::DynTestName(name),
|
name: testing::DynTestName(name),
|
||||||
ignore: should_ignore,
|
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|| {
|
testfn: testing::DynTestFn(Box::new(move|| {
|
||||||
runtest(&test,
|
runtest(&test,
|
||||||
|
@ -373,7 +381,7 @@ impl Collector {
|
||||||
should_panic,
|
should_panic,
|
||||||
no_run,
|
no_run,
|
||||||
as_test_harness,
|
as_test_harness,
|
||||||
inject_crate);
|
&opts);
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
19
src/test/rustdoc/issue-18199.rs
Normal file
19
src/test/rustdoc/issue-18199.rs
Normal file
|
@ -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() {}
|
Loading…
Add table
Add a link
Reference in a new issue