Skip to content

Commit 74eb405

Browse files
authored
add a --chapter option to mdbook test. (#1741)
Sometimes when working on large books it is handy to be able to run mdbook on a single chapter of a book.
1 parent 13f53eb commit 74eb405

File tree

5 files changed

+77
-15
lines changed

5 files changed

+77
-15
lines changed

guide/src/cli/test.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ mdbook test path/to/book
4343
The `--library-path` (`-L`) option allows you to add directories to the library
4444
search path used by `rustdoc` when it builds and tests the examples. Multiple
4545
directories can be specified with multiple options (`-L foo -L bar`) or with a
46-
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
46+
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
4747
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
4848
contains the build output of your project. For example, if your Rust project's book is in a directory
4949
named `my-book`, the following command would include the crate's dependencies when running `test`:
@@ -61,3 +61,8 @@ The `--dest-dir` (`-d`) option allows you to change the output directory for the
6161
book. Relative paths are interpreted relative to the book's root directory. If
6262
not specified it will default to the value of the `build.build-dir` key in
6363
`book.toml`, or to `./book`.
64+
65+
#### --chapter
66+
67+
The `--chapter` (`-c`) option allows you to test a specific chapter of the
68+
book using the chapter name or the relative path to the chapter.

src/book/mod.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ impl MDBook {
246246

247247
/// Run `rustdoc` tests on the book, linking against the provided libraries.
248248
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
249+
// test_chapter with chapter:None will run all tests.
250+
self.test_chapter(library_paths, None)
251+
}
252+
253+
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
254+
/// If `chapter` is `None`, all tests will be run.
255+
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
249256
let library_args: Vec<&str> = (0..library_paths.len())
250257
.map(|_| "-L")
251258
.zip(library_paths.into_iter())
@@ -254,6 +261,8 @@ impl MDBook {
254261

255262
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
256263

264+
let mut chapter_found = false;
265+
257266
// FIXME: Is "test" the proper renderer name to use here?
258267
let preprocess_context =
259268
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
@@ -270,8 +279,16 @@ impl MDBook {
270279
_ => continue,
271280
};
272281

273-
let path = self.source_dir().join(&chapter_path);
274-
info!("Testing file: {:?}", path);
282+
if let Some(chapter) = chapter {
283+
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
284+
if chapter == "?" {
285+
info!("Skipping chapter '{}'...", ch.name);
286+
}
287+
continue;
288+
}
289+
}
290+
chapter_found = true;
291+
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
275292

276293
// write preprocessed file to tempdir
277294
let path = temp_dir.path().join(&chapter_path);
@@ -311,6 +328,11 @@ impl MDBook {
311328
if failed {
312329
bail!("One or more tests failed");
313330
}
331+
if let Some(chapter) = chapter {
332+
if !chapter_found {
333+
bail!("Chapter not found: {}", chapter);
334+
}
335+
}
314336
Ok(())
315337
}
316338

src/cmd/test.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ pub fn make_subcommand<'help>() -> App<'help> {
1717
Relative paths are interpreted relative to the book's root directory.{n}\
1818
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
1919
),
20+
).arg(
21+
Arg::new("chapter")
22+
.short('c')
23+
.long("chapter")
24+
.value_name("chapter")
25+
.help(
26+
"Only test the specified chapter{n}\
27+
Where the name of the chapter is defined in the SUMMARY.md file.{n}\
28+
Use the special name \"?\" to the list of chapter names."
29+
)
2030
)
2131
.arg(arg!([dir]
2232
"Root directory for the book{n}\
@@ -41,14 +51,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
4151
.values_of("library-path")
4252
.map(std::iter::Iterator::collect)
4353
.unwrap_or_default();
54+
let chapter: Option<&str> = args.value_of("chapter");
55+
4456
let book_dir = get_book_dir(args);
4557
let mut book = MDBook::load(&book_dir)?;
4658

4759
if let Some(dest_dir) = args.value_of("dest-dir") {
4860
book.config.build.build_dir = dest_dir.into();
4961
}
50-
51-
book.test(library_paths)?;
62+
match chapter {
63+
Some(_) => book.test_chapter(library_paths, chapter),
64+
None => book.test(library_paths),
65+
}?;
5266

5367
Ok(())
5468
}

tests/cli/test.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ fn mdbook_cli_can_correctly_test_a_passing_book() {
1010
let mut cmd = mdbook_cmd();
1111
cmd.arg("test").current_dir(temp.path());
1212
cmd.assert().success()
13-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
14-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
15-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
16-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
17-
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
13+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
14+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
15+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
16+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
17+
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap().not())
1818
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
1919
}
2020

@@ -25,10 +25,10 @@ fn mdbook_cli_detects_book_with_failing_tests() {
2525
let mut cmd = mdbook_cmd();
2626
cmd.arg("test").current_dir(temp.path());
2727
cmd.assert().failure()
28-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
29-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
30-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
31-
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
32-
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
28+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
29+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
30+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
31+
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
32+
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
3333
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
3434
}

tests/testing.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,24 @@ fn mdbook_detects_book_with_failing_tests() {
2424

2525
assert!(md.test(vec![]).is_err());
2626
}
27+
28+
#[test]
29+
fn mdbook_test_chapter() {
30+
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
31+
let mut md = MDBook::load(temp.path()).unwrap();
32+
33+
let result = md.test_chapter(vec![], Some("Introduction"));
34+
assert!(
35+
result.is_ok(),
36+
"test_chapter failed with {}",
37+
result.err().unwrap()
38+
);
39+
}
40+
41+
#[test]
42+
fn mdbook_test_chapter_not_found() {
43+
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
44+
let mut md = MDBook::load(temp.path()).unwrap();
45+
46+
assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
47+
}

0 commit comments

Comments
 (0)