Skip to content

Commit d9c0658

Browse files
committed
auto merge of #11120 : alexcrichton/rust/rustdoc-test, r=brson
This commit adds a `--test` flag to rustdoc to expose the ability to test code examples in doc strings. This work by using sundown's `lang` attribute to figure out how a code block should be tested. The format for this is: ``` 1. ```rust 2. ```rust,ignore 3. ```rust,notest 4. ```rust,should_fail ``` Where `rust` means that rustdoc will attempt to test is, `ignore` means that it will not execute the test but it will compile it, `notest` means that rustdoc completely ignores it, and `should_fail` means that the test should fail. This commit also leverages `extra::test` for the testing harness in order to allow parallelization and whatnot. I have fixed all existing code examples in libstd and libextra, but I have not made a pass through the crates in order to change code blocks to testable code blocks. It may also be a questionable decision to require opting-in to a testable code block. Finally, I kept our sugar in the doc suite to omit lines starting with `#` in documentation but still process them during tests. Closes #2925
2 parents f71c0dc + f9b231c commit d9c0658

38 files changed

+662
-169
lines changed

doc/rustdoc.md

+89
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,92 @@ javascript and a statically-generated search index. No special web server is
8585
required for the search.
8686

8787
[sundown]: https://github.com/vmg/sundown/
88+
89+
# Testing the Documentation
90+
91+
`rustdoc` has support for testing code examples which appear in the
92+
documentation. This is helpful for keeping code examples up to date with the
93+
source code.
94+
95+
To test documentation, the `--test` argument is passed to rustdoc:
96+
97+
~~~
98+
rustdoc --test crate.rs
99+
~~~
100+
101+
## Defining tests
102+
103+
Rust documentation currently uses the markdown format, and code blocks can refer
104+
to any piece of code-related documentation, which isn't always rust. Because of
105+
this, only code blocks with the language of "rust" will be considered for
106+
testing.
107+
108+
~~~
109+
```rust
110+
// This is a testable code block
111+
```
112+
113+
```
114+
// This is not a testable code block
115+
```
116+
117+
// This is not a testable code block (4-space indent)
118+
~~~
119+
120+
In addition to only testing "rust"-language code blocks, there are additional
121+
specifiers that can be used to dictate how a code block is tested:
122+
123+
~~~
124+
```rust,ignore
125+
// This code block is ignored by rustdoc, but is passed through to the test
126+
// harness
127+
```
128+
129+
```rust,should_fail
130+
// This code block is expected to generate a failure
131+
```
132+
~~~
133+
134+
Rustdoc also supplies some extra sugar for helping with some tedious
135+
documentation examples. If a line is prefixed with a `#` character, then the
136+
line will not show up in the HTML documentation, but it will be used when
137+
testing the code block.
138+
139+
~~~
140+
```rust
141+
# // showing 'fib' in this documentation would just be tedious and detracts from
142+
# // what's actualy being documented.
143+
# fn fib(n: int) { n + 2 }
144+
145+
do spawn { fib(200); }
146+
```
147+
~~~
148+
149+
The documentation online would look like `do spawn { fib(200); }`, but when
150+
testing this code, the `fib` function will be included (so it can compile).
151+
152+
## Running tests (advanced)
153+
154+
Running tests often requires some special configuration to filter tests, find
155+
libraries, or try running ignored examples. The testing framework that rustdoc
156+
uses is build on `extra::test`, which is also used when you compile crates with
157+
rustc's `--test` flag. Extra arguments can be passed to rustdoc's test harness
158+
with the `--test-args` flag.
159+
160+
~~~
161+
// Only run tests containing 'foo' in their name
162+
rustdoc --test lib.rs --test-args 'foo'
163+
164+
// See what's possible when running tests
165+
rustdoc --test lib.rs --test-args '--help'
166+
167+
// Run all ignored tests
168+
rustdoc --test lib.rs --test-args '--ignored'
169+
~~~
170+
171+
When testing a library, code examples will often show how functions are used,
172+
and this code often requires `use`-ing paths from the crate. To accomodate this,
173+
rustdoc will implicitly add `extern mod <crate>;` where `<crate>` is the name of
174+
the crate being tested to the top of each code example. This means that rustdoc
175+
must be able to find a compiled version of the library crate being tested. Extra
176+
search paths may be added via the `-L` flag to `rustdoc`.

