Skip to content

Commit 282705c

Browse files
committed
auto merge of #15039 : huonw/rust/rustdoc-testharnesss, r=alexcrichton
```test_harness #[test] fn foo() {} ``` will now compile and run the tests, rather than just ignoring & stripping them (i.e. it is as if `--test` was passed). Also, the specific example in #12242 was fixed (but that issue is broader than that example).
2 parents 40ca89e + cb6219f commit 282705c

File tree

4 files changed

+120
-74
lines changed

4 files changed

+120
-74
lines changed

src/doc/guide-testing.md

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
To create test functions, add a `#[test]` attribute like this:
66

7-
~~~
7+
~~~test_harness
88
fn return_two() -> int {
99
2
1010
}
@@ -37,7 +37,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
3737
Rust has built in support for simple unit testing. Functions can be
3838
marked as unit tests using the `test` attribute.
3939

40-
~~~
40+
~~~test_harness
4141
#[test]
4242
fn return_none_if_empty() {
4343
// ... test code ...
@@ -55,7 +55,7 @@ other (`assert_eq`, ...) means, then the test fails.
5555
When compiling a crate with the `--test` flag `--cfg test` is also
5656
implied, so that tests can be conditionally compiled.
5757

58-
~~~
58+
~~~test_harness
5959
#[cfg(test)]
6060
mod tests {
6161
#[test]
@@ -80,11 +80,11 @@ Tests that are intended to fail can be annotated with the
8080
task to fail then the test will be counted as successful; otherwise it
8181
will be counted as a failure. For example:
8282

83-
~~~
83+
~~~test_harness
8484
#[test]
8585
#[should_fail]
8686
fn test_out_of_bounds_failure() {
87-
let v: [int] = [];
87+
let v: &[int] = [];
8888
v[0];
8989
}
9090
~~~
@@ -204,26 +204,22 @@ amount.
204204

205205
For example:
206206

207-
~~~
208-
# #![allow(unused_imports)]
207+
~~~test_harness
209208
extern crate test;
210209
211-
use std::slice;
212210
use test::Bencher;
213211
214212
#[bench]
215213
fn bench_sum_1024_ints(b: &mut Bencher) {
216-
let v = slice::from_fn(1024, |n| n);
217-
b.iter(|| {v.iter().fold(0, |old, new| old + *new);} );
214+
let v = Vec::from_fn(1024, |n| n);
215+
b.iter(|| v.iter().fold(0, |old, new| old + *new));
218216
}
219217
220218
#[bench]
221219
fn initialise_a_vector(b: &mut Bencher) {
222-
b.iter(|| {slice::from_elem(1024, 0u64);} );
220+
b.iter(|| Vec::from_elem(1024, 0u64));
223221
b.bytes = 1024 * 8;
224222
}
225-
226-
# fn main() {}
227223
~~~
228224

229225
The benchmark runner will calibrate measurement of the benchmark
@@ -266,19 +262,16 @@ benchmarking what one expects. For example, the compiler might
266262
recognize that some calculation has no external effects and remove
267263
it entirely.
268264

269-
~~~
270-
# #![allow(unused_imports)]
265+
~~~test_harness
271266
extern crate test;
272267
use test::Bencher;
273268
274269
#[bench]
275270
fn bench_xor_1000_ints(b: &mut Bencher) {
276271
b.iter(|| {
277-
range(0, 1000).fold(0, |old, new| old ^ new);
278-
});
272+
range(0, 1000).fold(0, |old, new| old ^ new);
273+
});
279274
}
280-
281-
# fn main() {}
282275
~~~
283276

284277
gives the following results
@@ -297,8 +290,11 @@ cannot remove the computation entirely. This could be done for the
297290
example above by adjusting the `bh.iter` call to
298291

299292
~~~
300-
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
301-
bh.iter(|| range(0, 1000).fold(0, |old, new| old ^ new))
293+
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let b = X;
294+
b.iter(|| {
295+
// note lack of `;` (could also use an explicit `return`).
296+
range(0, 1000).fold(0, |old, new| old ^ new)
297+
});
302298
~~~
303299

304300
Or, the other option is to call the generic `test::black_box`
@@ -309,10 +305,10 @@ forces it to consider any argument as used.
309305
extern crate test;
310306
311307
# fn main() {
312-
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
313-
bh.iter(|| {
314-
test::black_box(range(0, 1000).fold(0, |old, new| old ^ new));
315-
});
308+
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let b = X;
309+
b.iter(|| {
310+
test::black_box(range(0, 1000).fold(0, |old, new| old ^ new));
311+
});
316312
# }
317313
~~~
318314

src/doc/rustdoc.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ You can specify that the code block should be compiled but not run with the
171171
```
172172
~~~
173173

174+
Lastly, you can specify that a code block be compiled as if `--test`
175+
were passed to the compiler using the `test_harness` directive.
176+
177+
~~~md
178+
```test_harness
179+
#[test]
180+
fn foo() {
181+
fail!("oops! (will run & register as failure)")
182+
}
183+
```
184+
~~~
185+
174186
Rustdoc also supplies some extra sugar for helping with some tedious
175187
documentation examples. If a line is prefixed with `# `, then the line
176188
will not show up in the HTML documentation, but it will be used when

