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

@ -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 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 not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`. `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. /// Run `rustdoc` tests on the book, linking against the provided libraries.
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> { 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()) let library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L") .map(|_| "-L")
.zip(library_paths.into_iter()) .zip(library_paths.into_iter())
@ -254,6 +261,8 @@ impl MDBook {
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?; let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
let mut chapter_found = false;
// FIXME: Is "test" the proper renderer name to use here? // FIXME: Is "test" the proper renderer name to use here?
let preprocess_context = let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string()); PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
@ -270,8 +279,16 @@ impl MDBook {
_ => continue, _ => continue,
}; };
let path = self.source_dir().join(&chapter_path); if let Some(chapter) = chapter {
info!("Testing file: {:?}", path); 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 // write preprocessed file to tempdir
let path = temp_dir.path().join(&chapter_path); let path = temp_dir.path().join(&chapter_path);
@ -311,6 +328,11 @@ impl MDBook {
if failed { if failed {
bail!("One or more tests failed"); bail!("One or more tests failed");
} }
if let Some(chapter) = chapter {
if !chapter_found {
bail!("Chapter not found: {}", chapter);
}
}
Ok(()) 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}\ 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`.", 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] .arg(arg!([dir]
"Root directory for the book{n}\ "Root directory for the book{n}\
@ -41,14 +51,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
.values_of("library-path") .values_of("library-path")
.map(std::iter::Iterator::collect) .map(std::iter::Iterator::collect)
.unwrap_or_default(); .unwrap_or_default();
let chapter: Option<&str> = args.value_of("chapter");
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") { if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into(); book.config.build.build_dir = dest_dir.into();
} }
match chapter {
book.test(library_paths)?; Some(_) => book.test_chapter(library_paths, chapter),
None => book.test(library_paths),
}?;
Ok(()) Ok(())
} }

View File

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