mk/tests.mk

+27
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
# The names of crates that must be tested
1717
TEST_TARGET_CRATES = std extra rustuv
18+
TEST_DOC_CRATES = std extra
1819
TEST_HOST_CRATES = rustpkg rustc rustdoc syntax
1920
TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
2021

@@ -281,6 +282,7 @@ check-stage$(1)-T-$(2)-H-$(3)-exec: \
281282
check-stage$(1)-T-$(2)-H-$(3)-rpass-full-exec \
282283
check-stage$(1)-T-$(2)-H-$(3)-rmake-exec \
283284
check-stage$(1)-T-$(2)-H-$(3)-crates-exec \
285+
check-stage$(1)-T-$(2)-H-$(3)-doc-crates-exec \
284286
check-stage$(1)-T-$(2)-H-$(3)-bench-exec \
285287
check-stage$(1)-T-$(2)-H-$(3)-debuginfo-exec \
286288
check-stage$(1)-T-$(2)-H-$(3)-codegen-exec \
@@ -303,6 +305,10 @@ check-stage$(1)-T-$(2)-H-$(3)-crates-exec: \
303305

304306
endif
305307

308+
check-stage$(1)-T-$(2)-H-$(3)-doc-crates-exec: \
309+
$$(foreach crate,$$(TEST_DOC_CRATES), \
310+
check-stage$(1)-T-$(2)-H-$(3)-doc-$$(crate)-exec)
311+
306312
check-stage$(1)-T-$(2)-H-$(3)-doc-exec: \
307313
$$(foreach docname,$$(DOC_TEST_NAMES), \
308314
check-stage$(1)-T-$(2)-H-$(3)-doc-$$(docname)-exec)
@@ -734,6 +740,26 @@ $(foreach host,$(CFG_HOST), \
734740
$(foreach docname,$(DOC_TEST_NAMES), \
735741
$(eval $(call DEF_RUN_DOC_TEST,$(stage),$(target),$(host),$(docname)))))))
736742

743+
CRATE_DOC_LIB-std = $(STDLIB_CRATE)
744+
CRATE_DOC_LIB-extra = $(EXTRALIB_CRATE)
745+
746+
define DEF_CRATE_DOC_TEST
747+
748+
check-stage$(1)-T-$(2)-H-$(2)-doc-$(3)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(2),doc-$(3))
749+
750+
$$(call TEST_OK_FILE,$(1),$(2),$(2),doc-$(3)): \
751+
$$(TEST_SREQ$(1)_T_$(2)_H_$(2)) \
752+
$$(HBIN$(1)_H_$(2))/rustdoc$$(X_$(2))
753+
@$$(call E, run doc-$(3) [$(2)])
754+
$$(Q)$$(HBIN$(1)_H_$(2))/rustdoc$$(X_$(2)) --test \
755+
$$(CRATE_DOC_LIB-$(3)) && touch $$@
756+
757+
endef
758+
759+
$(foreach host,$(CFG_HOST), \
760+
$(foreach stage,$(STAGES), \
761+
$(foreach crate,$(TEST_DOC_CRATES), \
762+
$(eval $(call DEF_CRATE_DOC_TEST,$(stage),$(host),$(crate))))))
737763

