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 https://github.com/rust-lang/rust/issues/12242 was fixed (but that issue is broader than that example).
This commit is contained in:
commit
282705c784
4 changed files with 119 additions and 73 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
To create test functions, add a `#[test]` attribute like this:
|
||||
|
||||
~~~
|
||||
~~~test_harness
|
||||
fn return_two() -> int {
|
||||
2
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
|||
Rust has built in support for simple unit testing. Functions can be
|
||||
marked as unit tests using the `test` attribute.
|
||||
|
||||
~~~
|
||||
~~~test_harness
|
||||
#[test]
|
||||
fn return_none_if_empty() {
|
||||
// ... test code ...
|
||||
|
@ -55,7 +55,7 @@ other (`assert_eq`, ...) means, then the test fails.
|
|||
When compiling a crate with the `--test` flag `--cfg test` is also
|
||||
implied, so that tests can be conditionally compiled.
|
||||
|
||||
~~~
|
||||
~~~test_harness
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
@ -80,11 +80,11 @@ Tests that are intended to fail can be annotated with the
|
|||
task to fail then the test will be counted as successful; otherwise it
|
||||
will be counted as a failure. For example:
|
||||
|
||||
~~~
|
||||
~~~test_harness
|
||||
#[test]
|
||||
#[should_fail]
|
||||
fn test_out_of_bounds_failure() {
|
||||
let v: [int] = [];
|
||||
let v: &[int] = [];
|
||||
v[0];
|
||||
}
|
||||
~~~
|
||||
|
@ -204,26 +204,22 @@ amount.
|
|||
|
||||
For example:
|
||||
|
||||
~~~
|
||||
# #![allow(unused_imports)]
|
||||
~~~test_harness
|
||||
extern crate test;
|
||||
|
||||
use std::slice;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_sum_1024_ints(b: &mut Bencher) {
|
||||
let v = slice::from_fn(1024, |n| n);
|
||||
b.iter(|| {v.iter().fold(0, |old, new| old + *new);} );
|
||||
let v = Vec::from_fn(1024, |n| n);
|
||||
b.iter(|| v.iter().fold(0, |old, new| old + *new));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn initialise_a_vector(b: &mut Bencher) {
|
||||
b.iter(|| {slice::from_elem(1024, 0u64);} );
|
||||
b.iter(|| Vec::from_elem(1024, 0u64));
|
||||
b.bytes = 1024 * 8;
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
~~~
|
||||
|
||||
The benchmark runner will calibrate measurement of the benchmark
|
||||
|
@ -266,19 +262,16 @@ benchmarking what one expects. For example, the compiler might
|
|||
recognize that some calculation has no external effects and remove
|
||||
it entirely.
|
||||
|
||||
~~~
|
||||
# #![allow(unused_imports)]
|
||||
~~~test_harness
|
||||
extern crate test;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_xor_1000_ints(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
range(0, 1000).fold(0, |old, new| old ^ new);
|
||||
});
|
||||
range(0, 1000).fold(0, |old, new| old ^ new);
|
||||
});
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
~~~
|
||||
|
||||
gives the following results
|
||||
|
@ -297,8 +290,11 @@ cannot remove the computation entirely. This could be done for the
|
|||
example above by adjusting the `bh.iter` call to
|
||||
|
||||
~~~
|
||||
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
|
||||
bh.iter(|| range(0, 1000).fold(0, |old, new| old ^ new))
|
||||
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let b = X;
|
||||
b.iter(|| {
|
||||
// note lack of `;` (could also use an explicit `return`).
|
||||
range(0, 1000).fold(0, |old, new| old ^ new)
|
||||
});
|
||||
~~~
|
||||
|
||||
Or, the other option is to call the generic `test::black_box`
|
||||
|
@ -309,10 +305,10 @@ forces it to consider any argument as used.
|
|||
extern crate test;
|
||||
|
||||
# fn main() {
|
||||
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
|
||||
bh.iter(|| {
|
||||
test::black_box(range(0, 1000).fold(0, |old, new| old ^ new));
|
||||
});
|
||||
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let b = X;
|
||||
b.iter(|| {
|
||||
test::black_box(range(0, 1000).fold(0, |old, new| old ^ new));
|
||||
});
|
||||
# }
|
||||
~~~
|
||||
|
||||
|
|
|
@ -171,6 +171,18 @@ You can specify that the code block should be compiled but not run with the
|
|||
```
|
||||
~~~
|
||||
|
||||
Lastly, you can specify that a code block be compiled as if `--test`
|
||||
were passed to the compiler using the `test_harness` directive.
|
||||
|
||||
~~~md
|
||||
```test_harness
|
||||
#[test]
|
||||
fn foo() {
|
||||
fail!("oops! (will run & register as failure)")
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
Rustdoc also supplies some extra sugar for helping with some tedious
|
||||
documentation examples. If a line is prefixed with `# `, then the line
|
||||
will not show up in the HTML documentation, but it will be used when
|
||||
|
|
|
@ -174,8 +174,7 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
|
|||
slice::raw::buf_as_slice((*lang).data,
|
||||
(*lang).size as uint, |rlang| {
|
||||
let rlang = str::from_utf8(rlang).unwrap();
|
||||
let (_,_,_,notrust) = parse_lang_string(rlang);
|
||||
if notrust {
|
||||
if LangString::parse(rlang).notrust {
|
||||
(my_opaque.dfltblk)(ob, &buf, lang,
|
||||
opaque as *mut libc::c_void);
|
||||
true
|
||||
|
@ -196,7 +195,7 @@ 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.as_slice());
|
||||
let test = test::maketest(test.as_slice(), krate, false);
|
||||
let test = test::maketest(test.as_slice(), krate, false, false);
|
||||
s.push_str(format!("<span id='rust-example-raw-{}' \
|
||||
class='rusttest'>{}</span>",
|
||||
i, Escape(test.as_slice())).as_slice());
|
||||
|
@ -309,16 +308,16 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
|
|||
lang: *hoedown_buffer, opaque: *mut libc::c_void) {
|
||||
unsafe {
|
||||
if text.is_null() { return }
|
||||
let (should_fail, no_run, ignore, notrust) = if lang.is_null() {
|
||||
(false, false, false, false)
|
||||
let block_info = if lang.is_null() {
|
||||
LangString::all_false()
|
||||
} else {
|
||||
slice::raw::buf_as_slice((*lang).data,
|
||||
(*lang).size as uint, |lang| {
|
||||
let s = str::from_utf8(lang).unwrap();
|
||||
parse_lang_string(s)
|
||||
LangString::parse(s)
|
||||
})
|
||||
};
|
||||
if notrust { return }
|
||||
if block_info.notrust { return }
|
||||
slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
|
||||
let opaque = opaque as *mut hoedown_html_renderer_state;
|
||||
let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
|
||||
|
@ -327,7 +326,9 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
|
|||
stripped_filtered_line(l).unwrap_or(l)
|
||||
});
|
||||
let text = lines.collect::<Vec<&str>>().connect("\n");
|
||||
tests.add_test(text.to_string(), should_fail, no_run, ignore);
|
||||
tests.add_test(text.to_string(),
|
||||
block_info.should_fail, block_info.no_run,
|
||||
block_info.ignore, block_info.test_harness);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -365,33 +366,52 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_lang_string(string: &str) -> (bool,bool,bool,bool) {
|
||||
let mut seen_rust_tags = false;
|
||||
let mut seen_other_tags = false;
|
||||
let mut should_fail = false;
|
||||
let mut no_run = false;
|
||||
let mut ignore = false;
|
||||
let mut notrust = false;
|
||||
#[deriving(Eq, PartialEq, Clone, Show)]
|
||||
struct LangString {
|
||||
should_fail: bool,
|
||||
no_run: bool,
|
||||
ignore: bool,
|
||||
notrust: bool,
|
||||
test_harness: bool,
|
||||
}
|
||||
|
||||
let mut tokens = string.as_slice().split(|c: char|
|
||||
!(c == '_' || c == '-' || c.is_alphanumeric())
|
||||
);
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
"" => {},
|
||||
"should_fail" => { should_fail = true; seen_rust_tags = true; },
|
||||
"no_run" => { no_run = true; seen_rust_tags = true; },
|
||||
"ignore" => { ignore = true; seen_rust_tags = true; },
|
||||
"notrust" => { notrust = true; seen_rust_tags = true; },
|
||||
"rust" => { notrust = false; seen_rust_tags = true; },
|
||||
_ => { seen_other_tags = true }
|
||||
impl LangString {
|
||||
fn all_false() -> LangString {
|
||||
LangString {
|
||||
should_fail: false,
|
||||
no_run: false,
|
||||
ignore: false,
|
||||
notrust: false,
|
||||
test_harness: false,
|
||||
}
|
||||
}
|
||||
|
||||
let notrust = notrust || (seen_other_tags && !seen_rust_tags);
|
||||
fn parse(string: &str) -> LangString {
|
||||
let mut seen_rust_tags = false;
|
||||
let mut seen_other_tags = false;
|
||||
let mut data = LangString::all_false();
|
||||
|
||||
(should_fail, no_run, ignore, notrust)
|
||||
let mut tokens = string.as_slice().split(|c: char|
|
||||
!(c == '_' || c == '-' || c.is_alphanumeric())
|
||||
);
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
"" => {},
|
||||
"should_fail" => { data.should_fail = true; seen_rust_tags = true; },
|
||||
"no_run" => { data.no_run = true; seen_rust_tags = true; },
|
||||
"ignore" => { data.ignore = true; seen_rust_tags = true; },
|
||||
"notrust" => { data.notrust = true; seen_rust_tags = true; },
|
||||
"rust" => { data.notrust = false; seen_rust_tags = true; },
|
||||
"test_harness" => { data.test_harness = true; seen_rust_tags = true; }
|
||||
_ => { seen_other_tags = true }
|
||||
}
|
||||
}
|
||||
|
||||
data.notrust |= seen_other_tags && !seen_rust_tags;
|
||||
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
/// By default this markdown renderer generates anchors for each header in the
|
||||
|
@ -425,19 +445,32 @@ impl<'a> fmt::Show for MarkdownWithToc<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_lang_string;
|
||||
use super::LangString;
|
||||
|
||||
#[test]
|
||||
fn test_parse_lang_string() {
|
||||
assert_eq!(parse_lang_string(""), (false,false,false,false))
|
||||
assert_eq!(parse_lang_string("rust"), (false,false,false,false))
|
||||
assert_eq!(parse_lang_string("sh"), (false,false,false,true))
|
||||
assert_eq!(parse_lang_string("notrust"), (false,false,false,true))
|
||||
assert_eq!(parse_lang_string("ignore"), (false,false,true,false))
|
||||
assert_eq!(parse_lang_string("should_fail"), (true,false,false,false))
|
||||
assert_eq!(parse_lang_string("no_run"), (false,true,false,false))
|
||||
assert_eq!(parse_lang_string("{.no_run .example}"), (false,true,false,false))
|
||||
assert_eq!(parse_lang_string("{.sh .should_fail}"), (true,false,false,false))
|
||||
assert_eq!(parse_lang_string("{.example .rust}"), (false,false,false,false))
|
||||
fn test_lang_string_parse() {
|
||||
fn t(s: &str,
|
||||
should_fail: bool, no_run: bool, ignore: bool, notrust: bool, test_harness: bool) {
|
||||
assert_eq!(LangString::parse(s), LangString {
|
||||
should_fail: should_fail,
|
||||
no_run: no_run,
|
||||
ignore: ignore,
|
||||
notrust: notrust,
|
||||
test_harness: test_harness,
|
||||
})
|
||||
}
|
||||
|
||||
t("", false,false,false,false,false);
|
||||
t("rust", false,false,false,false,false);
|
||||
t("sh", false,false,false,true,false);
|
||||
t("notrust", false,false,false,true,false);
|
||||
t("ignore", false,false,true,false,false);
|
||||
t("should_fail", true,false,false,false,false);
|
||||
t("no_run", false,true,false,false,false);
|
||||
t("test_harness", false,false,false,false,true);
|
||||
t("{.no_run .example}", false,true,false,false,false);
|
||||
t("{.sh .should_fail}", true,false,false,false,false);
|
||||
t("{.example .rust}", false,false,false,false,false);
|
||||
t("{.test_harness .rust}", false,false,false,false,true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,8 +102,10 @@ pub fn run(input: &str,
|
|||
}
|
||||
|
||||
fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
|
||||
no_run: bool) {
|
||||
let test = maketest(test, Some(cratename), true);
|
||||
no_run: bool, as_test_harness: bool) {
|
||||
// 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);
|
||||
let input = driver::StrInput(test.to_string());
|
||||
|
||||
let sessopts = config::Options {
|
||||
|
@ -116,6 +118,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
|
|||
prefer_dynamic: true,
|
||||
.. config::basic_codegen_options()
|
||||
},
|
||||
test: as_test_harness,
|
||||
..config::basic_options().clone()
|
||||
};
|
||||
|
||||
|
@ -200,7 +203,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
|
|||
}
|
||||
}
|
||||
|
||||
pub fn maketest(s: &str, cratename: Option<&str>, lints: bool) -> String {
|
||||
pub fn maketest(s: &str, cratename: Option<&str>, lints: bool, dont_insert_main: bool) -> String {
|
||||
let mut prog = String::new();
|
||||
if lints {
|
||||
prog.push_str(r"
|
||||
|
@ -222,7 +225,7 @@ pub fn maketest(s: &str, cratename: Option<&str>, lints: bool) -> String {
|
|||
None => {}
|
||||
}
|
||||
}
|
||||
if s.contains("fn main") {
|
||||
if dont_insert_main || s.contains("fn main") {
|
||||
prog.push_str(s);
|
||||
} else {
|
||||
prog.push_str("fn main() {\n ");
|
||||
|
@ -257,7 +260,8 @@ impl Collector {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_test(&mut self, test: String, should_fail: bool, no_run: bool, should_ignore: bool) {
|
||||
pub fn add_test(&mut self, test: String,
|
||||
should_fail: bool, no_run: bool, should_ignore: bool, as_test_harness: bool) {
|
||||
let name = if self.use_headers {
|
||||
let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
|
||||
format!("{}_{}", s, self.cnt)
|
||||
|
@ -279,7 +283,8 @@ impl Collector {
|
|||
cratename.as_slice(),
|
||||
libs,
|
||||
should_fail,
|
||||
no_run);
|
||||
no_run,
|
||||
as_test_harness);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue