diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 1580091488f20..43561fa4f2fc1 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -1264,15 +1264,15 @@ impl Step for Compiletest { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -struct DocTest { +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct BookTest { compiler: Compiler, - path: &'static str, + path: PathBuf, name: &'static str, is_ext_doc: bool, } -impl Step for DocTest { +impl Step for BookTest { type Output = (); const ONLY_HOSTS: bool = true; @@ -1280,12 +1280,59 @@ impl Step for DocTest { run.never() } - /// Runs `rustdoc --test` for all documentation in `src/doc`. + /// Runs the documentation tests for a book in `src/doc`. /// - /// This will run all tests in our markdown documentation (e.g., the book) - /// located in `src/doc`. The `rustdoc` that's run is the one that sits next to - /// `compiler`. + /// This uses the `rustdoc` that sits next to `compiler`. fn run(self, builder: &Builder<'_>) { + // External docs are different from local because: + // - Some books need pre-processing by mdbook before being tested. + // - They need to save their state to toolstate. + // - They are only tested on the "checktools" builders. + // + // The local docs are tested by default, and we don't want to pay the + // cost of building mdbook, so they use `rustdoc --test` directly. + // Also, the unstable book is special because SUMMARY.md is generated, + // so it is easier to just run `rustdoc` on its files. + if self.is_ext_doc { + self.run_ext_doc(builder); + } else { + self.run_local_doc(builder); + } + } +} + +impl BookTest { + /// This runs the equivalent of `mdbook test` (via the rustbook wrapper) + /// which in turn runs `rustdoc --test` on each file in the book. + fn run_ext_doc(self, builder: &Builder<'_>) { + let compiler = self.compiler; + + builder.ensure(compile::Std { compiler, target: compiler.host }); + + // mdbook just executes a binary named "rustdoc", so we need to update + // PATH so that it points to our rustdoc. + let mut rustdoc_path = builder.rustdoc(compiler); + rustdoc_path.pop(); + let old_path = env::var_os("PATH").unwrap_or_default(); + let new_path = env::join_paths(iter::once(rustdoc_path).chain(env::split_paths(&old_path))) + .expect("could not add rustdoc to PATH"); + + let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook); + let path = builder.src.join(&self.path); + rustbook_cmd.env("PATH", new_path).arg("test").arg(path); + builder.add_rust_test_threads(&mut rustbook_cmd); + builder.info(&format!("Testing rustbook {}", self.path.display())); + let _time = util::timeit(&builder); + let toolstate = if try_run(builder, &mut rustbook_cmd) { + ToolState::TestPass + } else { + ToolState::TestFail + }; + builder.save_toolstate(self.name, toolstate); + } + + /// This runs `rustdoc --test` on all `.md` files in the path. + fn run_local_doc(self, builder: &Builder<'_>) { let compiler = self.compiler; builder.ensure(compile::Std { compiler, target: compiler.host }); @@ -1294,7 +1341,6 @@ impl Step for DocTest { // tests for all files that end in `*.md` let mut stack = vec![builder.src.join(self.path)]; let _time = util::timeit(&builder); - let mut files = Vec::new(); while let Some(p) = stack.pop() { if p.is_dir() { @@ -1306,25 +1352,13 @@ impl Step for DocTest { continue; } - // The nostarch directory in the book is for no starch, and so isn't - // guaranteed to builder. We don't care if it doesn't build, so skip it. - if p.to_str().map_or(false, |p| p.contains("nostarch")) { - continue; - } - files.push(p); } files.sort(); - let mut toolstate = ToolState::TestPass; for file in files { - if !markdown_test(builder, compiler, &file) { - toolstate = ToolState::TestFail; - } - } - if self.is_ext_doc { - builder.save_toolstate(self.name, toolstate); + markdown_test(builder, compiler, &file); } } } @@ -1353,9 +1387,9 @@ macro_rules! test_book { } fn run(self, builder: &Builder<'_>) { - builder.ensure(DocTest { + builder.ensure(BookTest { compiler: self.compiler, - path: $path, + path: PathBuf::from($path), name: $book_name, is_ext_doc: !$default, }); diff --git a/src/doc/book b/src/doc/book index 87dd684367857..6fb3705e52303 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit 87dd6843678575f8dda962f239d14ef4be14b352 +Subproject commit 6fb3705e5230311b096d47f7e2c91f9ce24393d0 diff --git a/src/doc/edition-guide b/src/doc/edition-guide index 1a2390247ad6d..37f9e68484111 160000 --- a/src/doc/edition-guide +++ b/src/doc/edition-guide @@ -1 +1 @@ -Subproject commit 1a2390247ad6d08160e0dd74f40a01a9578659c2 +Subproject commit 37f9e6848411188a1062ead1bd8ebe4b8aa16899 diff --git a/src/doc/embedded-book b/src/doc/embedded-book index 4d78994915af1..b2e1092bf67bd 160000 --- a/src/doc/embedded-book +++ b/src/doc/embedded-book @@ -1 +1 @@ -Subproject commit 4d78994915af1bde9a95c04a8c27d8dca066232a +Subproject commit b2e1092bf67bd4d7686c4553f186edbb7f5f92db diff --git a/src/doc/reference b/src/doc/reference index 11e893fc1357b..64239df6d1735 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 11e893fc1357bc688418ddf1087c2b7aa25d154d +Subproject commit 64239df6d173562b9deb4f012e4c3e6e960c4754 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index 1c2bd024d13f8..32facd5522ddb 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit 1c2bd024d13f8011307e13386cf1fea2180352b5 +Subproject commit 32facd5522ddbbf37baf01e4e4b6562bc55c071a diff --git a/src/doc/rustdoc/book.toml b/src/doc/rustdoc/book.toml new file mode 100644 index 0000000000000..ba30c107667ed --- /dev/null +++ b/src/doc/rustdoc/book.toml @@ -0,0 +1,4 @@ +[book] +authors = ["The Rust Project Developers"] +src = "src" +title = "The rustdoc book" diff --git a/src/tools/rustbook/src/main.rs b/src/tools/rustbook/src/main.rs index 023f5aa1e284b..ca4bf4ea662c5 100644 --- a/src/tools/rustbook/src/main.rs +++ b/src/tools/rustbook/src/main.rs @@ -25,6 +25,11 @@ fn main() { .arg_from_usage(d_message) .arg_from_usage(dir_message), ) + .subcommand( + SubCommand::with_name("test") + .about("Tests that a book's Rust code samples compile") + .arg_from_usage(dir_message), + ) .subcommand( SubCommand::with_name("linkcheck") .about("Run linkcheck with mdBook 3") @@ -36,13 +41,12 @@ fn main() { match matches.subcommand() { ("build", Some(sub_matches)) => { if let Err(e) = build(sub_matches) { - eprintln!("Error: {}", e); - - for cause in e.iter().skip(1) { - eprintln!("\tCaused By: {}", cause); - } - - ::std::process::exit(101); + handle_error(e); + } + } + ("test", Some(sub_matches)) => { + if let Err(e) = test(sub_matches) { + handle_error(e); } } ("linkcheck", Some(sub_matches)) => { @@ -146,6 +150,12 @@ pub fn build(args: &ArgMatches<'_>) -> Result3<()> { Ok(()) } +fn test(args: &ArgMatches<'_>) -> Result3<()> { + let book_dir = get_book_dir(args); + let mut book = MDBook::load(&book_dir)?; + book.test(vec![]) +} + fn get_book_dir(args: &ArgMatches<'_>) -> PathBuf { if let Some(dir) = args.value_of("dir") { // Check if path is relative from current dir, or absolute... @@ -155,3 +165,13 @@ fn get_book_dir(args: &ArgMatches<'_>) -> PathBuf { env::current_dir().unwrap() } } + +fn handle_error(error: mdbook::errors::Error) -> ! { + eprintln!("Error: {}", error); + + for cause in error.iter().skip(1) { + eprintln!("\tCaused By: {}", cause); + } + + ::std::process::exit(101); +} diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index c8c61b6fb5015..be1e598d8d4fe 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -58,6 +58,7 @@ fn filter_dirs(path: &Path) -> bool { "src/tools/rls", "src/tools/rust-installer", "src/tools/rustfmt", + "src/doc/book", // Filter RLS output directories "target/rls", ];