src/librustdoc/html/markdown.rs

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
174174
slice::raw::buf_as_slice((*lang).data,
175175
(*lang).size as uint, |rlang| {
176176
let rlang = str::from_utf8(rlang).unwrap();
177-
let (_,_,_,notrust) = parse_lang_string(rlang);
178-
if notrust {
177+
if LangString::parse(rlang).notrust {
179178
(my_opaque.dfltblk)(ob, &buf, lang,
180179
opaque as *mut libc::c_void);
181180
true
@@ -196,7 +195,7 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
196195
stripped_filtered_line(l).unwrap_or(l)
197196
}).collect::<Vec<&str>>().connect("\n");
198197
let krate = krate.as_ref().map(|s| s.as_slice());
199-
let test = test::maketest(test.as_slice(), krate, false);
198+
let test = test::maketest(test.as_slice(), krate, false, false);
200199
s.push_str(format!("<span id='rust-example-raw-{}' \
201200
class='rusttest'>{}</span>",
202201
i, Escape(test.as_slice())).as_slice());
@@ -309,16 +308,16 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
309308
lang: *hoedown_buffer, opaque: *mut libc::c_void) {
310309
unsafe {
311310
if text.is_null() { return }
312-
let (should_fail, no_run, ignore, notrust) = if lang.is_null() {
313-
(false, false, false, false)
311+
let block_info = if lang.is_null() {
312+
LangString::all_false()
314313
} else {
315314
slice::raw::buf_as_slice((*lang).data,
316315
(*lang).size as uint, |lang| {
317316
let s = str::from_utf8(lang).unwrap();
318-
parse_lang_string(s)
317+
LangString::parse(s)
319318
})
320319
};
321-
if notrust { return }
320+
if block_info.notrust { return }
322321
slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
323322
let opaque = opaque as *mut hoedown_html_renderer_state;
324323
let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
@@ -327,7 +326,9 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
327326
stripped_filtered_line(l).unwrap_or(l)
328327
});
329328
let text = lines.collect::<Vec<&str>>().connect("\n");
330-
tests.add_test(text.to_string(), should_fail, no_run, ignore);
329+
tests.add_test(text.to_string(),
330+
block_info.should_fail, block_info.no_run,
331+
block_info.ignore, block_info.test_harness);
331332
})
332333
}
333334
}
@@ -365,33 +366,52 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
365366
}
366367
}
367368

