diff --git a/guide/src/cli/test.md b/guide/src/cli/test.md index e134dc9b..a542f3ce 100644 --- a/guide/src/cli/test.md +++ b/guide/src/cli/test.md @@ -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. \ No newline at end of file diff --git a/src/book/mod.rs b/src/book/mod.rs index 9745d2b7..e407ccc3 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -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(()) } diff --git a/src/cmd/test.rs b/src/cmd/test.rs index 02f982a4..f5ca3ee4 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -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(()) } diff --git a/tests/cli/test.rs b/tests/cli/test.rs index bc525d9a..63114d3a 100644 --- a/tests/cli/test.rs +++ b/tests/cli/test.rs @@ -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()); } diff --git a/tests/testing.rs b/tests/testing.rs index 2b2c0fd0..3030c5cb 100644 --- a/tests/testing.rs +++ b/tests/testing.rs @@ -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()); +}