//! Tests for custom preprocessors. use crate::prelude::*; use anyhow::Result; use mdbook_core::book::Book; use mdbook_driver::builtin_preprocessors::CmdPreprocessor; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use std::sync::{Arc, Mutex}; struct Spy(Arc>); #[derive(Debug, Default)] struct Inner { run_count: usize, rendered_with: Vec, } impl Preprocessor for Spy { fn name(&self) -> &str { "dummy" } fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { let mut inner = self.0.lock().unwrap(); inner.run_count += 1; inner.rendered_with.push(ctx.renderer.clone()); Ok(book) } } // Test that preprocessor gets run. #[test] fn runs_preprocessors() { let test = BookTest::init(|_| {}); let spy: Arc> = Default::default(); let mut book = test.load_book(); book.with_preprocessor(Spy(Arc::clone(&spy))); book.build().unwrap(); let inner = spy.lock().unwrap(); assert_eq!(inner.run_count, 1); assert_eq!(inner.rendered_with, ["html"]); } // No-op preprocessor works. #[test] fn nop_preprocessor() { BookTest::from_dir("preprocessor/nop_preprocessor").run("build", |cmd| { cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend [TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }); } // Failing preprocessor generates an error. #[test] fn failing_preprocessor() { BookTest::from_dir("preprocessor/failing_preprocessor") .run("build", |cmd| { cmd.expect_failure() .expect_stdout(str![[""]]) .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started Boom!!1! [TIMESTAMP] [ERROR] (mdbook_core::utils): Error: The "nop-preprocessor" preprocessor exited unsuccessfully with [EXIT_STATUS]: 1 status "#]]); }); } fn example() -> CmdPreprocessor { CmdPreprocessor::new( "nop-preprocessor".to_string(), "cargo run --quiet --example nop-preprocessor --".to_string(), std::env::current_dir().unwrap(), ) } #[test] fn example_supports_whatever() { let cmd = example(); let got = cmd.supports_renderer("whatever").unwrap(); assert_eq!(got, true); } #[test] fn example_doesnt_support_not_supported() { let cmd = example(); let got = cmd.supports_renderer("not-supported").unwrap(); assert_eq!(got, false); } // Checks the behavior of a relative path to a preprocessor. #[test] fn relative_command_path() { let mut test = BookTest::init(|_| {}); test.rust_program( "preprocessors/my-preprocessor", r#" fn main() { let mut args = std::env::args().skip(1); if args.next().as_deref() == Some("supports") { std::fs::write("support-check", args.next().unwrap()).unwrap(); return; } use std::io::Read; let mut s = String::new(); std::io::stdin().read_to_string(&mut s).unwrap(); std::fs::write("preprocessor-ran", "test").unwrap(); println!("{{\"sections\": []}}"); } "#, ) .change_file( "book.toml", "[preprocessor.my-preprocessor]\n\ command = 'preprocessors/my-preprocessor'\n", ) .run("build", |cmd| { cmd.expect_stdout(str![""]).expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend [TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) .check_file("support-check", "html") .check_file("preprocessor-ran", "test") // Try again, but outside of the book root to check relative path behavior. .rm_r("support-check") .rm_r("preprocessor-ran") .run("build ..", |cmd| { cmd.current_dir(cmd.dir.join("src")) .expect_stdout(str![""]) .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend [TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/src/../book` "#]]); }) .check_file("support-check", "html") .check_file("preprocessor-ran", "test"); }