368-
fn parse_lang_string(string: &str) -> (bool,bool,bool,bool) {
369-
let mut seen_rust_tags = false;
370-
let mut seen_other_tags = false;
371-
let mut should_fail = false;
372-
let mut no_run = false;
373-
let mut ignore = false;
374-
let mut notrust = false;
375-
376-
let mut tokens = string.as_slice().split(|c: char|
377-
!(c == '_' || c == '-' || c.is_alphanumeric())
378-
);
379-
380-
for token in tokens {
381-
match token {
382-
"" => {},
383-
"should_fail" => { should_fail = true; seen_rust_tags = true; },
384-
"no_run" => { no_run = true; seen_rust_tags = true; },
385-
"ignore" => { ignore = true; seen_rust_tags = true; },
386-
"notrust" => { notrust = true; seen_rust_tags = true; },
387-
"rust" => { notrust = false; seen_rust_tags = true; },
388-
_ => { seen_other_tags = true }
369+
#[deriving(Eq, PartialEq, Clone, Show)]
370+
struct LangString {
371+
should_fail: bool,
372+
no_run: bool,
373+
ignore: bool,
374+
notrust: bool,
375+
test_harness: bool,
376+
}
377+
378+
impl LangString {
379+
fn all_false() -> LangString {
380+
LangString {
381+
should_fail: false,
382+
no_run: false,
383+
ignore: false,
384+
notrust: false,
385+
test_harness: false,
389386
}
390387
}
391388

392-
let notrust = notrust || (seen_other_tags && !seen_rust_tags);
389+
fn parse(string: &str) -> LangString {
390+
let mut seen_rust_tags = false;
391+
let mut seen_other_tags = false;
392+
let mut data = LangString::all_false();
393+
394+
let mut tokens = string.as_slice().split(|c: char|
395+
!(c == '_' || c == '-' || c.is_alphanumeric())
396+
);
397+
398+
for token in tokens {
399+
match token {
400+
"" => {},
401+
"should_fail" => { data.should_fail = true; seen_rust_tags = true; },
402+
"no_run" => { data.no_run = true; seen_rust_tags = true; },
403+
"ignore" => { data.ignore = true; seen_rust_tags = true; },
404+
"notrust" => { data.notrust = true; seen_rust_tags = true; },
405+
"rust" => { data.notrust = false; seen_rust_tags = true; },
406+
"test_harness" => { data.test_harness = true; seen_rust_tags = true; }
407+
_ => { seen_other_tags = true }
408+
}
409+
}
410+
411+
data.notrust |= seen_other_tags && !seen_rust_tags;
393412

394-
(should_fail, no_run, ignore, notrust)
413+
data
414+
}
395415
}
396416

397417
/// By default this markdown renderer generates anchors for each header in the
@@ -425,19 +445,32 @@ impl<'a> fmt::Show for MarkdownWithToc<'a> {
425445

426446
#[cfg(test)]
427447
mod tests {
428-
use super::parse_lang_string;
448+
use super::LangString;
429449

430450
#[test]
431-
fn test_parse_lang_string() {
432-
assert_eq!(parse_lang_string(""), (false,false,false,false))
433-
assert_eq!(parse_lang_string("rust"), (false,false,false,false))
434-
assert_eq!(parse_lang_string("sh"), (false,false,false,true))
435-
assert_eq!(parse_lang_string("notrust"), (false,false,false,true))
436-
assert_eq!(parse_lang_string("ignore"), (false,false,true,false))
437-
assert_eq!(parse_lang_string("should_fail"), (true,false,false,false))
438-
assert_eq!(parse_lang_string("no_run"), (false,true,false,false))
439-
assert_eq!(parse_lang_string("{.no_run .example}"), (false,true,false,false))
440-
assert_eq!(parse_lang_string("{.sh .should_fail}"), (true,false,false,false))
441-
assert_eq!(parse_lang_string("{.example .rust}"), (false,false,false,false))
451+
fn test_lang_string_parse() {
452+
fn t(s: &str,
453+
should_fail: bool, no_run: bool, ignore: bool, notrust: bool, test_harness: bool) {
454+
assert_eq!(LangString::parse(s), LangString {
455+
should_fail: should_fail,
456+
no_run: no_run,
457+
ignore: ignore,
458+
notrust: notrust,
459+
test_harness: test_harness,
460+
})
461+
}
462+
463+
t("", false,false,false,false,false);
464+
t("rust", false,false,false,false,false);
465+
t("sh", false,false,false,true,false);
466+
t("notrust", false,false,false,true,false);
467+
t("ignore", false,false,true,false,false);
468+
t("should_fail", true,false,false,false,false);
469+
t("no_run", false,true,false,false,false);
470+
t("test_harness", false,false,false,false,true);
471+
t("{.no_run .example}", false,true,false,false,false);
472+
t("{.sh .should_fail}", true,false,false,false,false);
473+
t("{.example .rust}", false,false,false,false,false);
474+
t("{.test_harness .rust}", false,false,false,false,true);
442475
}
443476
}

src/librustdoc/test.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ pub fn run(input: &str,
102102
}
103103

104104
fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
105-
no_run: bool) {
106-
let test = maketest(test, Some(cratename), true);
105+
no_run: bool, as_test_harness: bool) {
106+
// the test harness wants its own `main` & top level functions, so
107+
// never wrap the test in `fn main() { ... }`
108+
let test = maketest(test, Some(cratename), true, as_test_harness);
107109
let input = driver::StrInput(test.to_string());
108110

109111
let sessopts = config::Options {
@@ -116,6 +118,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
116118
prefer_dynamic: true,
117119
.. config::basic_codegen_options()
118120
},
121+
test: as_test_harness,
119122
..config::basic_options().clone()
120123
};
121124

@@ -200,7 +203,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
200203
}
201204
}
202205

203-
pub fn maketest(s: &str, cratename: Option<&str>, lints: bool) -> String {
206+
pub fn maketest(s: &str, cratename: Option<&str>, lints: bool, dont_insert_main: bool) -> String {
204207
let mut prog = String::new();
205208
if lints {
206209
prog.push_str(r"
@@ -222,7 +225,7 @@ pub fn maketest(s: &str, cratename: Option<&str>, lints: bool) -> String {
222225
None => {}
223226
}
224227
}
225-
if s.contains("fn main") {
228+
if dont_insert_main || s.contains("fn main") {
226229
prog.push_str(s);
227230
} else {
228231
prog.push_str("fn main() {\n ");
@@ -257,7 +260,8 @@ impl Collector {
257260
}
258261
}
259262

260-
pub fn add_test(&mut self, test: String, should_fail: bool, no_run: bool, should_ignore: bool) {
263+
pub fn add_test(&mut self, test: String,
264+
should_fail: bool, no_run: bool, should_ignore: bool, as_test_harness: bool) {
261265
let name = if self.use_headers {
262266
let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
263267
format!("{}_{}", s, self.cnt)
@@ -279,7 +283,8 @@ impl Collector {
279283
cratename.as_slice(),
280284
libs,
281285
should_fail,
282-
no_run);
286+
no_run,
287+
as_test_harness);
283288
}),
284289
});
285290
}

0 commit comments

Comments
 (0)