738764
######################################################################
739765
# Extracting tests for docs
@@ -762,6 +788,7 @@ $(foreach host,$(CFG_HOST), \
762788
TEST_GROUPS = \
763789
crates \
764790
$(foreach crate,$(TEST_CRATES),$(crate)) \
791+
$(foreach crate,$(TEST_DOC_CRATES),doc-$(crate)) \
765792
rpass \
766793
rpass-full \
767794
rfail \

src/libextra/arc.rs

+20-16
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@
1818
* With simple pipes, without Arc, a copy would have to be made for each task.
1919
*
2020
* ```rust
21-
* extern mod std;
22-
* use extra::arc;
23-
* let numbers=vec::from_fn(100, |ind| (ind as float)*rand::random());
24-
* let shared_numbers=arc::Arc::new(numbers);
21+
* use extra::arc::Arc;
22+
* use std::{rand, vec};
2523
*
26-
* do 10.times {
27-
* let (port, chan) = stream();
24+
* let numbers = vec::from_fn(100, |i| (i as f32) * rand::random());
25+
* let shared_numbers = Arc::new(numbers);
26+
*
27+
* for _ in range(0, 10) {
28+
* let (port, chan) = Chan::new();
2829
* chan.send(shared_numbers.clone());
2930
*
3031
* do spawn {
31-
* let shared_numbers=port.recv();
32-
* let local_numbers=shared_numbers.get();
32+
* let shared_numbers = port.recv();
33+
* let local_numbers = shared_numbers.get();
3334
*
3435
* // Work with the local numbers
3536
* }
@@ -448,15 +449,18 @@ impl<T:Freeze + Send> RWArc<T> {
448449
* # Example
449450
*
450451
* ```rust
451-
* do arc.write_downgrade |mut write_token| {
452-
* do write_token.write_cond |state, condvar| {
453-
* ... exclusive access with mutable state ...
454-
* }
452+
* use extra::arc::RWArc;
453+
*
454+
* let arc = RWArc::new(1);
455+
* arc.write_downgrade(|mut write_token| {
456+
* write_token.write_cond(|state, condvar| {
457+
* // ... exclusive access with mutable state ...
458+
* });
455459
* let read_token = arc.downgrade(write_token);
456-
* do read_token.read |state| {
457-
* ... shared access with immutable state ...
458-
* }
459-
* }
460+
* read_token.read(|state| {
461+
* // ... shared access with immutable state ...
462+
* });
463+
* })
460464
* ```
461465
*/
462466
pub fn write_downgrade<U>(&self, blk: |v: RWWriteMode<T>| -> U) -> U {

src/libextra/future.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
* # Example
1616
*
1717
* ```rust
18+
* use extra::future::Future;
1819
* # fn fib(n: uint) -> uint {42};
1920
* # fn make_a_sandwich() {};
20-
* let mut delayed_fib = extra::future::spawn (|| fib(5000) );
21+
* let mut delayed_fib = do Future::spawn { fib(5000) };
2122
* make_a_sandwich();
2223
* println!("fib(5000) = {}", delayed_fib.get())
2324
* ```

src/libextra/glob.rs

+23-16
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ pub struct GlobIterator {
5353
/// `puppies.jpg` and `hamsters.gif`:
5454
///
5555
/// ```rust
56+
/// use extra::glob::glob;
57+
///
5658
/// for path in glob("/media/pictures/*.jpg") {
57-
/// println(path.to_str());
59+
/// println!("{}", path.display());
5860
/// }
5961
/// ```
6062
///
@@ -188,21 +190,23 @@ enum MatchResult {
188190
impl Pattern {
189191

190192
/**
191-
* This function compiles Unix shell style patterns: `?` matches any single character,
192-
* `*` matches any (possibly empty) sequence of characters and `[...]` matches any character
193-
* inside the brackets, unless the first character is `!` in which case it matches any
194-
* character except those between the `!` and the `]`. Character sequences can also specify
195-
* ranges of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any character
196-
* between 0 and 9 inclusive.
193+
* This function compiles Unix shell style patterns: `?` matches any single
194+
* character, `*` matches any (possibly empty) sequence of characters and
195+
* `[...]` matches any character inside the brackets, unless the first
196+
* character is `!` in which case it matches any character except those
197+
* between the `!` and the `]`. Character sequences can also specify ranges
198+
* of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any
199+
* character between 0 and 9 inclusive.
197200
*
198-
* The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets (e.g. `[?]`).
199-
* When a `]` occurs immediately following `[` or `[!` then it is interpreted as
200-
* being part of, rather then ending, the character set, so `]` and NOT `]` can be
201-
* matched by `[]]` and `[!]]` respectively. The `-` character can be specified inside a
202-
* character sequence pattern by placing it at the start or the end, e.g. `[abc-]`.
201+
* The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets
202+
* (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then
203+
* it is interpreted as being part of, rather then ending, the character
204+
* set, so `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively.
205+
* The `-` character can be specified inside a character sequence pattern by
206+
* placing it at the start or the end, e.g. `[abc-]`.
203207
*
204-
* When a `[` does not have a closing `]` before the end of the string then the `[` will
205-
* be treated literally.
208+
* When a `[` does not have a closing `]` before the end of the string then
209+
* the `[` will be treated literally.
206210
*/
207211
pub fn new(pattern: &str) -> Pattern {
208212

@@ -229,7 +233,8 @@ impl Pattern {
229233
match chars.slice_from(i + 3).position_elem(&']') {
230234
None => (),
231235
Some(j) => {
232-
let cs = parse_char_specifiers(chars.slice(i + 2, i + 3 + j));
236+
let chars = chars.slice(i + 2, i + 3 + j);
237+
let cs = parse_char_specifiers(chars);
233238
tokens.push(AnyExcept(cs));
234239
i += j + 4;
235240
continue;
@@ -292,6 +297,8 @@ impl Pattern {
292297
* # Example
293298
*
294299
* ```rust
300+
* use extra::glob::Pattern;
301+
*
295302
* assert!(Pattern::new("c?t").matches("cat"));
296303
* assert!(Pattern::new("k[!e]tteh").matches("kitteh"));
297304
* assert!(Pattern::new("d*g").matches("doog"));
@@ -509,7 +516,7 @@ impl MatchOptions {
509516
*
510517
* This function always returns this value:
511518
*
512-
* ```rust
519+
* ```rust,ignore
513520
* MatchOptions {
514521
* case_sensitive: true,
515522
* require_literal_separator: false.

src/libextra/hex.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ impl<'a> ToHex for &'a [u8] {
2828
* # Example
2929
*
3030
* ```rust
31-
* extern mod extra;
3231
* use extra::hex::ToHex;
3332
*
3433
* fn main () {
@@ -71,12 +70,11 @@ impl<'a> FromHex for &'a str {
7170
* This converts a string literal to hexadecimal and back.
7271
*
7372
* ```rust
74-
* extern mod extra;
7573
* use extra::hex::{FromHex, ToHex};
7674
* use std::str;
7775
*
7876
* fn main () {
79-
* let hello_str = "Hello, World".to_hex();
77+
* let hello_str = "Hello, World".as_bytes().to_hex();
8078
* println!("{}", hello_str);
8179
* let bytes = hello_str.from_hex().unwrap();
8280
* println!("{:?}", bytes);

src/libextra/lru_cache.rs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
//! # Example
1818
//!
1919
//! ```rust
20+
//! use extra::lru_cache::LruCache;
21+
//!
2022
//! let mut cache: LruCache<int, int> = LruCache::new(2);
2123
//! cache.put(1, 10);
2224
//! cache.put(2, 20);

src/libextra/sync.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -568,13 +568,16 @@ impl RWLock {
568568
* # Example
569569
*
570570
* ```rust
571+
* use extra::sync::RWLock;
572+
*
573+
* let lock = RWLock::new();
571574
* lock.write_downgrade(|mut write_token| {
572575
* write_token.write_cond(|condvar| {
573-
* ... exclusive access ...
576+
* // ... exclusive access ...
574577
* });
575578
* let read_token = lock.downgrade(write_token);
576579
* read_token.read(|| {
577-
* ... shared access ...
580+
* // ... shared access ...
578581
* })
579582
* })
580583
* ```

src/libextra/url.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use std::uint;
2626
/// # Example
2727
///
2828
/// ```rust
29+
/// use extra::url::{Url, UserInfo};
30+
///
2931
/// let url = Url { scheme: ~"https",
3032
/// user: Some(UserInfo { user: ~"username", pass: None }),
3133
/// host: ~"example.com",
@@ -388,8 +390,10 @@ fn query_from_str(rawquery: &str) -> Query {
388390
* # Example
389391
*
390392
* ```rust
393+
* use extra::url;
394+
*
391395
* let query = ~[(~"title", ~"The Village"), (~"north", ~"52.91"), (~"west", ~"4.10")];
392-
* println(query_to_str(&query)); // title=The%20Village&north=52.91&west=4.10
396+
* println(url::query_to_str(&query)); // title=The%20Village&north=52.91&west=4.10
393397
* ```
394398
*/
395399
pub fn query_to_str(query: &Query) -> ~str {

0 commit comments

Comments
 (0)