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.
This commit is contained in:
Chris Lovett 2022-08-25 19:13:51 -07:00 committed by GitHub
parent 13f53eb64f
commit 74eb4059d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 15 deletions

View File

@ -43,7 +43,7 @@ mdbook test path/to/book
The `--library-path` (`-L`) option allows you to add directories to the library
search path used by `rustdoc` when it builds and tests the examples. Multiple
directories can be specified with multiple options (`-L foo -L bar`) or with a
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
contains the build output of your project. For example, if your Rust project's book is in a directory
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
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### --chapter
The `--chapter` (`-c`) option allows you to test a specific chapter of the
book using the chapter name or the relative path to the chapter.

View File

@ -246,6 +246,13 @@ impl MDBook {
/// Run `rustdoc` tests on the book, linking against the provided libraries.
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
// test_chapter with chapter:None will run all tests.
self.test_chapter(library_paths, None)
}
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
/// If `chapter` is `None`, all tests will be run.
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
let library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L")
.zip(library_paths.into_iter())
@ -254,6 +261,8 @@ impl MDBook {
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
let mut chapter_found = false;
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
@ -270,8 +279,16 @@ impl MDBook {
_ => continue,
};
let path = self.source_dir().join(&chapter_path);
info!("Testing file: {:?}", path);
if let Some(chapter) = chapter {
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
if chapter == "?" {
info!("Skipping chapter '{}'...", ch.name);
}
continue;
}
}
chapter_found = true;
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
// write preprocessed file to tempdir
let path = temp_dir.path().join(&chapter_path);
@ -311,6 +328,11 @@ impl MDBook {
if failed {
bail!("One or more tests failed");
}
if let Some(chapter) = chapter {
if !chapter_found {
bail!("Chapter not found: {}", chapter);
}
}
Ok(())
}

View File

@ -17,6 +17,16 @@ pub fn make_subcommand<'help>() -> App<'help> {
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
).arg(
Arg::new("chapter")
.short('c')
.long("chapter")
.value_name("chapter")
.help(
"Only test the specified chapter{n}\
Where the name of the chapter is defined in the SUMMARY.md file.{n}\
Use the special name \"?\" to the list of chapter names."
)
)
.arg(arg!([dir]
"Root directory for the book{n}\
@ -41,14 +51,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
.values_of("library-path")
.map(std::iter::Iterator::collect)
.unwrap_or_default();
let chapter: Option<&str> = args.value_of("chapter");
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
book.test(library_paths)?;
match chapter {
Some(_) => book.test_chapter(library_paths, chapter),
None => book.test(library_paths),
}?;
Ok(())
}

View File

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

View File

@ -24,3 +24,24 @@ fn mdbook_detects_book_with_failing_tests() {
assert!(md.test(vec![]).is_err());
}
#[test]
fn mdbook_test_chapter() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
let result = md.test_chapter(vec![], Some("Introduction"));
assert!(
result.is_ok(),
"test_chapter failed with {}",
result.err().unwrap()
);
}
#[test]
fn mdbook_test_chapter_not_found() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
}