Skip to content

Commit 179719d

Browse files
committed
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 rust-lang#18199
1 parent 641bca0 commit 179719d

File tree

5 files changed

+78
-44
lines changed

5 files changed

+78
-44
lines changed

src/librustdoc/clean/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
199199
module: Some(module),
200200
externs: externs,
201201
primitives: primitives,
202-
external_traits: cx.external_traits.borrow_mut().take().unwrap(),
202+
external_traits: cx.external_traits.borrow_mut().take()
203+
.unwrap_or(HashMap::new()),
203204
}
204205
}
205206
}

src/librustdoc/html/markdown.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929

3030
use libc;
3131
use std::ascii::AsciiExt;
32-
use std::ffi::CString;
3332
use std::cell::RefCell;
3433
use std::collections::HashMap;
34+
use std::default::Default;
35+
use std::ffi::CString;
3536
use std::fmt;
3637
use std::slice;
3738
use std::str;
@@ -244,7 +245,8 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
244245
stripped_filtered_line(l).unwrap_or(l)
245246
}).collect::<Vec<&str>>().connect("\n");
246247
let krate = krate.as_ref().map(|s| &**s);
247-
let test = test::maketest(&test, krate, false, false, true);
248+
let test = test::maketest(&test, krate, false,
249+
&Default::default());
248250
s.push_str(&format!("<span class='rusttest'>{}</span>", Escape(&test)));
249251
});
250252
s.push_str(&highlight::highlight(&text,

src/librustdoc/markdown.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use std::default::Default;
1112
use std::fs::File;
12-
use std::io;
1313
use std::io::prelude::*;
14+
use std::io;
1415
use std::path::{PathBuf, Path};
1516

1617
use core;
@@ -23,7 +24,7 @@ use externalfiles::ExternalHtml;
2324
use html::escape::Escape;
2425
use html::markdown;
2526
use html::markdown::{Markdown, MarkdownWithToc, find_testable_code, reset_headers};
26-
use test::Collector;
27+
use test::{TestOptions, Collector};
2728

2829
/// Separate any lines at the start of the file that begin with `%`.
2930
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,
143144
mut test_args: Vec<String>) -> isize {
144145
let input_str = load_or_return!(input, 1, 2);
145146

146-
let mut collector = Collector::new(input.to_string(), libs, externs, true, false);
147+
let mut opts = TestOptions::default();
148+
opts.no_crate_inject = true;
149+
let mut collector = Collector::new(input.to_string(), libs, externs,
150+
true, opts);
147151
find_testable_code(&input_str, &mut collector);
148152
test_args.insert(0, "rustdoctest".to_string());
149153
testing::test_main(&test_args, collector.tests);

src/librustdoc/test.rs

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ use html::markdown;
3838
use passes;
3939
use visit_ast::RustdocVisitor;
4040

41+
#[derive(Clone, Default)]
42+
pub struct TestOptions {
43+
pub no_crate_inject: bool,
44+
pub attrs: Vec<String>,
45+
}
46+
4147
pub fn run(input: &str,
4248
cfgs: Vec<String>,
4349
libs: SearchPaths,
@@ -75,7 +81,7 @@ pub fn run(input: &str,
7581
"rustdoc-test", None)
7682
.expect("phase_2_configure_and_expand aborted in rustdoc!");
7783

78-
let inject_crate = should_inject_crate(&krate);
84+
let opts = scrape_test_config(&krate);
7985

8086
let ctx = core::DocContext {
8187
krate: &krate,
@@ -102,7 +108,7 @@ pub fn run(input: &str,
102108
libs,
103109
externs,
104110
false,
105-
inject_crate);
111+
opts);
106112
collector.fold_crate(krate);
107113

108114
test_args.insert(0, "rustdoctest".to_string());
@@ -113,41 +119,44 @@ pub fn run(input: &str,
113119
}
114120

115121
// Look for #![doc(test(no_crate_inject))], used by crates in the std facade
116-
fn should_inject_crate(krate: &::syntax::ast::Crate) -> bool {
122+
fn scrape_test_config(krate: &::syntax::ast::Crate) -> TestOptions {
117123
use syntax::attr::AttrMetaMethods;
124+
use syntax::print::pprust;
118125

119-
let mut inject_crate = true;
120-
121-
for attr in &krate.attrs {
122-
if attr.check_name("doc") {
123-
for list in attr.meta_item_list().into_iter() {
124-
for attr in list {
125-
if attr.check_name("test") {
126-
for list in attr.meta_item_list().into_iter() {
127-
for attr in list {
128-
if attr.check_name("no_crate_inject") {
129-
inject_crate = false;
130-
}
131-
}
132-
}
133-
}
126+
let mut opts = TestOptions {
127+
no_crate_inject: true,
128+
attrs: Vec::new(),
129+
};
130+
131+
let attrs = krate.attrs.iter().filter(|a| a.check_name("doc"))
132+
.filter_map(|a| a.meta_item_list())
133+
.flat_map(|l| l.iter())
134+
.filter(|a| a.check_name("test"))
135+
.filter_map(|a| a.meta_item_list())
136+
.flat_map(|l| l.iter());
137+
for attr in attrs {
138+
if attr.check_name("no_crate_inject") {
139+
opts.no_crate_inject = true;
140+
}
141+
if attr.check_name("attr") {
142+
if let Some(l) = attr.meta_item_list() {
143+
for item in l {
144+
opts.attrs.push(pprust::meta_item_to_string(item));
134145
}
135146
}
136147
}
137148
}
138149

139-
return inject_crate;
150+
return opts;
140151
}
141152

142-
#[allow(deprecated)]
143153
fn runtest(test: &str, cratename: &str, libs: SearchPaths,
144154
externs: core::Externs,
145155
should_panic: bool, no_run: bool, as_test_harness: bool,
146-
inject_crate: bool) {
156+
opts: &TestOptions) {
147157
// the test harness wants its own `main` & top level functions, so
148158
// never wrap the test in `fn main() { ... }`
149-
let test = maketest(test, Some(cratename), true, as_test_harness,
150-
inject_crate);
159+
let test = maketest(test, Some(cratename), as_test_harness, opts);
151160
let input = config::Input::Str(test.to_string());
152161

153162
let sessopts = config::Options {
@@ -250,8 +259,8 @@ fn runtest(test: &str, cratename: &str, libs: SearchPaths,
250259
}
251260
}
252261

253-
pub fn maketest(s: &str, cratename: Option<&str>, lints: bool,
254-
dont_insert_main: bool, inject_crate: bool) -> String {
262+
pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
263+
opts: &TestOptions) -> String {
255264
let (crate_attrs, everything_else) = partition_source(s);
256265

257266
let mut prog = String::new();
@@ -260,20 +269,18 @@ pub fn maketest(s: &str, cratename: Option<&str>, lints: bool,
260269
// are intended to be crate attributes.
261270
prog.push_str(&crate_attrs);
262271

263-
if lints {
264-
prog.push_str(r"
265-
#![allow(unused_variables, unused_assignments, unused_mut, unused_attributes, dead_code)]
266-
");
272+
// Next, any attributes for other aspects such as lints.
273+
for attr in &opts.attrs {
274+
prog.push_str(&format!("#![{}]\n", attr));
267275
}
268276

269277
// Don't inject `extern crate std` because it's already injected by the
270278
// compiler.
271-
if !s.contains("extern crate") && inject_crate {
279+
if !s.contains("extern crate") && !opts.no_crate_inject {
272280
match cratename {
273281
Some(cratename) => {
274282
if s.contains(cratename) {
275-
prog.push_str(&format!("extern crate {};\n",
276-
cratename));
283+
prog.push_str(&format!("extern crate {};\n", cratename));
277284
}
278285
}
279286
None => {}
@@ -325,12 +332,12 @@ pub struct Collector {
325332
use_headers: bool,
326333
current_header: Option<String>,
327334
cratename: String,
328-
inject_crate: bool
335+
opts: TestOptions,
329336
}
330337

331338
impl Collector {
332339
pub fn new(cratename: String, libs: SearchPaths, externs: core::Externs,
333-
use_headers: bool, inject_crate: bool) -> Collector {
340+
use_headers: bool, opts: TestOptions) -> Collector {
334341
Collector {
335342
tests: Vec::new(),
336343
names: Vec::new(),
@@ -340,7 +347,7 @@ impl Collector {
340347
use_headers: use_headers,
341348
current_header: None,
342349
cratename: cratename,
343-
inject_crate: inject_crate
350+
opts: opts,
344351
}
345352
}
346353

@@ -357,13 +364,14 @@ impl Collector {
357364
let libs = self.libs.clone();
358365
let externs = self.externs.clone();
359366
let cratename = self.cratename.to_string();
360-
let inject_crate = self.inject_crate;
367+
let opts = self.opts.clone();
361368
debug!("Creating test {}: {}", name, test);
362369
self.tests.push(testing::TestDescAndFn {
363370
desc: testing::TestDesc {
364371
name: testing::DynTestName(name),
365372
ignore: should_ignore,
366-
should_panic: testing::ShouldPanic::No, // compiler failures are test failures
373+
// compiler failures are test failures
374+
should_panic: testing::ShouldPanic::No,
367375
},
368376
testfn: testing::DynTestFn(Box::new(move|| {
369377
runtest(&test,
@@ -373,7 +381,7 @@ impl Collector {
373381
should_panic,
374382
no_run,
375383
as_test_harness,
376-
inject_crate);
384+
&opts);
377385
}))
378386
});
379387
}

src/test/rustdoc/issue-18199.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2015 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.
4+
//
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.
10+
11+
// compile-flags:--test
12+
13+
#![doc(test(attr(feature(staged_api))))]
14+
15+
/// ```
16+
/// #![staged_api]
17+
/// fn main() {}
18+
/// ```
19+
pub fn foo() {}

0 commit comments

Comments
 (0)