Merge pull request #2676 from ehuss/booktest
Introduce new testsuite infrastructure
This commit is contained in:
commit
9b12c5130f
195 changed files with 2902 additions and 10601 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,6 +9,7 @@ guide/book
|
|||
.vscode
|
||||
tests/dummy_book/book/
|
||||
test_book/book/
|
||||
tests/testsuite/*/*/book/
|
||||
|
||||
# Ignore Jetbrains specific files.
|
||||
.idea/
|
||||
|
|
|
|||
174
Cargo.lock
generated
174
Cargo.lock
generated
|
|
@ -75,6 +75,15 @@ version = "1.0.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-lossy"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
|
|
@ -93,6 +102,19 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-svg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
"html-escape",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.7"
|
||||
|
|
@ -110,22 +132,6 @@ version = "1.0.95"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"libc",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
|
|
@ -288,6 +294,15 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "content_inspector"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
|
|
@ -427,12 +442,6 @@ version = "0.1.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
|
@ -455,10 +464,10 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "elasticlunr-rs"
|
||||
|
|
@ -529,15 +538,6 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
@ -737,6 +737,15 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "html-escape"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
|
||||
dependencies = [
|
||||
"utf8-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.26.0"
|
||||
|
|
@ -1209,7 +1218,6 @@ version = "0.4.48"
|
|||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
@ -1226,7 +1234,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
"opener",
|
||||
"pathdiff",
|
||||
"predicates",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.10.3",
|
||||
"regex",
|
||||
|
|
@ -1236,6 +1243,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"sha2",
|
||||
"shlex",
|
||||
"snapbox",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
|
|
@ -1615,36 +1623,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.1"
|
||||
|
|
@ -1920,6 +1898,12 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
|
|
@ -1947,6 +1931,37 @@ version = "1.13.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"anstyle-svg",
|
||||
"content_inspector",
|
||||
"dunce",
|
||||
"filetime",
|
||||
"normalize-line-endings",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"snapbox-macros",
|
||||
"tempfile",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox-macros"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
|
|
@ -2063,12 +2078,6 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
|
@ -2261,6 +2270,12 @@ version = "1.0.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
|
|
@ -2284,6 +2299,12 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
|
|
@ -2302,15 +2323,6 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
|
|||
|
|
@ -64,10 +64,9 @@ elasticlunr-rs = { version = "3.0.2", optional = true }
|
|||
ammonia = { version = "4.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.11"
|
||||
predicates = "3.0.3"
|
||||
select = "0.6.0"
|
||||
semver = "1.0.17"
|
||||
snapbox = { version = "0.6.21", features = ["diff", "dir", "term-svg", "regex", "json"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
walkdir = "2.3.3"
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ fn main() {
|
|||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
} else if let Err(e) = handle_preprocessing(&preprocessor) {
|
||||
eprintln!("{e}");
|
||||
eprintln!("{e:?}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
//! Integration tests to make sure alternative backends work.
|
||||
|
||||
use mdbook::config::Config;
|
||||
use mdbook::MDBook;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
|
||||
#[test]
|
||||
fn passing_alternate_backend() {
|
||||
let (md, _temp) = dummy_book_with_backend("passing", success_cmd(), false);
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failing_alternate_backend() {
|
||||
let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false);
|
||||
|
||||
md.build().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_backends_are_fatal() {
|
||||
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
|
||||
let got = md.build();
|
||||
assert!(got.is_err());
|
||||
let error_message = got.err().unwrap().to_string();
|
||||
assert_eq!(error_message, "Rendering failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_optional_backends_are_not_fatal() {
|
||||
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true);
|
||||
assert!(md.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alternate_backend_with_arguments() {
|
||||
let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false);
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backends_receive_render_context_via_stdin() {
|
||||
use mdbook::renderer::RenderContext;
|
||||
use std::fs::File;
|
||||
|
||||
let (md, temp) = dummy_book_with_backend("cat-to-file", "renderers/myrenderer", false);
|
||||
|
||||
let renderers = temp.path().join("renderers");
|
||||
fs::create_dir(&renderers).unwrap();
|
||||
rust_exe(
|
||||
&renderers,
|
||||
"myrenderer",
|
||||
r#"fn main() {
|
||||
use std::io::Read;
|
||||
let mut s = String::new();
|
||||
std::io::stdin().read_to_string(&mut s).unwrap();
|
||||
std::fs::write("out.txt", s).unwrap();
|
||||
}"#,
|
||||
);
|
||||
|
||||
let out_file = temp.path().join("book/out.txt");
|
||||
|
||||
assert!(!out_file.exists());
|
||||
md.build().unwrap();
|
||||
assert!(out_file.exists());
|
||||
|
||||
let got = RenderContext::from_json(File::open(&out_file).unwrap());
|
||||
assert!(got.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_command_path() {
|
||||
// Checks behavior of relative paths for the `command` setting.
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let renderers = temp.path().join("renderers");
|
||||
fs::create_dir(&renderers).unwrap();
|
||||
rust_exe(
|
||||
&renderers,
|
||||
"myrenderer",
|
||||
r#"fn main() {
|
||||
std::fs::write("output", "test").unwrap();
|
||||
}"#,
|
||||
);
|
||||
let do_test = |cmd_path| {
|
||||
let mut config = Config::default();
|
||||
config
|
||||
.set("output.html", toml::value::Table::new())
|
||||
.unwrap();
|
||||
config.set("output.myrenderer.command", cmd_path).unwrap();
|
||||
let md = MDBook::init(temp.path())
|
||||
.with_config(config)
|
||||
.build()
|
||||
.unwrap();
|
||||
let output = temp.path().join("book/myrenderer/output");
|
||||
assert!(!output.exists());
|
||||
md.build().unwrap();
|
||||
assert!(output.exists());
|
||||
fs::remove_file(output).unwrap();
|
||||
};
|
||||
// Legacy paths work, relative to the output directory.
|
||||
if cfg!(windows) {
|
||||
do_test("../../renderers/myrenderer.exe");
|
||||
} else {
|
||||
do_test("../../renderers/myrenderer");
|
||||
}
|
||||
// Modern path, relative to the book directory.
|
||||
do_test("renderers/myrenderer");
|
||||
}
|
||||
|
||||
fn dummy_book_with_backend(
|
||||
name: &str,
|
||||
command: &str,
|
||||
backend_is_optional: bool,
|
||||
) -> (MDBook, TempDir) {
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
|
||||
let mut config = Config::default();
|
||||
config
|
||||
.set(format!("output.{name}.command"), command)
|
||||
.unwrap();
|
||||
|
||||
if backend_is_optional {
|
||||
config.set(format!("output.{name}.optional"), true).unwrap();
|
||||
}
|
||||
|
||||
let md = MDBook::init(temp.path())
|
||||
.with_config(config)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
(md, temp)
|
||||
}
|
||||
|
||||
fn fail_cmd() -> &'static str {
|
||||
if cfg!(windows) {
|
||||
r#"cmd.exe /c "exit 1""#
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
}
|
||||
|
||||
fn success_cmd() -> &'static str {
|
||||
if cfg!(windows) {
|
||||
r#"cmd.exe /c "exit 0""#
|
||||
} else {
|
||||
"true"
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_exe(temp: &Path, name: &str, src: &str) {
|
||||
let rs = temp.join(name).with_extension("rs");
|
||||
fs::write(&rs, src).unwrap();
|
||||
let status = std::process::Command::new("rustc")
|
||||
.arg(rs)
|
||||
.current_dir(temp)
|
||||
.status()
|
||||
.expect("rustc should run");
|
||||
assert!(status.success());
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
mod dummy_book;
|
||||
|
||||
use crate::dummy_book::DummyBook;
|
||||
use mdbook::book::Book;
|
||||
use mdbook::config::Config;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||
use mdbook::renderer::{RenderContext, Renderer};
|
||||
use mdbook::MDBook;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct Spy(Arc<Mutex<Inner>>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Inner {
|
||||
run_count: usize,
|
||||
rendered_with: Vec<String>,
|
||||
}
|
||||
|
||||
impl Preprocessor for Spy {
|
||||
fn name(&self) -> &str {
|
||||
"dummy"
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
inner.run_count += 1;
|
||||
inner.rendered_with.push(ctx.renderer.clone());
|
||||
Ok(book)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for Spy {
|
||||
fn name(&self) -> &str {
|
||||
"dummy"
|
||||
}
|
||||
|
||||
fn render(&self, _ctx: &RenderContext) -> Result<()> {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
inner.run_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_runs_preprocessors() {
|
||||
let spy: Arc<Mutex<Inner>> = Default::default();
|
||||
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let cfg = Config::default();
|
||||
|
||||
let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
|
||||
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.len(), 1);
|
||||
assert_eq!(
|
||||
"html", inner.rendered_with[0],
|
||||
"We should have been run with the default HTML renderer"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_runs_renderers() {
|
||||
let spy: Arc<Mutex<Inner>> = Default::default();
|
||||
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let cfg = Config::default();
|
||||
|
||||
let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
|
||||
book.with_renderer(Spy(Arc::clone(&spy)));
|
||||
book.build().unwrap();
|
||||
|
||||
let inner = spy.lock().unwrap();
|
||||
assert_eq!(inner.run_count, 1);
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
use crate::cli::cmd::mdbook_cmd;
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
#[test]
|
||||
fn mdbook_cli_dummy_book_generates_index_html() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
|
||||
// doesn't exist before
|
||||
assert!(!temp.path().join("book").exists());
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.arg("build").current_dir(temp.path());
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicates::str::is_match(r##"Stack depth exceeded in first[\\/]recursive.md."##)
|
||||
.unwrap(),
|
||||
)
|
||||
.stderr(predicates::str::contains(
|
||||
r##"[INFO] (mdbook::book): Running the html backend"##,
|
||||
));
|
||||
|
||||
// exists afterward
|
||||
assert!(temp.path().join("book").exists());
|
||||
|
||||
let index_file = temp.path().join("book/index.html");
|
||||
assert!(index_file.exists());
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
use assert_cmd::Command;
|
||||
|
||||
pub(crate) fn mdbook_cmd() -> Command {
|
||||
let mut cmd = Command::cargo_bin("mdbook").unwrap();
|
||||
cmd.env_remove("RUST_LOG");
|
||||
cmd
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
use crate::cli::cmd::mdbook_cmd;
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use mdbook::config::Config;
|
||||
|
||||
/// Run `mdbook init` with `--force` to skip the confirmation prompts
|
||||
#[test]
|
||||
fn base_mdbook_init_can_skip_confirmation_prompts() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
|
||||
// doesn't exist before
|
||||
assert!(!temp.path().join("book").exists());
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.args(["init", "--force"]).current_dir(temp.path());
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicates::str::contains("\nAll done, no errors...\n"));
|
||||
|
||||
let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(config.book.title, None);
|
||||
|
||||
assert!(!temp.path().join(".gitignore").exists());
|
||||
}
|
||||
|
||||
/// Run `mdbook init` with `--title` without git config.
|
||||
///
|
||||
/// Regression test for https://github.com/rust-lang/mdBook/issues/2485
|
||||
#[test]
|
||||
fn no_git_config_with_title() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
|
||||
// doesn't exist before
|
||||
assert!(!temp.path().join("book").exists());
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.args(["init", "--title", "Example title"])
|
||||
.current_dir(temp.path())
|
||||
.env("GIT_CONFIG_GLOBAL", "")
|
||||
.env("GIT_CONFIG_NOSYSTEM", "1");
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicates::str::contains("\nAll done, no errors...\n"));
|
||||
|
||||
let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(config.book.title.as_deref(), Some("Example title"));
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
mod build;
|
||||
mod cmd;
|
||||
mod init;
|
||||
mod test;
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
use crate::cli::cmd::mdbook_cmd;
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use predicates::boolean::PredicateBooleanExt;
|
||||
|
||||
#[test]
|
||||
fn mdbook_cli_can_correctly_test_a_passing_book() {
|
||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.arg("test").current_dir(temp.path());
|
||||
cmd.assert().success()
|
||||
.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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_cli_detects_book_with_failing_tests() {
|
||||
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.arg("test").current_dir(temp.path());
|
||||
cmd.assert().failure()
|
||||
.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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_cli() {
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.assert()
|
||||
.failure()
|
||||
.code(2)
|
||||
.stdout(predicates::str::is_empty())
|
||||
.stderr(predicates::str::contains(
|
||||
"Creates a book from markdown files",
|
||||
));
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
mod cli;
|
||||
mod dummy_book;
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
mod dummy_book;
|
||||
|
||||
use crate::dummy_book::DummyBook;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||
use mdbook::MDBook;
|
||||
|
||||
fn example() -> CmdPreprocessor {
|
||||
CmdPreprocessor::new(
|
||||
"nop-preprocessor".to_string(),
|
||||
"cargo run --example nop-preprocessor --".to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_supports_whatever() {
|
||||
let cmd = example();
|
||||
|
||||
let got = cmd.supports_renderer("whatever");
|
||||
|
||||
assert_eq!(got, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_doesnt_support_not_supported() {
|
||||
let cmd = example();
|
||||
|
||||
let got = cmd.supports_renderer("not-supported");
|
||||
|
||||
assert_eq!(got, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ask_the_preprocessor_to_blow_up() {
|
||||
let dummy_book = DummyBook::new();
|
||||
let temp = dummy_book.build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
md.with_preprocessor(example());
|
||||
|
||||
md.config
|
||||
.set("preprocessor.nop-preprocessor.blow-up", true)
|
||||
.unwrap();
|
||||
|
||||
let got = md.build();
|
||||
|
||||
assert!(got.is_err());
|
||||
let error_message = got.err().unwrap().to_string();
|
||||
let status = if cfg!(windows) {
|
||||
"exit code: 1"
|
||||
} else {
|
||||
"exit status: 1"
|
||||
};
|
||||
assert_eq!(
|
||||
error_message,
|
||||
format!(
|
||||
r#"The "nop-preprocessor" preprocessor exited unsuccessfully with {status} status"#
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_the_dummy_book() {
|
||||
let dummy_book = DummyBook::new();
|
||||
let temp = dummy_book.build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
md.with_preprocessor(example());
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
//! This will create an entire book in a temporary directory using some
|
||||
//! dummy contents from the `tests/dummy-book/` directory.
|
||||
|
||||
// Not all features are used in all test crates, so...
|
||||
#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
|
||||
|
||||
use anyhow::Context;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::MDBook;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// Create a dummy book in a temporary directory, using the contents of
|
||||
/// `SUMMARY_MD` as a guide.
|
||||
///
|
||||
/// The "Nested Chapter" file contains a code block with a single
|
||||
/// `assert!($TEST_STATUS)`. If you want to check MDBook's testing
|
||||
/// functionality, `$TEST_STATUS` can be substitute for either `true` or
|
||||
/// `false`. This is done using the `passing_test` parameter.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DummyBook {
|
||||
passing_test: bool,
|
||||
}
|
||||
|
||||
impl DummyBook {
|
||||
/// Create a new `DummyBook` with all the defaults.
|
||||
pub fn new() -> DummyBook {
|
||||
DummyBook { passing_test: true }
|
||||
}
|
||||
|
||||
/// Whether the doc-test included in the "Nested Chapter" should pass or
|
||||
/// fail (it passes by default).
|
||||
pub fn with_passing_test(&mut self, test_passes: bool) -> &mut DummyBook {
|
||||
self.passing_test = test_passes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Write a book to a temporary directory using the provided settings.
|
||||
pub fn build(&self) -> Result<TempDir> {
|
||||
let temp = TempFileBuilder::new()
|
||||
.prefix("dummy_book-")
|
||||
.tempdir()
|
||||
.with_context(|| "Unable to create temp directory")?;
|
||||
|
||||
let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book");
|
||||
recursive_copy(&dummy_book_root, temp.path()).with_context(|| {
|
||||
"Couldn't copy files into a \
|
||||
temporary directory"
|
||||
})?;
|
||||
|
||||
let sub_pattern = if self.passing_test { "true" } else { "false" };
|
||||
let files_containing_tests = [
|
||||
"src/first/nested.md",
|
||||
"src/first/nested-test.rs",
|
||||
"src/first/nested-test-with-anchors.rs",
|
||||
"src/first/partially-included-test.rs",
|
||||
"src/first/partially-included-test-with-anchors.rs",
|
||||
];
|
||||
for file in &files_containing_tests {
|
||||
let path_containing_tests = temp.path().join(file);
|
||||
replace_pattern_in_file(&path_containing_tests, "$TEST_STATUS", sub_pattern)?;
|
||||
}
|
||||
|
||||
Ok(temp)
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()> {
|
||||
let contents = fs::read_to_string(filename)?;
|
||||
File::create(filename)?.write_all(contents.replace(from, to).as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the contents of the provided file into memory and then iterate through
|
||||
/// the list of strings asserting that the file contains all of them.
|
||||
pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
|
||||
let filename = filename.as_ref();
|
||||
let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
|
||||
|
||||
for s in strings {
|
||||
assert!(
|
||||
content.contains(s),
|
||||
"Searching for {:?} in {}\n\n{}",
|
||||
s,
|
||||
filename.display(),
|
||||
content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_doesnt_contain_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
|
||||
let filename = filename.as_ref();
|
||||
let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
|
||||
|
||||
for s in strings {
|
||||
assert!(
|
||||
!content.contains(s),
|
||||
"Found {:?} in {}\n\n{}",
|
||||
s,
|
||||
filename.display(),
|
||||
content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively copy an entire directory tree to somewhere else (a la `cp -r`).
|
||||
fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()> {
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
|
||||
for entry in WalkDir::new(from) {
|
||||
let entry = entry.with_context(|| "Unable to inspect directory entry")?;
|
||||
|
||||
let original_location = entry.path();
|
||||
let relative = original_location
|
||||
.strip_prefix(from)
|
||||
.expect("`original_location` is inside the `from` directory");
|
||||
let new_location = to.join(relative);
|
||||
|
||||
if original_location.is_file() {
|
||||
if let Some(parent) = new_location.parent() {
|
||||
fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?;
|
||||
}
|
||||
|
||||
fs::copy(original_location, &new_location)
|
||||
.with_context(|| "Unable to copy file contents")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_copy_of_example_book() -> Result<TempDir> {
|
||||
let temp = TempFileBuilder::new().prefix("guide").tempdir()?;
|
||||
|
||||
let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
|
||||
|
||||
recursive_copy(guide, temp.path())?;
|
||||
|
||||
Ok(temp)
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Dummy Book
|
||||
|
||||
This file is just here to cause the index preprocessor to run.
|
||||
|
||||
Does a pretty good job, too.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Includes
|
||||
|
||||
{{#include ../SUMMARY.md::}}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// The next line will cause a `testing` test to fail if the anchor feature is broken in such a way
|
||||
// that the whole file gets mistakenly included.
|
||||
assert!(!$TEST_STATUS);
|
||||
|
||||
// ANCHOR: myanchor
|
||||
// ANCHOR: unendinganchor
|
||||
// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in
|
||||
// such a way that the content between anchors isn't included.
|
||||
// unique-string-for-anchor-test
|
||||
assert!($TEST_STATUS);
|
||||
// ANCHOR_END: myanchor
|
||||
|
|
@ -1 +0,0 @@
|
|||
assert!($TEST_STATUS);
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# Nested Chapter
|
||||
|
||||
This file has some testable code.
|
||||
|
||||
```rust
|
||||
assert!($TEST_STATUS);
|
||||
```
|
||||
|
||||
## Some Section
|
||||
|
||||
```rust
|
||||
{{#include nested-test.rs}}
|
||||
```
|
||||
|
||||
## Anchors include the part of a file between special comments
|
||||
|
||||
```rust
|
||||
{{#include nested-test-with-anchors.rs:myanchor}}
|
||||
```
|
||||
|
||||
## Rustdoc include adds the rest of the file as hidden
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include partially-included-test.rs:5:7}}
|
||||
```
|
||||
|
||||
## Rustdoc include works with anchors too
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}}
|
||||
```
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Introduction
|
||||
|
||||
Here's some interesting text...
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Second Chapter
|
||||
|
||||
This makes sure you can insert runnable Rust files.
|
||||
|
||||
{{#playground example.rs}}
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Root README
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# This dummy book is for testing the conversion of README.md to index.html by IndexPreprocessor
|
||||
|
||||
[Root README](README.md)
|
||||
|
||||
- [1st README](first/README.md)
|
||||
- [2nd README](second/README.md)
|
||||
- [2nd index](second/index.md)
|
||||
|
|
@ -1 +0,0 @@
|
|||
# First README
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Second README
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Second index
|
||||
157
tests/init.rs
157
tests/init.rs
|
|
@ -1,157 +0,0 @@
|
|||
use mdbook::config::Config;
|
||||
use mdbook::MDBook;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
|
||||
/// Run `mdbook init` in an empty directory and make sure the default files
|
||||
/// are created.
|
||||
#[test]
|
||||
fn base_mdbook_init_should_create_default_content() {
|
||||
let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"];
|
||||
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
for file in &created_files {
|
||||
assert!(!temp.path().join(file).exists());
|
||||
}
|
||||
|
||||
MDBook::init(temp.path()).build().unwrap();
|
||||
|
||||
for file in &created_files {
|
||||
let target = temp.path().join(file);
|
||||
println!("{}", target.display());
|
||||
assert!(target.exists(), "{file} doesn't exist");
|
||||
}
|
||||
|
||||
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(
|
||||
contents,
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nsrc = \"src\"\n"
|
||||
);
|
||||
}
|
||||
|
||||
/// Run `mdbook init` in a directory containing a SUMMARY.md should create the
|
||||
/// files listed in the summary.
|
||||
#[test]
|
||||
fn run_mdbook_init_should_create_content_from_summary() {
|
||||
let created_files = vec!["intro.md", "first.md", "outro.md"];
|
||||
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let src_dir = temp.path().join("src");
|
||||
fs::create_dir_all(src_dir.clone()).unwrap();
|
||||
static SUMMARY: &str = r#"# Summary
|
||||
|
||||
[intro](intro.md)
|
||||
|
||||
- [First chapter](first.md)
|
||||
|
||||
[outro](outro.md)
|
||||
|
||||
"#;
|
||||
|
||||
let mut summary = File::create(src_dir.join("SUMMARY.md")).unwrap();
|
||||
summary.write_all(SUMMARY.as_bytes()).unwrap();
|
||||
MDBook::init(temp.path()).build().unwrap();
|
||||
|
||||
for file in &created_files {
|
||||
let target = src_dir.join(file);
|
||||
println!("{}", target.display());
|
||||
assert!(target.exists(), "{file} doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/// Set some custom arguments for where to place the source and destination
|
||||
/// files, then call `mdbook init`.
|
||||
#[test]
|
||||
fn run_mdbook_init_with_custom_book_and_src_locations() {
|
||||
let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"];
|
||||
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
for file in &created_files {
|
||||
assert!(
|
||||
!temp.path().join(file).exists(),
|
||||
"{file} shouldn't exist yet!"
|
||||
);
|
||||
}
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.book.src = PathBuf::from("in");
|
||||
cfg.build.build_dir = PathBuf::from("out");
|
||||
|
||||
MDBook::init(temp.path()).with_config(cfg).build().unwrap();
|
||||
|
||||
for file in &created_files {
|
||||
let target = temp.path().join(file);
|
||||
assert!(
|
||||
target.exists(),
|
||||
"{file} should have been created by `mdbook init`"
|
||||
);
|
||||
}
|
||||
|
||||
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(
|
||||
contents,
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn book_toml_isnt_required() {
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let md = MDBook::init(temp.path()).build().unwrap();
|
||||
|
||||
let _ = fs::remove_file(temp.path().join("book.toml"));
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_theme() {
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
MDBook::init(temp.path()).copy_theme(true).build().unwrap();
|
||||
let expected = vec![
|
||||
"book.js",
|
||||
"css/chrome.css",
|
||||
"css/general.css",
|
||||
"css/print.css",
|
||||
"css/variables.css",
|
||||
"favicon.png",
|
||||
"favicon.svg",
|
||||
"fonts/OPEN-SANS-LICENSE.txt",
|
||||
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
|
||||
"fonts/fonts.css",
|
||||
"fonts/open-sans-v17-all-charsets-300.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-300italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-600.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-600italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-700.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-700italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-800.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-800italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-regular.woff2",
|
||||
"fonts/source-code-pro-v11-all-charsets-500.woff2",
|
||||
"highlight.css",
|
||||
"highlight.js",
|
||||
"index.hbs",
|
||||
];
|
||||
let theme_dir = temp.path().join("theme");
|
||||
let mut actual: Vec<_> = walkdir::WalkDir::new(&theme_dir)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| !e.file_type().is_dir())
|
||||
.map(|e| {
|
||||
e.path()
|
||||
.strip_prefix(&theme_dir)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace('\\', "/")
|
||||
})
|
||||
.collect();
|
||||
actual.sort();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
//! Some integration tests to make sure the `SUMMARY.md` parser can deal with
|
||||
//! some real-life examples.
|
||||
|
||||
use mdbook::book;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
macro_rules! summary_md_test {
|
||||
($name:ident, $filename:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
env_logger::try_init().ok();
|
||||
|
||||
let filename = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("summary_md_files")
|
||||
.join($filename);
|
||||
|
||||
if !filename.exists() {
|
||||
panic!("{} Doesn't exist", filename.display());
|
||||
}
|
||||
|
||||
let mut content = String::new();
|
||||
File::open(&filename)
|
||||
.unwrap()
|
||||
.read_to_string(&mut content)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = book::parse_summary(&content) {
|
||||
eprintln!("Error parsing {}", filename.display());
|
||||
eprintln!();
|
||||
eprintln!("{:?}", e);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
summary_md_test!(rust_by_example, "rust_by_example.md");
|
||||
summary_md_test!(rust_ffi_guide, "rust_ffi_guide.md");
|
||||
summary_md_test!(example_book, "example_book.md");
|
||||
summary_md_test!(the_book_2nd_edition, "the_book-2nd_edition.md");
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,20 +0,0 @@
|
|||
# Summary
|
||||
|
||||
- [mdBook](README.md)
|
||||
- [Command Line Tool](cli/cli-tool.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
- [watch](cli/watch.md)
|
||||
- [serve](cli/serve.md)
|
||||
- [test](cli/test.md)
|
||||
- [Format](format/format.md)
|
||||
- [SUMMARY.md](format/summary.md)
|
||||
- [Configuration](format/config.md)
|
||||
- [Theme](format/theme/theme.md)
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [Rust code specific features](format/rust.md)
|
||||
- [Rust Library](lib/lib.md)
|
||||
-----------
|
||||
[Contributors](misc/contributors.md)
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
# Summary
|
||||
|
||||
[Introduction](index.md)
|
||||
|
||||
- [Hello World](hello.md)
|
||||
- [Comments](hello/comment.md)
|
||||
- [Formatted print](hello/print.md)
|
||||
- [Debug](hello/print/print_debug.md)
|
||||
- [Display](hello/print/print_display.md)
|
||||
- [Testcase: List](hello/print/print_display/testcase_list.md)
|
||||
- [Formatting](hello/print/fmt.md)
|
||||
|
||||
- [Primitives](primitives.md)
|
||||
- [Literals and operators](primitives/literals.md)
|
||||
- [Tuples](primitives/tuples.md)
|
||||
- [Arrays and Slices](primitives/array.md)
|
||||
|
||||
- [Custom Types](custom_types.md)
|
||||
- [Structures](custom_types/structs.md)
|
||||
- [Enums](custom_types/enum.md)
|
||||
- [use](custom_types/enum/enum_use.md)
|
||||
- [C-like](custom_types/enum/c_like.md)
|
||||
- [Testcase: linked-list](custom_types/enum/testcase_linked_list.md)
|
||||
- [constants](custom_types/constants.md)
|
||||
|
||||
- [Variable Bindings](variable_bindings.md)
|
||||
- [Mutability](variable_bindings/mut.md)
|
||||
- [Scope and Shadowing](variable_bindings/scope.md)
|
||||
- [Declare first](variable_bindings/declare.md)
|
||||
|
||||
- [Types](types.md)
|
||||
- [Casting](types/cast.md)
|
||||
- [Literals](types/literals.md)
|
||||
- [Inference](types/inference.md)
|
||||
- [Aliasing](types/alias.md)
|
||||
|
||||
- [Conversion](conversion.md)
|
||||
- [From and Into](conversion/from_into.md)
|
||||
- [To and From String](conversion/string.md)
|
||||
|
||||
- [Expressions](expression.md)
|
||||
|
||||
- [Flow Control](flow_control.md)
|
||||
- [if/else](flow_control/if_else.md)
|
||||
- [loop](flow_control/loop.md)
|
||||
- [Nesting and labels](flow_control/loop/nested.md)
|
||||
- [Returning from loops](flow_control/loop/return.md)
|
||||
- [while](flow_control/while.md)
|
||||
- [for and range](flow_control/for.md)
|
||||
- [match](flow_control/match.md)
|
||||
- [Destructuring](flow_control/match/destructuring.md)
|
||||
- [tuples](flow_control/match/destructuring/destructure_tuple.md)
|
||||
- [enums](flow_control/match/destructuring/destructure_enum.md)
|
||||
- [pointers/ref](flow_control/match/destructuring/destructure_pointers.md)
|
||||
- [structs](flow_control/match/destructuring/destructure_structures.md)
|
||||
- [Guards](flow_control/match/guard.md)
|
||||
- [Binding](flow_control/match/binding.md)
|
||||
- [if let](flow_control/if_let.md)
|
||||
- [while let](flow_control/while_let.md)
|
||||
|
||||
- [Functions](fn.md)
|
||||
- [Methods](fn/methods.md)
|
||||
- [Closures](fn/closures.md)
|
||||
- [Capturing](fn/closures/capture.md)
|
||||
- [As input parameters](fn/closures/input_parameters.md)
|
||||
- [Type anonymity](fn/closures/anonymity.md)
|
||||
- [Input functions](fn/closures/input_functions.md)
|
||||
- [As output parameters](fn/closures/output_parameters.md)
|
||||
- [Examples in `std`](fn/closures/closure_examples.md)
|
||||
- [Iterator::any](fn/closures/closure_examples/iter_any.md)
|
||||
- [Iterator::find](fn/closures/closure_examples/iter_find.md)
|
||||
- [Higher Order Functions](fn/hof.md)
|
||||
|
||||
- [Modules](mod.md)
|
||||
- [Visibility](mod/visibility.md)
|
||||
- [Struct visibility](mod/struct_visibility.md)
|
||||
- [The `use` declaration](mod/use.md)
|
||||
- [`super` and `self`](mod/super.md)
|
||||
- [File hierarchy](mod/split.md)
|
||||
|
||||
- [Crates](crates.md)
|
||||
- [Library](crates/lib.md)
|
||||
- [`extern crate`](crates/link.md)
|
||||
|
||||
- [Attributes](attribute.md)
|
||||
- [`dead_code`](attribute/unused.md)
|
||||
- [Crates](attribute/crate.md)
|
||||
- [`cfg`](attribute/cfg.md)
|
||||
- [Custom](attribute/cfg/custom.md)
|
||||
|
||||
- [Generics](generics.md)
|
||||
- [Functions](generics/gen_fn.md)
|
||||
- [Implementation](generics/impl.md)
|
||||
- [Traits](generics/gen_trait.md)
|
||||
- [Bounds](generics/bounds.md)
|
||||
- [Testcase: empty bounds](generics/bounds/testcase_empty.md)
|
||||
- [Multiple bounds](generics/multi_bounds.md)
|
||||
- [Where clauses](generics/where.md)
|
||||
- [New Type Idiom](generics/new_types.md)
|
||||
- [Associated items](generics/assoc_items.md)
|
||||
- [The Problem](generics/assoc_items/the_problem.md)
|
||||
- [Associated types](generics/assoc_items/types.md)
|
||||
- [Phantom type parameters](generics/phantom.md)
|
||||
- [Testcase: unit clarification](generics/phantom/testcase_units.md)
|
||||
|
||||
- [Scoping rules](scope.md)
|
||||
- [RAII](scope/raii.md)
|
||||
- [Ownership and moves](scope/move.md)
|
||||
- [Mutability](scope/move/mut.md)
|
||||
- [Borrowing](scope/borrow.md)
|
||||
- [Mutability](scope/borrow/mut.md)
|
||||
- [Freezing](scope/borrow/freeze.md)
|
||||
- [Aliasing](scope/borrow/alias.md)
|
||||
- [The ref pattern](scope/borrow/ref.md)
|
||||
- [Lifetimes](scope/lifetime.md)
|
||||
- [Explicit annotation](scope/lifetime/explicit.md)
|
||||
- [Functions](scope/lifetime/fn.md)
|
||||
- [Methods](scope/lifetime/methods.md)
|
||||
- [Structs](scope/lifetime/struct.md)
|
||||
- [Bounds](scope/lifetime/lifetime_bounds.md)
|
||||
- [Coercion](scope/lifetime/lifetime_coercion.md)
|
||||
- [static](scope/lifetime/static_lifetime.md)
|
||||
- [elision](scope/lifetime/elision.md)
|
||||
|
||||
- [Traits](trait.md)
|
||||
- [Derive](trait/derive.md)
|
||||
- [Operator Overloading](trait/ops.md)
|
||||
- [Drop](trait/drop.md)
|
||||
- [Iterators](trait/iter.md)
|
||||
- [Clone](trait/clone.md)
|
||||
|
||||
- [macro_rules!](macros.md)
|
||||
- [Syntax](macro/syntax.md)
|
||||
- [Designators](macros/designators.md)
|
||||
- [Overload](macros/overload.md)
|
||||
- [Repeat](macros/repeat.md)
|
||||
- [DRY (Don't Repeat Yourself)](macros/dry.md)
|
||||
- [DSL (Domain Specific Languages)](macros/dsl.md)
|
||||
- [Variadics](macros/variadics.md)
|
||||
|
||||
- [Error handling](error.md)
|
||||
- [`panic`](error/panic.md)
|
||||
- [`Option` & `unwrap`](error/option_unwrap.md)
|
||||
- [Combinators: `map`](error/option_unwrap/map.md)
|
||||
- [Combinators: `and_then`](error/option_unwrap/and_then.md)
|
||||
- [`Result`](error/result.md)
|
||||
- [`map` for `Result`](error/result/result_map.md)
|
||||
- [aliases for `Result`](error/result/result_alias.md)
|
||||
- [Early returns](error/result/early_returns.md)
|
||||
- [Introducing `?`](error/result/enter_question_mark.md)
|
||||
- [Multiple error types](error/multiple_error_types.md)
|
||||
- [Pulling `Result`s out of `Option`s](error/multiple_error_types/option_result.md)
|
||||
- [Defining an error type](error/multiple_error_types/define_error_type.md)
|
||||
- [`Box`ing errors](error/multiple_error_types/boxing_errors.md)
|
||||
- [Other uses of `?`](error/multiple_error_types/reenter_question_mark.md)
|
||||
- [Wrapping errors](error/multiple_error_types/wrap_error.md)
|
||||
- [Iterating over `Result`s](error/iter_result.md)
|
||||
|
||||
- [Std library types](std.md)
|
||||
- [Box, stack and heap](std/box.md)
|
||||
- [Vectors](std/vec.md)
|
||||
- [Strings](std/str.md)
|
||||
- [`Option`](std/option.md)
|
||||
- [`Result`](std/result.md)
|
||||
- [`?`](std/result/question_mark.md)
|
||||
- [`panic!`](std/panic.md)
|
||||
- [HashMap](std/hash.md)
|
||||
- [Alternate/custom key types](std/hash/alt_key_types.md)
|
||||
- [HashSet](std/hash/hashset.md)
|
||||
|
||||
- [Std misc](std_misc.md)
|
||||
- [Threads](std_misc/threads.md)
|
||||
- [Testcase: map-reduce](std_misc/threads/testcase_mapreduce.md)
|
||||
- [Channels](std_misc/channels.md)
|
||||
- [Path](std_misc/path.md)
|
||||
- [File I/O](std_misc/file.md)
|
||||
- [`open`](std_misc/file/open.md)
|
||||
- [`create`](std_misc/file/create.md)
|
||||
- [Child processes](std_misc/process.md)
|
||||
- [Pipes](std_misc/process/pipe.md)
|
||||
- [Wait](std_misc/process/wait.md)
|
||||
- [Filesystem Operations](std_misc/fs.md)
|
||||
- [Program arguments](std_misc/arg.md)
|
||||
- [Argument parsing](std_misc/arg/matching.md)
|
||||
- [Foreign Function Interface](std_misc/ffi.md)
|
||||
|
||||
- [Meta](meta.md)
|
||||
- [Documentation](meta/doc.md)
|
||||
- [Testing](meta/test.md)
|
||||
|
||||
- [Unsafe Operations](unsafe.md)
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Summary
|
||||
|
||||
- [Overview](./overview.md)
|
||||
- [Setting Up](./setting_up.md)
|
||||
- [Core Client Library](./client.md)
|
||||
- [Constructing a Basic Request](./basic_request.md)
|
||||
- [Sending the Request](./send_basic.md)
|
||||
- [Generating a Header File](./cbindgen.md)
|
||||
- [Better Error Handling](./error_handling.md)
|
||||
- [Asynchronous Operations](./async.md)
|
||||
- [More Complex Requests](./complex_request.md)
|
||||
- [Testing](./testing.md)
|
||||
- [Dynamic Loading & Plugins](./dynamic_loading.md)
|
||||
|
||||
---
|
||||
|
||||
- [Break All The Things!!1!](./fun/index.md)
|
||||
- [Problems](./fun/problems.md)
|
||||
- [Solutions](./fun/solutions.md)
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
# The Rust Programming Language
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Introduction](ch01-00-introduction.md)
|
||||
- [Installation](ch01-01-installation.md)
|
||||
- [Hello, World!](ch01-02-hello-world.md)
|
||||
|
||||
- [Guessing Game Tutorial](ch02-00-guessing-game-tutorial.md)
|
||||
|
||||
- [Common Programming Concepts](ch03-00-common-programming-concepts.md)
|
||||
- [Variables and Mutability](ch03-01-variables-and-mutability.md)
|
||||
- [Data Types](ch03-02-data-types.md)
|
||||
- [How Functions Work](ch03-03-how-functions-work.md)
|
||||
- [Comments](ch03-04-comments.md)
|
||||
- [Control Flow](ch03-05-control-flow.md)
|
||||
|
||||
- [Understanding Ownership](ch04-00-understanding-ownership.md)
|
||||
- [What is Ownership?](ch04-01-what-is-ownership.md)
|
||||
- [References & Borrowing](ch04-02-references-and-borrowing.md)
|
||||
- [Slices](ch04-03-slices.md)
|
||||
|
||||
- [Using Structs to Structure Related Data](ch05-00-structs.md)
|
||||
- [Defining and Instantiating Structs](ch05-01-defining-structs.md)
|
||||
- [An Example Program Using Structs](ch05-02-example-structs.md)
|
||||
- [Method Syntax](ch05-03-method-syntax.md)
|
||||
|
||||
- [Enums and Pattern Matching](ch06-00-enums.md)
|
||||
- [Defining an Enum](ch06-01-defining-an-enum.md)
|
||||
- [The `match` Control Flow Operator](ch06-02-match.md)
|
||||
- [Concise Control Flow with `if let`](ch06-03-if-let.md)
|
||||
|
||||
## Basic Rust Literacy
|
||||
|
||||
- [Modules](ch07-00-modules.md)
|
||||
- [`mod` and the Filesystem](ch07-01-mod-and-the-filesystem.md)
|
||||
- [Controlling Visibility with `pub`](ch07-02-controlling-visibility-with-pub.md)
|
||||
- [Referring to Names in Different Modules](ch07-03-importing-names-with-use.md)
|
||||
|
||||
- [Common Collections](ch08-00-common-collections.md)
|
||||
- [Vectors](ch08-01-vectors.md)
|
||||
- [Strings](ch08-02-strings.md)
|
||||
- [Hash Maps](ch08-03-hash-maps.md)
|
||||
|
||||
- [Error Handling](ch09-00-error-handling.md)
|
||||
- [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md)
|
||||
- [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md)
|
||||
- [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md)
|
||||
|
||||
- [Generic Types, Traits, and Lifetimes](ch10-00-generics.md)
|
||||
- [Generic Data Types](ch10-01-syntax.md)
|
||||
- [Traits: Defining Shared Behavior](ch10-02-traits.md)
|
||||
- [Validating References with Lifetimes](ch10-03-lifetime-syntax.md)
|
||||
|
||||
- [Testing](ch11-00-testing.md)
|
||||
- [Writing tests](ch11-01-writing-tests.md)
|
||||
- [Running tests](ch11-02-running-tests.md)
|
||||
- [Test Organization](ch11-03-test-organization.md)
|
||||
|
||||
- [An I/O Project: Building a Command Line Program](ch12-00-an-io-project.md)
|
||||
- [Accepting Command Line Arguments](ch12-01-accepting-command-line-arguments.md)
|
||||
- [Reading a File](ch12-02-reading-a-file.md)
|
||||
- [Refactoring to Improve Modularity and Error Handling](ch12-03-improving-error-handling-and-modularity.md)
|
||||
- [Developing the Library’s Functionality with Test Driven Development](ch12-04-testing-the-librarys-functionality.md)
|
||||
- [Working with Environment Variables](ch12-05-working-with-environment-variables.md)
|
||||
- [Writing Error Messages to Standard Error Instead of Standard Output](ch12-06-writing-to-stderr-instead-of-stdout.md)
|
||||
|
||||
## Thinking in Rust
|
||||
|
||||
- [Functional Language Features: Iterators and Closures](ch13-00-functional-features.md)
|
||||
- [Closures: Anonymous Functions that Can Capture Their Environment](ch13-01-closures.md)
|
||||
- [Processing a Series of Items with Iterators](ch13-02-iterators.md)
|
||||
- [Improving Our I/O Project](ch13-03-improving-our-io-project.md)
|
||||
- [Comparing Performance: Loops vs. Iterators](ch13-04-performance.md)
|
||||
|
||||
- [More about Cargo and Crates.io](ch14-00-more-about-cargo.md)
|
||||
- [Customizing Builds with Release Profiles](ch14-01-release-profiles.md)
|
||||
- [Publishing a Crate to Crates.io](ch14-02-publishing-to-crates-io.md)
|
||||
- [Cargo Workspaces](ch14-03-cargo-workspaces.md)
|
||||
- [Installing Binaries from Crates.io with `cargo install`](ch14-04-installing-binaries.md)
|
||||
- [Extending Cargo with Custom Commands](ch14-05-extending-cargo.md)
|
||||
|
||||
- [Smart Pointers](ch15-00-smart-pointers.md)
|
||||
- [`Box<T>` Points to Data on the Heap and Has a Known Size](ch15-01-box.md)
|
||||
- [The `Deref` Trait Allows Access to the Data Through a Reference](ch15-02-deref.md)
|
||||
- [The `Drop` Trait Runs Code on Cleanup](ch15-03-drop.md)
|
||||
- [`Rc<T>`, the Reference Counted Smart Pointer](ch15-04-rc.md)
|
||||
- [`RefCell<T>` and the Interior Mutability Pattern](ch15-05-interior-mutability.md)
|
||||
- [Creating Reference Cycles and Leaking Memory is Safe](ch15-06-reference-cycles.md)
|
||||
|
||||
- [Fearless Concurrency](ch16-00-concurrency.md)
|
||||
- [Threads](ch16-01-threads.md)
|
||||
- [Message Passing](ch16-02-message-passing.md)
|
||||
- [Shared State](ch16-03-shared-state.md)
|
||||
- [Extensible Concurrency: `Sync` and `Send`](ch16-04-extensible-concurrency-sync-and-send.md)
|
||||
|
||||
- [Is Rust an Object-Oriented Programming Language?](ch17-00-oop.md)
|
||||
- [What Does Object-Oriented Mean?](ch17-01-what-is-oo.md)
|
||||
- [Trait Objects for Using Values of Different Types](ch17-02-trait-objects.md)
|
||||
- [Object-Oriented Design Pattern Implementations](ch17-03-oo-design-patterns.md)
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
- [Patterns Match the Structure of Values](ch18-00-patterns.md)
|
||||
- [All the Places Patterns May be Used](ch18-01-all-the-places-for-patterns.md)
|
||||
- [Refutability: Whether a Pattern Might Fail to Match](ch18-02-refutability.md)
|
||||
- [All the Pattern Syntax](ch18-03-pattern-syntax.md)
|
||||
|
||||
- [Advanced Features](ch19-00-advanced-features.md)
|
||||
- [Unsafe Rust](ch19-01-unsafe-rust.md)
|
||||
- [Advanced Lifetimes](ch19-02-advanced-lifetimes.md)
|
||||
- [Advanced Traits](ch19-03-advanced-traits.md)
|
||||
- [Advanced Types](ch19-04-advanced-types.md)
|
||||
- [Advanced Functions & Closures](ch19-05-advanced-functions-and-closures.md)
|
||||
|
||||
- [Final Project: Building a Multithreaded Web Server](ch20-00-final-project-a-web-server.md)
|
||||
- [A Single Threaded Web Server](ch20-01-single-threaded.md)
|
||||
- [How Slow Requests Affect Throughput](ch20-02-slow-requests.md)
|
||||
- [Designing the Thread Pool Interface](ch20-03-designing-the-interface.md)
|
||||
- [Creating the Thread Pool and Storing Threads](ch20-04-storing-threads.md)
|
||||
- [Sending Requests to Threads Via Channels](ch20-05-sending-requests-via-channels.md)
|
||||
- [Graceful Shutdown and Cleanup](ch20-06-graceful-shutdown-and-cleanup.md)
|
||||
|
||||
- [Appendix](appendix-00.md)
|
||||
- [A - Keywords](appendix-01-keywords.md)
|
||||
- [B - Operators and Symbols](appendix-02-operators.md)
|
||||
- [C - Derivable Traits](appendix-03-derivable-traits.md)
|
||||
- [D - Macros](appendix-04-macros.md)
|
||||
- [E - Translations](appendix-05-translation.md)
|
||||
- [F - Newest Features](appendix-06-newest-features.md)
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
mod dummy_book;
|
||||
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use mdbook::MDBook;
|
||||
|
||||
#[test]
|
||||
fn mdbook_can_correctly_test_a_passing_book() {
|
||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
|
||||
let result = md.test(vec![]);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Tests failed with {}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_detects_book_with_failing_tests() {
|
||||
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
|
||||
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());
|
||||
}
|
||||
43
tests/testsuite/README.md
Normal file
43
tests/testsuite/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Testsuite
|
||||
|
||||
## Introduction
|
||||
|
||||
This is the main testsuite for exercising all functionality of mdBook.
|
||||
|
||||
Tests should be organized into modules based around major features. Tests should use `BookTest` to drive the test. `BookTest` will set up a temp directory, and provides a variety of methods to help create a build books.
|
||||
|
||||
## Basic structure of a test
|
||||
|
||||
Using `BookTest`, you typically use it to copy a directory into a temp directory, and then run mdbook commands in that temp directory. You can run the `mdbook` executable, or use the mdbook API to perform whatever tasks you need. Running the executable has the benefit of being able to validate the console output.
|
||||
|
||||
See `build::basic_build` for a simple test example. I recommend reviewing the methods on `BookTest` to learn more, and reviewing some of the existing tests to get a feel for how they are structured.
|
||||
|
||||
For example, let's say you are creating a new theme test. In the `testsuite/theme` directory, create a new directory with the book source that you want to exercise. At a minimum, this needs a `src/SUMMARY.md`, but often you'll also want `book.toml`. Then, in `testsuite/theme.rs`, add a test with `BookTest::from_dir("theme/mytest")`, and then use the methods to perform whatever actions you want.
|
||||
|
||||
`BookTest` is designed to be able to chain a series of actions. For example, you can do something like:
|
||||
|
||||
```rust
|
||||
BookTest::from_dir("theme/mytest")
|
||||
.build()
|
||||
.check_main_file("book/index.html", str![["file contents"]])
|
||||
.change_file("src/index.md", "new contents")
|
||||
.build()
|
||||
.check_main_file("book/index.html", str![["new contents"]]);
|
||||
```
|
||||
|
||||
## Snapbox
|
||||
|
||||
The testsuite uses [`snapbox`] to drive most of the tests. This library provides the ability to compare strings using a variety of methods. These strings are written in the source code using either the [`str!`] or [`file!`] macros.
|
||||
|
||||
The magic is that you can set the `SNAPSHOTS=overwrite` environment variable, and snapbox will automatically update the strings contents of `str!`, or the file contents of `file!`. This makes it easier to update tests. Snapbox provides nice diffing output, and quite a few other features.
|
||||
|
||||
Expected contents can have wildcards like `...` (matches any lines) or `[..]` (matches any characters on a line). See [snapbox filters] for more info and other filters.
|
||||
|
||||
Typically when writing a test, I'll just start with an empty `str!` or `file!`, and let snapbox fill it in. Then I review the contents to make sure they are what I expect.
|
||||
|
||||
Note that there is some normalization applied to the strings. See `book_test::assert` for how some of these normalizations happen.
|
||||
|
||||
[`snapbox`]: https://docs.rs/snapbox/latest/snapbox/
|
||||
[`str!`]: https://docs.rs/snapbox/latest/snapbox/macro.str.html
|
||||
[`file!`]: https://docs.rs/snapbox/latest/snapbox/macro.file.html
|
||||
[snapbox filters]: https://docs.rs/snapbox/latest/snapbox/assert/struct.Assert.html#method.eq
|
||||
442
tests/testsuite/book_test.rs
Normal file
442
tests/testsuite/book_test.rs
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
//! Utility for building and running tests against mdbook.
|
||||
|
||||
use mdbook::book::BookBuilder;
|
||||
use mdbook::MDBook;
|
||||
use snapbox::IntoData;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// Test number used for generating unique temp directory names.
|
||||
static NEXT_TEST_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum StatusCode {
|
||||
Success,
|
||||
Failure,
|
||||
Code(i32),
|
||||
}
|
||||
|
||||
/// Main helper for driving mdbook tests.
|
||||
pub struct BookTest {
|
||||
/// The temp directory where the test should perform its work.
|
||||
pub dir: PathBuf,
|
||||
assert: snapbox::Assert,
|
||||
/// This indicates whether or not the book has been built.
|
||||
built: bool,
|
||||
}
|
||||
|
||||
impl BookTest {
|
||||
/// Creates a new test, copying the contents from the given directory into
|
||||
/// a temp directory.
|
||||
pub fn from_dir(dir: &str) -> BookTest {
|
||||
// Copy this test book to a temp directory.
|
||||
let dir = Path::new("tests/testsuite").join(dir);
|
||||
assert!(dir.exists(), "{dir:?} should exist");
|
||||
let tmp = Self::new_tmp();
|
||||
mdbook::utils::fs::copy_files_except_ext(
|
||||
&dir,
|
||||
&tmp,
|
||||
true,
|
||||
Some(&PathBuf::from("book")),
|
||||
&[],
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("failed to copy test book {dir:?} to {tmp:?}: {e:?}"));
|
||||
Self::new(tmp)
|
||||
}
|
||||
|
||||
/// Creates a new test with an empty temp directory.
|
||||
pub fn empty() -> BookTest {
|
||||
Self::new(Self::new_tmp())
|
||||
}
|
||||
|
||||
/// Creates a new test with the given function to initialize a new book.
|
||||
///
|
||||
/// The book itself is not built.
|
||||
pub fn init(f: impl Fn(&mut BookBuilder)) -> BookTest {
|
||||
let tmp = Self::new_tmp();
|
||||
let mut bb = MDBook::init(&tmp);
|
||||
f(&mut bb);
|
||||
bb.build()
|
||||
.unwrap_or_else(|e| panic!("failed to initialize book at {tmp:?}: {e:?}"));
|
||||
Self::new(tmp)
|
||||
}
|
||||
|
||||
fn new_tmp() -> PathBuf {
|
||||
let id = NEXT_TEST_ID.fetch_add(1, Ordering::SeqCst);
|
||||
let tmp = Path::new(env!("CARGO_TARGET_TMPDIR"))
|
||||
.join("ts")
|
||||
.join(format!("t{id}"));
|
||||
if tmp.exists() {
|
||||
std::fs::remove_dir_all(&tmp)
|
||||
.unwrap_or_else(|e| panic!("failed to remove {tmp:?}: {e:?}"));
|
||||
}
|
||||
std::fs::create_dir_all(&tmp).unwrap_or_else(|e| panic!("failed to create {tmp:?}: {e:?}"));
|
||||
tmp
|
||||
}
|
||||
|
||||
fn new(dir: PathBuf) -> BookTest {
|
||||
let assert = assert(&dir);
|
||||
BookTest {
|
||||
dir,
|
||||
assert,
|
||||
built: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the contents of an HTML file that it has the given contents
|
||||
/// between the `<main>` tag.
|
||||
///
|
||||
/// Normally the contents outside of the `<main>` tag aren't interesting,
|
||||
/// and they add a significant amount of noise.
|
||||
pub fn check_main_file(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
|
||||
if !self.built {
|
||||
self.build();
|
||||
}
|
||||
let full_path = self.dir.join(path);
|
||||
let actual = read_to_string(&full_path);
|
||||
let start = actual
|
||||
.find("<main>")
|
||||
.unwrap_or_else(|| panic!("didn't find <main> in:\n{actual}"));
|
||||
let end = actual.find("</main>").unwrap();
|
||||
let contents = actual[start + 6..end - 7].trim();
|
||||
self.assert.eq(contents, expected);
|
||||
self
|
||||
}
|
||||
|
||||
/// Checks the summary contents of `toc.js` against the expected value.
|
||||
pub fn check_toc_js(&mut self, expected: impl IntoData) -> &mut Self {
|
||||
if !self.built {
|
||||
self.build();
|
||||
}
|
||||
let inner = self.toc_js_html();
|
||||
// Would be nice if this were prettified, but a primitive wrapping will do for now.
|
||||
let inner = inner.replace("><", ">\n<");
|
||||
self.assert.eq(inner, expected);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the summary contents from `toc.js`.
|
||||
pub fn toc_js_html(&self) -> String {
|
||||
let full_path = self.dir.join("book/toc.js");
|
||||
let actual = read_to_string(&full_path);
|
||||
let inner = actual
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let line = line.trim().strip_prefix("this.innerHTML = '")?;
|
||||
let line = line.strip_suffix("';")?;
|
||||
Some(line)
|
||||
})
|
||||
.next()
|
||||
.expect("should have innerHTML");
|
||||
inner.to_string()
|
||||
}
|
||||
|
||||
/// Checks that the contents of the given file matches the expected value.
|
||||
pub fn check_file(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
|
||||
if !self.built {
|
||||
self.build();
|
||||
}
|
||||
let path = self.dir.join(path);
|
||||
let actual = read_to_string(&path);
|
||||
self.assert.eq(actual, expected);
|
||||
self
|
||||
}
|
||||
|
||||
/// Checks that the given file contains the given string somewhere.
|
||||
pub fn check_file_contains(&mut self, path: &str, expected: &str) -> &mut Self {
|
||||
if !self.built {
|
||||
self.build();
|
||||
}
|
||||
let path = self.dir.join(path);
|
||||
let actual = read_to_string(&path);
|
||||
assert!(
|
||||
actual.contains(expected),
|
||||
"Did not find {expected:?} in {path:?}\n\n{actual}",
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Checks that the given file does not contain the given string anywhere.
|
||||
///
|
||||
/// Beware that using this is fragile, as it may be unable to catch
|
||||
/// regressions (it can't tell the difference between success, or the
|
||||
/// string being looked for changed).
|
||||
pub fn check_file_doesnt_contain(&mut self, path: &str, string: &str) -> &mut Self {
|
||||
if !self.built {
|
||||
self.build();
|
||||
}
|
||||
let path = self.dir.join(path);
|
||||
let actual = read_to_string(&path);
|
||||
assert!(
|
||||
!actual.contains(string),
|
||||
"Unexpectedly found {string:?} in {path:?}\n\n{actual}",
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Checks that the list of files at the given path matches the given value.
|
||||
pub fn check_file_list(&mut self, path: &str, expected: impl IntoData) -> &mut Self {
|
||||
let mut all_paths: Vec<_> = walkdir::WalkDir::new(&self.dir.join(path))
|
||||
.into_iter()
|
||||
// Skip the outer directory.
|
||||
.skip(1)
|
||||
.map(|e| {
|
||||
e.unwrap()
|
||||
.into_path()
|
||||
.strip_prefix(&self.dir)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace('\\', "/")
|
||||
})
|
||||
.collect();
|
||||
all_paths.sort();
|
||||
let actual = all_paths.join("\n");
|
||||
self.assert.eq(actual, expected);
|
||||
self
|
||||
}
|
||||
|
||||
/// Loads an [`MDBook`] from the temp directory.
|
||||
pub fn load_book(&self) -> MDBook {
|
||||
MDBook::load(&self.dir).unwrap_or_else(|e| panic!("book failed to load: {e:?}"))
|
||||
}
|
||||
|
||||
/// Builds the book in the temp directory.
|
||||
pub fn build(&mut self) -> &mut Self {
|
||||
let book = self.load_book();
|
||||
book.build()
|
||||
.unwrap_or_else(|e| panic!("book failed to build: {e:?}"));
|
||||
self.built = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs the `mdbook` binary in the temp directory.
|
||||
///
|
||||
/// This runs `mdbook` with the given args. The args are split on spaces
|
||||
/// (if you need args with spaces, use the `args` method). The given
|
||||
/// callback receives a [`BookCommand`] for you to customize how the
|
||||
/// executable is run.
|
||||
pub fn run(&mut self, args: &str, f: impl Fn(&mut BookCommand)) -> &mut Self {
|
||||
let mut cmd = BookCommand {
|
||||
assert: self.assert.clone(),
|
||||
dir: self.dir.clone(),
|
||||
args: split_args(args),
|
||||
env: BTreeMap::new(),
|
||||
expect_status: StatusCode::Success,
|
||||
expect_stderr_data: None,
|
||||
expect_stdout_data: None,
|
||||
};
|
||||
f(&mut cmd);
|
||||
cmd.run();
|
||||
self
|
||||
}
|
||||
|
||||
/// Change a file's contents in the given path.
|
||||
pub fn change_file(&mut self, path: impl AsRef<Path>, body: &str) -> &mut Self {
|
||||
let path = self.dir.join(path);
|
||||
std::fs::write(&path, body).unwrap_or_else(|e| panic!("failed to write {path:?}: {e:?}"));
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a Rust program with the given src.
|
||||
///
|
||||
/// The given path should be the path where to output the executable in
|
||||
/// the temp directory.
|
||||
pub fn rust_program(&mut self, path: &str, src: &str) -> &mut Self {
|
||||
let rs = self.dir.join(path).with_extension("rs");
|
||||
let parent = rs.parent().unwrap();
|
||||
if !parent.exists() {
|
||||
std::fs::create_dir_all(&parent).unwrap();
|
||||
}
|
||||
std::fs::write(&rs, src).unwrap_or_else(|e| panic!("failed to write {rs:?}: {e:?}"));
|
||||
let status = std::process::Command::new("rustc")
|
||||
.arg(&rs)
|
||||
.current_dir(&parent)
|
||||
.status()
|
||||
.expect("rustc should run");
|
||||
assert!(status.success());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for preparing to run the `mdbook` executable.
|
||||
///
|
||||
/// By default, it expects the process to succeed.
|
||||
pub struct BookCommand {
|
||||
pub dir: PathBuf,
|
||||
assert: snapbox::Assert,
|
||||
args: Vec<String>,
|
||||
env: BTreeMap<String, Option<String>>,
|
||||
expect_status: StatusCode,
|
||||
expect_stderr_data: Option<snapbox::Data>,
|
||||
expect_stdout_data: Option<snapbox::Data>,
|
||||
}
|
||||
|
||||
impl BookCommand {
|
||||
/// Indicates that the process should fail.
|
||||
pub fn expect_failure(&mut self) -> &mut Self {
|
||||
self.expect_status = StatusCode::Failure;
|
||||
self
|
||||
}
|
||||
|
||||
/// Indicates the process should fail with the given exit code.
|
||||
pub fn expect_code(&mut self, code: i32) -> &mut Self {
|
||||
self.expect_status = StatusCode::Code(code);
|
||||
self
|
||||
}
|
||||
|
||||
/// Verifies that stderr matches the given value.
|
||||
pub fn expect_stderr(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
|
||||
self.expect_stderr_data = Some(expected.into_data());
|
||||
self
|
||||
}
|
||||
|
||||
/// Verifies that stdout matches the given value.
|
||||
pub fn expect_stdout(&mut self, expected: impl snapbox::IntoData) -> &mut Self {
|
||||
self.expect_stdout_data = Some(expected.into_data());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds arguments to the command to run.
|
||||
pub fn args(&mut self, args: &[&str]) -> &mut Self {
|
||||
self.args.extend(args.into_iter().map(|t| t.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifies an environment variable to set on the executable.
|
||||
pub fn env<T: Into<String>>(&mut self, key: &str, value: T) -> &mut Self {
|
||||
self.env.insert(key.to_string(), Some(value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs the command, and verifies the output.
|
||||
fn run(&mut self) {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mdbook"));
|
||||
cmd.current_dir(&self.dir)
|
||||
.args(&self.args)
|
||||
.env_remove("RUST_LOG")
|
||||
// Don't read the system git config which is out of our control.
|
||||
.env("GIT_CONFIG_NOSYSTEM", "1")
|
||||
.env("GIT_CONFIG_GLOBAL", &self.dir)
|
||||
.env("GIT_CONFIG_SYSTEM", &self.dir)
|
||||
.env_remove("GIT_AUTHOR_EMAIL")
|
||||
.env_remove("GIT_AUTHOR_NAME")
|
||||
.env_remove("GIT_COMMITTER_EMAIL")
|
||||
.env_remove("GIT_COMMITTER_NAME");
|
||||
|
||||
for (k, v) in &self.env {
|
||||
match v {
|
||||
Some(v) => cmd.env(k, v),
|
||||
None => cmd.env_remove(k),
|
||||
};
|
||||
}
|
||||
|
||||
let output = cmd.output().expect("mdbook should be runnable");
|
||||
let stdout = std::str::from_utf8(&output.stdout).expect("stdout is not utf8");
|
||||
let stderr = std::str::from_utf8(&output.stderr).expect("stderr is not utf8");
|
||||
let render_output = || format!("\n--- stdout\n{stdout}\n--- stderr\n{stderr}");
|
||||
match (self.expect_status, output.status.success()) {
|
||||
(StatusCode::Success, false) => {
|
||||
panic!("mdbook failed, but expected success{}", render_output())
|
||||
}
|
||||
(StatusCode::Failure, true) => {
|
||||
panic!("mdbook succeeded, but expected failure{}", render_output())
|
||||
}
|
||||
(StatusCode::Code(expected), _) => match output.status.code() {
|
||||
Some(actual) => assert_eq!(
|
||||
actual, expected,
|
||||
"process exit code did not match as expected"
|
||||
),
|
||||
None => panic!("process exited via signal {:?}", output.status),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
self.expect_status = StatusCode::Success; // Reset to default.
|
||||
if let Some(expect_stderr_data) = &self.expect_stderr_data {
|
||||
if let Err(e) = self.assert.try_eq(
|
||||
Some(&"stderr"),
|
||||
stderr.into_data(),
|
||||
expect_stderr_data.clone(),
|
||||
) {
|
||||
panic!("{e}");
|
||||
}
|
||||
}
|
||||
if let Some(expect_stdout_data) = &self.expect_stdout_data {
|
||||
if let Err(e) = self.assert.try_eq(
|
||||
Some(&"stdout"),
|
||||
stdout.into_data(),
|
||||
expect_stdout_data.clone(),
|
||||
) {
|
||||
panic!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_args(s: &str) -> Vec<String> {
|
||||
s.split_whitespace()
|
||||
.map(|arg| {
|
||||
if arg.contains(&['"', '\''][..]) {
|
||||
panic!("shell-style argument parsing is not supported");
|
||||
}
|
||||
String::from(arg)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
static LITERAL_REDACTIONS: &[(&str, &str)] = &[
|
||||
// Unix message for an entity was not found
|
||||
("[NOT_FOUND]", "No such file or directory (os error 2)"),
|
||||
// Windows message for an entity was not found
|
||||
(
|
||||
"[NOT_FOUND]",
|
||||
"The system cannot find the file specified. (os error 2)",
|
||||
),
|
||||
(
|
||||
"[NOT_FOUND]",
|
||||
"The system cannot find the path specified. (os error 3)",
|
||||
),
|
||||
("[NOT_FOUND]", "program not found"),
|
||||
// Unix message for exit status
|
||||
("[EXIT_STATUS]", "exit status"),
|
||||
// Windows message for exit status
|
||||
("[EXIT_STATUS]", "exit code"),
|
||||
("[TAB]", "\t"),
|
||||
("[EXE]", std::env::consts::EXE_SUFFIX),
|
||||
];
|
||||
|
||||
/// This makes it easier to write regex replacements that are guaranteed to only
|
||||
/// get compiled once
|
||||
macro_rules! regex {
|
||||
($re:literal $(,)?) => {{
|
||||
static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
|
||||
RE.get_or_init(|| regex::Regex::new($re).unwrap())
|
||||
}};
|
||||
}
|
||||
|
||||
fn assert(root: &Path) -> snapbox::Assert {
|
||||
let mut subs = snapbox::Redactions::new();
|
||||
subs.insert("[ROOT]", root.to_path_buf()).unwrap();
|
||||
subs.insert(
|
||||
"[TIMESTAMP]",
|
||||
regex!(r"(?m)(?<redacted>20\d\d-\d{2}-\d{2} \d{2}:\d{2}:\d{2})"),
|
||||
)
|
||||
.unwrap();
|
||||
subs.insert("[VERSION]", mdbook::MDBOOK_VERSION).unwrap();
|
||||
|
||||
subs.extend(LITERAL_REDACTIONS.into_iter().cloned())
|
||||
.unwrap();
|
||||
|
||||
snapbox::Assert::new()
|
||||
.action_env(snapbox::assert::DEFAULT_ACTION_ENV)
|
||||
.redact_with(subs)
|
||||
}
|
||||
|
||||
/// Helper to read a string from the filesystem.
|
||||
#[track_caller]
|
||||
pub fn read_to_string<P: AsRef<Path>>(path: P) -> String {
|
||||
let path = path.as_ref();
|
||||
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("could not read file {path:?}: {e:?}"))
|
||||
}
|
||||
67
tests/testsuite/build.rs
Normal file
67
tests/testsuite/build.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
//! General build tests.
|
||||
//!
|
||||
//! More specific tests should usually go into a module based on the feature.
|
||||
//! This module should just have general build tests, or misc small things.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
// Simple smoke test that building works.
|
||||
#[test]
|
||||
fn basic_build() {
|
||||
BookTest::from_dir("build/basic_build").run("build", |cmd| {
|
||||
cmd.expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
|
||||
|
||||
"#]]);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure building fails if `create-missing` is false and one of the files does
|
||||
// not exist.
|
||||
#[test]
|
||||
fn failure_on_missing_file() {
|
||||
BookTest::from_dir("build/missing_file").run("build", |cmd| {
|
||||
cmd.expect_failure().expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [ERROR] (mdbook::utils): Error: Chapter file not found, ./chapter_1.md
|
||||
[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [NOT_FOUND]
|
||||
|
||||
"#]]);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure a missing file is created if `create-missing` is true.
|
||||
#[test]
|
||||
fn create_missing() {
|
||||
let test = BookTest::from_dir("build/create_missing");
|
||||
assert!(test.dir.join("src/SUMMARY.md").exists());
|
||||
assert!(!test.dir.join("src/chapter_1.md").exists());
|
||||
test.load_book();
|
||||
assert!(test.dir.join("src/chapter_1.md").exists());
|
||||
}
|
||||
|
||||
// Checks that it fails if the summary has a reserved filename.
|
||||
#[test]
|
||||
fn no_reserved_filename() {
|
||||
BookTest::from_dir("build/no_reserved_filename").run("build", |cmd| {
|
||||
cmd.expect_failure().expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
|
||||
[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed
|
||||
[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: print.md is reserved for internal use
|
||||
|
||||
"#]]);
|
||||
});
|
||||
}
|
||||
|
||||
// Build without book.toml should be OK.
|
||||
#[test]
|
||||
fn book_toml_isnt_required() {
|
||||
let mut test = BookTest::init(|_| {});
|
||||
std::fs::remove_file(test.dir.join("book.toml")).unwrap();
|
||||
test.build();
|
||||
test.check_main_file(
|
||||
"book/chapter_1.html",
|
||||
str![[r##"<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>"##]],
|
||||
);
|
||||
}
|
||||
2
tests/testsuite/build/basic_build/book.toml
Normal file
2
tests/testsuite/build/basic_build/book.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "basic_build"
|
||||
3
tests/testsuite/build/basic_build/src/SUMMARY.md
Normal file
3
tests/testsuite/build/basic_build/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
||||
2
tests/testsuite/build/create_missing/book.toml
Normal file
2
tests/testsuite/build/create_missing/book.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "create_missing"
|
||||
3
tests/testsuite/build/create_missing/src/SUMMARY.md
Normal file
3
tests/testsuite/build/create_missing/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
||||
5
tests/testsuite/build/missing_file/book.toml
Normal file
5
tests/testsuite/build/missing_file/book.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[book]
|
||||
title = "missing_file"
|
||||
|
||||
[build]
|
||||
create-missing = false
|
||||
3
tests/testsuite/build/missing_file/src/SUMMARY.md
Normal file
3
tests/testsuite/build/missing_file/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
||||
2
tests/testsuite/build/no_reserved_filename/book.toml
Normal file
2
tests/testsuite/build/no_reserved_filename/book.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "no_reserved_filename"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Print](print.md)
|
||||
1
tests/testsuite/build/no_reserved_filename/src/print.md
Normal file
1
tests/testsuite/build/no_reserved_filename/src/print.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Print
|
||||
36
tests/testsuite/cli.rs
Normal file
36
tests/testsuite/cli.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//! Basic tests for mdbook's CLI.
|
||||
|
||||
use crate::prelude::*;
|
||||
use snapbox::file;
|
||||
|
||||
// Test with no args.
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(all(feature = "watch", feature = "serve")),
|
||||
ignore = "needs all features"
|
||||
)]
|
||||
fn no_args() {
|
||||
BookTest::empty().run("", |cmd| {
|
||||
cmd.expect_code(2)
|
||||
.expect_stdout(str![[""]])
|
||||
.expect_stderr(file!["cli/no_args.term.svg"]);
|
||||
});
|
||||
}
|
||||
|
||||
// Help command.
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(all(feature = "watch", feature = "serve")),
|
||||
ignore = "needs all features"
|
||||
)]
|
||||
fn help() {
|
||||
BookTest::empty()
|
||||
.run("help", |cmd| {
|
||||
cmd.expect_stdout(file!["cli/help.term.svg"])
|
||||
.expect_stderr(str![[""]]);
|
||||
})
|
||||
.run("--help", |cmd| {
|
||||
cmd.expect_stdout(file!["cli/help.term.svg"])
|
||||
.expect_stderr(str![[""]]);
|
||||
});
|
||||
}
|
||||
63
tests/testsuite/cli/help.term.svg
Normal file
63
tests/testsuite/cli/help.term.svg
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<svg width="740px" height="398px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
line-height: 18px;
|
||||
}
|
||||
tspan {
|
||||
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
||||
white-space: pre;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
|
||||
|
||||
<text xml:space="preserve" class="container fg">
|
||||
<tspan x="10px" y="28px"><tspan>Creates a book from markdown files</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="46px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="64px"><tspan>Usage: mdbook[EXE] [COMMAND]</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="82px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="100px"><tspan>Commands:</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="118px"><tspan> init Creates the boilerplate structure and files for a new book</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="136px"><tspan> build Builds a book from its markdown files</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="154px"><tspan> test Tests that a book's Rust code samples compile</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="172px"><tspan> clean Deletes a built book</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="190px"><tspan> completions Generate shell completions for your shell to stdout</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="208px"><tspan> watch Watches a book's files and rebuilds it on changes</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="226px"><tspan> serve Serves a book at http://localhost:3000, and rebuilds it on changes</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="244px"><tspan> help Print this message or the help of the given subcommand(s)</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="262px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="280px"><tspan>Options:</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="298px"><tspan> -h, --help Print help</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="316px"><tspan> -V, --version Print version</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="334px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="352px"><tspan>For more information about a specific command, try `mdbook <command> --help`</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="370px"><tspan>The source code for mdBook is available at: https://github.com/rust-lang/mdBook</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="388px">
|
||||
</tspan>
|
||||
</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
63
tests/testsuite/cli/no_args.term.svg
Normal file
63
tests/testsuite/cli/no_args.term.svg
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<svg width="740px" height="398px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
line-height: 18px;
|
||||
}
|
||||
tspan {
|
||||
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
||||
white-space: pre;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
|
||||
|
||||
<text xml:space="preserve" class="container fg">
|
||||
<tspan x="10px" y="28px"><tspan>Creates a book from markdown files</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="46px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="64px"><tspan>Usage: mdbook[EXE] [COMMAND]</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="82px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="100px"><tspan>Commands:</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="118px"><tspan> init Creates the boilerplate structure and files for a new book</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="136px"><tspan> build Builds a book from its markdown files</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="154px"><tspan> test Tests that a book's Rust code samples compile</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="172px"><tspan> clean Deletes a built book</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="190px"><tspan> completions Generate shell completions for your shell to stdout</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="208px"><tspan> watch Watches a book's files and rebuilds it on changes</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="226px"><tspan> serve Serves a book at http://localhost:3000, and rebuilds it on changes</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="244px"><tspan> help Print this message or the help of the given subcommand(s)</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="262px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="280px"><tspan>Options:</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="298px"><tspan> -h, --help Print help</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="316px"><tspan> -V, --version Print version</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="334px">
|
||||
</tspan>
|
||||
<tspan x="10px" y="352px"><tspan>For more information about a specific command, try `mdbook <command> --help`</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="370px"><tspan>The source code for mdBook is available at: https://github.com/rust-lang/mdBook</tspan>
|
||||
</tspan>
|
||||
<tspan x="10px" y="388px">
|
||||
</tspan>
|
||||
</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
112
tests/testsuite/includes.rs
Normal file
112
tests/testsuite/includes.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//! Tests for include preprocessor.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
// Basic test for #include.
|
||||
#[test]
|
||||
fn include() {
|
||||
BookTest::from_dir("includes/all_includes")
|
||||
.check_main_file(
|
||||
"book/includes.html",
|
||||
str![[r##"
|
||||
<h1 id="basic-includes"><a class="header" href="#basic-includes">Basic Includes</a></h1>
|
||||
<h2 id="sample"><a class="header" href="#sample">Sample</a></h2>
|
||||
<p>This is a sample include.</p>
|
||||
"##]],
|
||||
)
|
||||
.check_main_file(
|
||||
"book/relative/includes.html",
|
||||
str![[r##"
|
||||
<h1 id="relative-includes"><a class="header" href="#relative-includes">Relative Includes</a></h1>
|
||||
<h2 id="sample"><a class="header" href="#sample">Sample</a></h2>
|
||||
<p>This is a sample include.</p>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// Checks for anchored includes.
|
||||
#[test]
|
||||
fn anchored_include() {
|
||||
BookTest::from_dir("includes/all_includes").check_main_file(
|
||||
"book/anchors.html",
|
||||
str![[r##"
|
||||
<h1 id="include-anchors"><a class="header" href="#include-anchors">Include Anchors</a></h1>
|
||||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>let x = 1;
|
||||
<span class="boring">}</span></code></pre></pre>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// Checks behavior of recursive include.
|
||||
#[test]
|
||||
fn recursive_include() {
|
||||
BookTest::from_dir("includes/all_includes")
|
||||
.run("build", |cmd| {
|
||||
cmd.expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
|
||||
[TIMESTAMP] [ERROR] (mdbook::preprocess::links): Stack depth exceeded in recursive.md. Check for cyclic includes
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
|
||||
|
||||
"#]]);
|
||||
})
|
||||
.check_main_file(
|
||||
"book/recursive.html",
|
||||
str![[r#"
|
||||
<p>Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world</p>
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
// Checks the behavior of `{{#playground}}` include.
|
||||
#[test]
|
||||
fn playground_include() {
|
||||
BookTest::from_dir("includes/all_includes")
|
||||
.check_main_file("book/playground.html",
|
||||
str![[r##"
|
||||
<h1 id="playground-includes"><a class="header" href="#playground-includes">Playground Includes</a></h1>
|
||||
<pre><pre class="playground"><code class="language-rust">fn main() {
|
||||
println!("Hello World!");
|
||||
<span class="boring">
|
||||
</span><span class="boring"> // You can even hide lines! :D
|
||||
</span><span class="boring"> println!("I am hidden! Expand the code snippet to see me");
|
||||
</span>}</code></pre></pre>
|
||||
"##]]);
|
||||
}
|
||||
|
||||
// Checks the behavior of `{{#rustdoc_include}}`.
|
||||
#[test]
|
||||
fn rustdoc_include() {
|
||||
BookTest::from_dir("includes/all_includes")
|
||||
.check_main_file("book/rustdoc.html",
|
||||
str![[r##"
|
||||
<h1 id="rustdoc-includes"><a class="header" href="#rustdoc-includes">Rustdoc Includes</a></h1>
|
||||
<h2 id="rustdoc-include-adds-the-rest-of-the-file-as-hidden"><a class="header" href="#rustdoc-include-adds-the-rest-of-the-file-as-hidden">Rustdoc include adds the rest of the file as hidden</a></h2>
|
||||
<pre><pre class="playground"><code class="language-rust"><span class="boring">fn some_function() {
|
||||
</span><span class="boring"> println!("some function");
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn main() {
|
||||
some_function();
|
||||
}</code></pre></pre>
|
||||
<h2 id="rustdoc-include-works-with-anchors-too"><a class="header" href="#rustdoc-include-works-with-anchors-too">Rustdoc include works with anchors too</a></h2>
|
||||
<pre><pre class="playground"><code class="language-rust"><span class="boring">fn some_other_function() {
|
||||
</span><span class="boring"> println!("unused anchor");
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn main() {
|
||||
some_other_function();
|
||||
}</code></pre></pre>
|
||||
"##]]);
|
||||
}
|
||||
6
tests/testsuite/includes/all_includes/book.toml
Normal file
6
tests/testsuite/includes/all_includes/book.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Eric Huss"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "all_includes"
|
||||
8
tests/testsuite/includes/all_includes/src/SUMMARY.md
Normal file
8
tests/testsuite/includes/all_includes/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Summary
|
||||
|
||||
- [Basic Includes](./includes.md)
|
||||
- [Relative Includes](./relative/includes.md)
|
||||
- [Recursive Includes](./recursive.md)
|
||||
- [Include Anchors](./anchors.md)
|
||||
- [Rustdoc Includes](./rustdoc.md)
|
||||
- [Playground Includes](./playground.md)
|
||||
5
tests/testsuite/includes/all_includes/src/anchors.md
Normal file
5
tests/testsuite/includes/all_includes/src/anchors.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Include Anchors
|
||||
|
||||
```rust
|
||||
{{#include nested-test-with-anchors.rs:myanchor}}
|
||||
```
|
||||
4
tests/testsuite/includes/all_includes/src/includes.md
Normal file
4
tests/testsuite/includes/all_includes/src/includes.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Basic Includes
|
||||
|
||||
{{#include sample.md}}
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// This is a test of includes with anchors.
|
||||
|
||||
// ANCHOR: myanchor
|
||||
// ANCHOR: unendinganchor
|
||||
let x = 1;
|
||||
// ANCHOR_END: myanchor
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
fn some_other_function() {
|
||||
// ANCHOR: unused-anchor-that-should-be-stripped
|
||||
assert!($TEST_STATUS);
|
||||
println!("unused anchor");
|
||||
// ANCHOR_END: unused-anchor-that-should-be-stripped
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fn some_function() {
|
||||
assert!($TEST_STATUS);
|
||||
println!("some function");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
3
tests/testsuite/includes/all_includes/src/playground.md
Normal file
3
tests/testsuite/includes/all_includes/src/playground.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Playground Includes
|
||||
|
||||
{{#playground example.rs}}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Relative Includes
|
||||
|
||||
{{#include ../sample.md}}
|
||||
13
tests/testsuite/includes/all_includes/src/rustdoc.md
Normal file
13
tests/testsuite/includes/all_includes/src/rustdoc.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Rustdoc Includes
|
||||
|
||||
## Rustdoc include adds the rest of the file as hidden
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include partially-included-test.rs:5:7}}
|
||||
```
|
||||
|
||||
## Rustdoc include works with anchors too
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}}
|
||||
```
|
||||
3
tests/testsuite/includes/all_includes/src/sample.md
Normal file
3
tests/testsuite/includes/all_includes/src/sample.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Sample
|
||||
|
||||
This is a sample include.
|
||||
38
tests/testsuite/index.rs
Normal file
38
tests/testsuite/index.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//! Tests for the index preprocessor.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
// Checks basic README to index.html conversion.
|
||||
#[test]
|
||||
fn readme_to_index() {
|
||||
let mut test = BookTest::from_dir("index/basic_readme");
|
||||
test.check_main_file(
|
||||
"book/index.html",
|
||||
str![[r##"<h1 id="intro"><a class="header" href="#intro">Intro</a></h1>"##]],
|
||||
)
|
||||
.check_main_file(
|
||||
"book/first/index.html",
|
||||
str![[r##"<h1 id="first"><a class="header" href="#first">First</a></h1>"##]],
|
||||
)
|
||||
.check_main_file(
|
||||
"book/second/index.html",
|
||||
str![[r##"<h1 id="second"><a class="header" href="#second">Second</a></h1>"##]],
|
||||
)
|
||||
.check_toc_js(str![[r#"
|
||||
<ol class="chapter">
|
||||
<li class="chapter-item expanded affix ">
|
||||
<a href="index.html">Intro</a>
|
||||
</li>
|
||||
<li class="chapter-item expanded ">
|
||||
<a href="first/index.html">
|
||||
<strong aria-hidden="true">1.</strong> First</a>
|
||||
</li>
|
||||
<li class="chapter-item expanded ">
|
||||
<a href="second/index.html">
|
||||
<strong aria-hidden="true">2.</strong> Second</a>
|
||||
</li>
|
||||
</ol>
|
||||
"#]]);
|
||||
assert!(test.dir.join("book/index.html").exists());
|
||||
assert!(!test.dir.join("book/README.html").exists());
|
||||
}
|
||||
2
tests/testsuite/index/basic_readme/book.toml
Normal file
2
tests/testsuite/index/basic_readme/book.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "basic_readme"
|
||||
1
tests/testsuite/index/basic_readme/src/README.md
Normal file
1
tests/testsuite/index/basic_readme/src/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Intro
|
||||
6
tests/testsuite/index/basic_readme/src/SUMMARY.md
Normal file
6
tests/testsuite/index/basic_readme/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Summary
|
||||
|
||||
[Intro](./README.md)
|
||||
|
||||
- [First](first/README)
|
||||
- [Second](second/Readme.md)
|
||||
1
tests/testsuite/index/basic_readme/src/first/README
Normal file
1
tests/testsuite/index/basic_readme/src/first/README
Normal file
|
|
@ -0,0 +1 @@
|
|||
# First
|
||||
1
tests/testsuite/index/basic_readme/src/second/Readme.md
Normal file
1
tests/testsuite/index/basic_readme/src/second/Readme.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Second
|
||||
253
tests/testsuite/init.rs
Normal file
253
tests/testsuite/init.rs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
//! Tests for `mdbook init`.
|
||||
|
||||
use crate::prelude::*;
|
||||
use mdbook::{Config, MDBook};
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Tests "init" with no args.
|
||||
#[test]
|
||||
fn basic_init() {
|
||||
let mut test = BookTest::empty();
|
||||
test.run("init", |cmd| {
|
||||
cmd.expect_stdout(str![[r#"
|
||||
|
||||
Do you want a .gitignore to be created? (y/n)
|
||||
What title would you like to give the book?
|
||||
|
||||
All done, no errors...
|
||||
|
||||
"#]])
|
||||
.expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
|
||||
|
||||
"#]]);
|
||||
})
|
||||
.check_file(
|
||||
"book.toml",
|
||||
str![[r#"
|
||||
[book]
|
||||
authors = []
|
||||
language = "en"
|
||||
src = "src"
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"src/SUMMARY.md",
|
||||
str
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"src/chapter_1.md",
|
||||
str![[r#"
|
||||
# Chapter 1
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_main_file(
|
||||
"book/chapter_1.html",
|
||||
str![[r##"<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>"##]],
|
||||
);
|
||||
assert!(!test.dir.join(".gitignore").exists());
|
||||
assert!(test.dir.join("book").exists());
|
||||
}
|
||||
|
||||
// Test init via API. This does a little less than the CLI does.
|
||||
#[test]
|
||||
fn init_api() {
|
||||
let mut test = BookTest::empty();
|
||||
MDBook::init(&test.dir).build().unwrap();
|
||||
test.check_file_list(
|
||||
".",
|
||||
str![[r#"
|
||||
book
|
||||
book.toml
|
||||
src
|
||||
src/SUMMARY.md
|
||||
src/chapter_1.md
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
// Run `mdbook init` with `--force` to skip the confirmation prompts
|
||||
#[test]
|
||||
fn init_force() {
|
||||
let mut test = BookTest::empty();
|
||||
test.run("init --force", |cmd| {
|
||||
cmd.expect_stdout(str![[r#"
|
||||
|
||||
All done, no errors...
|
||||
|
||||
"#]])
|
||||
.expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
|
||||
|
||||
"#]]);
|
||||
})
|
||||
.check_file(
|
||||
"book.toml",
|
||||
str![[r#"
|
||||
[book]
|
||||
authors = []
|
||||
language = "en"
|
||||
src = "src"
|
||||
|
||||
"#]],
|
||||
);
|
||||
assert!(!test.dir.join(".gitignore").exists());
|
||||
}
|
||||
|
||||
// Run `mdbook init` with `--title` without git config.
|
||||
//
|
||||
// Regression test for https://github.com/rust-lang/mdBook/issues/2485
|
||||
#[test]
|
||||
fn no_git_config_with_title() {
|
||||
let mut test = BookTest::empty();
|
||||
test.run("init", |cmd| {
|
||||
cmd.expect_stdout(str![[r#"
|
||||
|
||||
Do you want a .gitignore to be created? (y/n)
|
||||
|
||||
All done, no errors...
|
||||
|
||||
"#]])
|
||||
.expect_stderr(str![[r#"
|
||||
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
|
||||
|
||||
"#]])
|
||||
.args(&["--title", "Example title"]);
|
||||
})
|
||||
.check_file(
|
||||
"book.toml",
|
||||
str![[r#"
|
||||
[book]
|
||||
authors = []
|
||||
language = "en"
|
||||
src = "src"
|
||||
title = "Example title"
|
||||
|
||||
"#]],
|
||||
);
|
||||
assert!(!test.dir.join(".gitignore").exists());
|
||||
}
|
||||
|
||||
// Run `mdbook init` in a directory containing a SUMMARY.md should create the
|
||||
// files listed in the summary.
|
||||
#[test]
|
||||
fn init_from_summary() {
|
||||
BookTest::from_dir("init/init_from_summary")
|
||||
.run("init", |_| {})
|
||||
.check_file(
|
||||
"src/intro.md",
|
||||
str![[r#"
|
||||
# intro
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"src/first.md",
|
||||
str![[r#"
|
||||
# First chapter
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"src/outro.md",
|
||||
str![[r#"
|
||||
# outro
|
||||
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
// Set some custom arguments for where to place the source and destination
|
||||
// files, then call `mdbook init`.
|
||||
#[test]
|
||||
fn init_with_custom_book_and_src_locations() {
|
||||
let mut test = BookTest::empty();
|
||||
let mut cfg = Config::default();
|
||||
cfg.book.src = PathBuf::from("in");
|
||||
cfg.build.build_dir = PathBuf::from("out");
|
||||
MDBook::init(&test.dir).with_config(cfg).build().unwrap();
|
||||
test.check_file(
|
||||
"book.toml",
|
||||
str![[r#"
|
||||
[book]
|
||||
authors = []
|
||||
language = "en"
|
||||
src = "in"
|
||||
|
||||
[build]
|
||||
build-dir = "out"
|
||||
create-missing = true
|
||||
extra-watch-dirs = []
|
||||
use-default-preprocessors = true
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"in/SUMMARY.md",
|
||||
str
|
||||
|
||||
"#]],
|
||||
)
|
||||
.check_file(
|
||||
"in/chapter_1.md",
|
||||
str![[r#"
|
||||
# Chapter 1
|
||||
|
||||
"#]],
|
||||
);
|
||||
assert!(test.dir.join("out").exists());
|
||||
}
|
||||
|
||||
// Copies the theme into the initialized directory.
|
||||
#[test]
|
||||
fn copy_theme() {
|
||||
BookTest::empty()
|
||||
.run("init --theme", |_| {})
|
||||
.check_file_list(
|
||||
".",
|
||||
str![[r#"
|
||||
book
|
||||
book.toml
|
||||
src
|
||||
src/SUMMARY.md
|
||||
src/chapter_1.md
|
||||
theme
|
||||
theme/book.js
|
||||
theme/css
|
||||
theme/css/chrome.css
|
||||
theme/css/general.css
|
||||
theme/css/print.css
|
||||
theme/css/variables.css
|
||||
theme/favicon.png
|
||||
theme/favicon.svg
|
||||
theme/fonts
|
||||
theme/fonts/OPEN-SANS-LICENSE.txt
|
||||
theme/fonts/SOURCE-CODE-PRO-LICENSE.txt
|
||||
theme/fonts/fonts.css
|
||||
theme/fonts/open-sans-v17-all-charsets-300.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-300italic.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-600.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-600italic.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-700.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-700italic.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-800.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-800italic.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-italic.woff2
|
||||
theme/fonts/open-sans-v17-all-charsets-regular.woff2
|
||||
theme/fonts/source-code-pro-v11-all-charsets-500.woff2
|
||||
theme/highlight.css
|
||||
theme/highlight.js
|
||||
theme/index.hbs
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
8
tests/testsuite/init/init_from_summary/src/SUMMARY.md
Normal file
8
tests/testsuite/init/init_from_summary/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Summary
|
||||
|
||||
[intro](intro.md)
|
||||
|
||||
- [First chapter](first.md)
|
||||
|
||||
[outro](outro.md)
|
||||
|
||||
27
tests/testsuite/main.rs
Normal file
27
tests/testsuite/main.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//! Main testsuite for exercising all functionality of mdBook.
|
||||
//!
|
||||
//! See README.md for documentation.
|
||||
|
||||
mod book_test;
|
||||
mod build;
|
||||
mod cli;
|
||||
mod includes;
|
||||
mod index;
|
||||
mod init;
|
||||
mod markdown;
|
||||
mod playground;
|
||||
mod preprocessor;
|
||||
mod print;
|
||||
mod redirects;
|
||||
mod renderer;
|
||||
mod rendering;
|
||||
#[cfg(feature = "search")]
|
||||
mod search;
|
||||
mod test;
|
||||
mod theme;
|
||||
mod toc;
|
||||
|
||||
mod prelude {
|
||||
pub use crate::book_test::BookTest;
|
||||
pub use snapbox::str;
|
||||
}
|
||||
146
tests/testsuite/markdown.rs
Normal file
146
tests/testsuite/markdown.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
//! Tests for special markdown rendering.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
// Checks custom header id and classes.
|
||||
#[test]
|
||||
fn custom_header_attributes() {
|
||||
BookTest::from_dir("markdown/custom_header_attributes")
|
||||
.check_main_file("book/custom_header_attributes.html", str![[r##"
|
||||
<h1 id="attrs"><a class="header" href="#attrs">Heading Attributes</a></h1>
|
||||
<h2 id="heading-with-classes" class="class1 class2"><a class="header" href="#heading-with-classes">Heading with classes</a></h2>
|
||||
<h2 id="both" class="class1 class2"><a class="header" href="#both">Heading with id and classes</a></h2>
|
||||
"##]]);
|
||||
}
|
||||
|
||||
// Test for a variety of footnote renderings.
|
||||
#[test]
|
||||
fn footnotes() {
|
||||
BookTest::from_dir("markdown/footnotes")
|
||||
.check_main_file("book/footnotes.html", str![[r##"
|
||||
<h1 id="footnote-tests"><a class="header" href="#footnote-tests">Footnote tests</a></h1>
|
||||
<p>Footnote example<sup class="footnote-reference" id="fr-1-1"><a href="#footnote-1">1</a></sup>, or with a word<sup class="footnote-reference" id="fr-word-1"><a href="#footnote-word">2</a></sup>.</p>
|
||||
<p>There are multiple references to word<sup class="footnote-reference" id="fr-word-2"><a href="#footnote-word">2</a></sup>.</p>
|
||||
<p>Footnote without a paragraph<sup class="footnote-reference" id="fr-para-1"><a href="#footnote-para">3</a></sup></p>
|
||||
<p>Footnote with multiple paragraphs<sup class="footnote-reference" id="fr-multiple-1"><a href="#footnote-multiple">4</a></sup></p>
|
||||
<p>Footnote name with wacky characters<sup class="footnote-reference" id="fr-"wacky"-1"><a href="#footnote-"wacky"">5</a></sup></p>
|
||||
<p>Testing when referring to something earlier.<sup class="footnote-reference" id="fr-define-before-use-1"><a href="#footnote-define-before-use">6</a></sup></p>
|
||||
<hr>
|
||||
<ol class="footnote-definition"><li id="footnote-1">
|
||||
<p>This is a footnote. <a href="#fr-1-1">↩</a> <a href="#fr-1-2">↩2</a></p>
|
||||
</li>
|
||||
<li id="footnote-word">
|
||||
<p>A longer footnote.
|
||||
With multiple lines. <a href="other.html">Link to other</a>.
|
||||
With a reference inside.<sup class="footnote-reference" id="fr-1-2"><a href="#footnote-1">1</a></sup> <a href="#fr-word-1">↩</a> <a href="#fr-word-2">↩2</a></p>
|
||||
</li>
|
||||
<li id="footnote-para">
|
||||
<ol>
|
||||
<li>Item one
|
||||
<ol>
|
||||
<li>Sub-item</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>Item two</li>
|
||||
</ol>
|
||||
<a href="#fr-para-1">↩</a></li>
|
||||
<li id="footnote-multiple">
|
||||
<p>One</p>
|
||||
<p>Two</p>
|
||||
<p>Three <a href="#fr-multiple-1">↩</a></p>
|
||||
</li>
|
||||
<li id="footnote-"wacky"">
|
||||
<p>Testing footnote id with special characters. <a href="#fr-"wacky"-1">↩</a></p>
|
||||
</li>
|
||||
<li id="footnote-define-before-use">
|
||||
<p>This is defined before it is referred to. <a href="#fr-define-before-use-1">↩</a></p>
|
||||
</li>
|
||||
</ol>
|
||||
"##]]);
|
||||
}
|
||||
|
||||
// Basic table test.
|
||||
#[test]
|
||||
fn tables() {
|
||||
BookTest::from_dir("markdown/tables").check_main_file(
|
||||
"book/tables.html",
|
||||
str![[r##"
|
||||
<h1 id="tables"><a class="header" href="#tables">Tables</a></h1>
|
||||
<div class="table-wrapper"><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody>
|
||||
<tr><td>baz</td><td>bim</td></tr>
|
||||
<tr><td>Backslash in code</td><td><code>/</code></td></tr>
|
||||
<tr><td>Double back in code</td><td><code>//</code></td></tr>
|
||||
<tr><td>Pipe in code</td><td><code>|</code></td></tr>
|
||||
<tr><td>Pipe in code2</td><td><code>test | inside</code></td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// Strikethrough test.
|
||||
#[test]
|
||||
fn strikethrough() {
|
||||
BookTest::from_dir("markdown/strikethrough").check_main_file(
|
||||
"book/strikethrough.html",
|
||||
str![[r##"
|
||||
<h1 id="strikethrough"><a class="header" href="#strikethrough">Strikethrough</a></h1>
|
||||
<p><del>strikethrough example</del></p>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// Tasklist test.
|
||||
#[test]
|
||||
fn tasklists() {
|
||||
BookTest::from_dir("markdown/tasklists").check_main_file(
|
||||
"book/tasklists.html",
|
||||
str![[r##"
|
||||
<h2 id="tasklisks"><a class="header" href="#tasklisks">Tasklisks</a></h2>
|
||||
<ul>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
Apples</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
Broccoli</li>
|
||||
<li><input disabled="" type="checkbox"/>
|
||||
Carrots</li>
|
||||
</ul>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// Smart punctuation test.
|
||||
#[test]
|
||||
fn smart_punctuation() {
|
||||
BookTest::from_dir("markdown/smart_punctuation")
|
||||
// Default is off.
|
||||
.check_main_file(
|
||||
"book/smart_punctuation.html",
|
||||
str![[r##"
|
||||
<h1 id="smart-punctuation"><a class="header" href="#smart-punctuation">Smart Punctuation</a></h1>
|
||||
<ul>
|
||||
<li>En dash: --</li>
|
||||
<li>Em dash: ---</li>
|
||||
<li>Ellipsis: ...</li>
|
||||
<li>Double quote: "quote"</li>
|
||||
<li>Single quote: 'quote'</li>
|
||||
</ul>
|
||||
"##]],
|
||||
)
|
||||
.run("build", |cmd| {
|
||||
cmd.env("MDBOOK_OUTPUT__HTML__SMART_PUNCTUATION", "true");
|
||||
})
|
||||
.check_main_file(
|
||||
"book/smart_punctuation.html",
|
||||
str![[r##"
|
||||
<h1 id="smart-punctuation"><a class="header" href="#smart-punctuation">Smart Punctuation</a></h1>
|
||||
<ul>
|
||||
<li>En dash: –</li>
|
||||
<li>Em dash: —</li>
|
||||
<li>Ellipsis: …</li>
|
||||
<li>Double quote: “quote”</li>
|
||||
<li>Single quote: ‘quote’</li>
|
||||
</ul>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "custom_header_attributes"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Heading Attributes](./custom_header_attributes.md)
|
||||
1
tests/testsuite/markdown/footnotes/src/SUMMARY.md
Normal file
1
tests/testsuite/markdown/footnotes/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- [Footnotes](footnotes.md)
|
||||
|
|
@ -1,21 +1,11 @@
|
|||
# Markdown tests
|
||||
|
||||
Tests for some markdown output.
|
||||
|
||||
## Tables
|
||||
|
||||
| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |
|
||||
|
||||
## Footnotes
|
||||
# Footnote tests
|
||||
|
||||
Footnote example[^1], or with a word[^word].
|
||||
|
||||
[^1]: This is a footnote.
|
||||
|
||||
[^word]: A longer footnote.
|
||||
With multiple lines. [Link to unicode](unicode.md).
|
||||
With multiple lines. [Link to other](other.md).
|
||||
With a reference inside.[^1]
|
||||
|
||||
There are multiple references to word[^word].
|
||||
|
|
@ -31,10 +21,12 @@ Footnote with multiple paragraphs[^multiple]
|
|||
|
||||
[^define-before-use]: This is defined before it is referred to.
|
||||
|
||||
<!-- Using <p> tags to work around rustdoc issue, this should move to a separate book.
|
||||
https://github.com/rust-lang/rust/issues/139064
|
||||
-->
|
||||
[^multiple]: <p>One</p><p>Two</p><p>Three</p>
|
||||
[^multiple]:
|
||||
One
|
||||
|
||||
Two
|
||||
|
||||
Three
|
||||
|
||||
[^unused]: This footnote is defined by not used.
|
||||
|
||||
|
|
@ -43,13 +35,3 @@ Footnote name with wacky characters[^"wacky"]
|
|||
[^"wacky"]: Testing footnote id with special characters.
|
||||
|
||||
Testing when referring to something earlier.[^define-before-use]
|
||||
|
||||
## Strikethrough
|
||||
|
||||
~~strikethrough example~~
|
||||
|
||||
## Tasklisks
|
||||
|
||||
- [X] Apples
|
||||
- [X] Broccoli
|
||||
- [ ] Carrots
|
||||
|
|
@ -0,0 +1 @@
|
|||
- [Smart Punctuation](smart_punctuation.md)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Smart Punctuation
|
||||
|
||||
- En dash: --
|
||||
- Em dash: ---
|
||||
- Ellipsis: ...
|
||||
- Double quote: "quote"
|
||||
- Single quote: 'quote'
|
||||
1
tests/testsuite/markdown/strikethrough/src/SUMMARY.md
Normal file
1
tests/testsuite/markdown/strikethrough/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- [Strikethrough](strikethrough.md)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Strikethrough
|
||||
|
||||
~~strikethrough example~~
|
||||
|
||||
1
tests/testsuite/markdown/tables/src/SUMMARY.md
Normal file
1
tests/testsuite/markdown/tables/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- [Tables](tables.md)
|
||||
9
tests/testsuite/markdown/tables/src/tables.md
Normal file
9
tests/testsuite/markdown/tables/src/tables.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Tables
|
||||
|
||||
| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |
|
||||
| Backslash in code | `\` |
|
||||
| Double back in code | `\\` |
|
||||
| Pipe in code | `\|` |
|
||||
| Pipe in code2 | `test \| inside` |
|
||||
1
tests/testsuite/markdown/tasklists/src/SUMMARY.md
Normal file
1
tests/testsuite/markdown/tasklists/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- [Tasklists](tasklists.md)
|
||||
5
tests/testsuite/markdown/tasklists/src/tasklists.md
Normal file
5
tests/testsuite/markdown/tasklists/src/tasklists.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
## Tasklisks
|
||||
|
||||
- [X] Apples
|
||||
- [X] Broccoli
|
||||
- [ ] Carrots
|
||||
30
tests/testsuite/playground.rs
Normal file
30
tests/testsuite/playground.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//! Tests for Rust playground support.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
// Verifies that a rust codeblock gets the playground class.
|
||||
#[test]
|
||||
fn playground_on_rust_code() {
|
||||
BookTest::from_dir("playground/playground_on_rust_code").check_main_file(
|
||||
"book/index.html",
|
||||
str![[r##"
|
||||
<h1 id="rust-sample"><a class="header" href="#rust-sample">Rust Sample</a></h1>
|
||||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>let x = 1;
|
||||
<span class="boring">}</span></code></pre></pre>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
// When the playground is disabled, there should be no playground class.
|
||||
#[test]
|
||||
fn disabled_playground() {
|
||||
BookTest::from_dir("playground/disabled_playground").check_main_file(
|
||||
"book/index.html",
|
||||
str![[r##"
|
||||
<h1 id="rust-sample"><a class="header" href="#rust-sample">Rust Sample</a></h1>
|
||||
<pre><code class="language-rust">let x = 1;</code></pre>
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
5
tests/testsuite/playground/disabled_playground/book.toml
Normal file
5
tests/testsuite/playground/disabled_playground/book.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[book]
|
||||
title = "playground_on_rust_code"
|
||||
|
||||
[output.html.playground]
|
||||
runnable = false
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Rust Playground](./index.md)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Rust Sample
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
```
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[book]
|
||||
title = "playground_on_rust_code"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Rust Playground](./index.md)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Rust Sample
|
||||
|
||||
```rust
|
||||
let x = 1;
|
||||
```
|
||||
95
tests/testsuite/preprocessor.rs
Normal file
95
tests/testsuite/preprocessor.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//! Tests for custom preprocessors.
|
||||
|
||||
use crate::prelude::*;
|
||||
use mdbook::book::Book;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct Spy(Arc<Mutex<Inner>>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Inner {
|
||||
run_count: usize,
|
||||
rendered_with: Vec<String>,
|
||||
}
|
||||
|
||||
impl Preprocessor for Spy {
|
||||
fn name(&self) -> &str {
|
||||
"dummy"
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
|
||||
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<Mutex<Inner>> = 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::book): Book building has started
|
||||
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
|
||||
|
||||
"#]]);
|
||||
});
|
||||
}
|
||||
|
||||
// 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::book): Book building has started
|
||||
Boom!!1!
|
||||
[TIMESTAMP] [ERROR] (mdbook::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(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_supports_whatever() {
|
||||
let cmd = example();
|
||||
|
||||
let got = cmd.supports_renderer("whatever");
|
||||
|
||||
assert_eq!(got, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_doesnt_support_not_supported() {
|
||||
let cmd = example();
|
||||
|
||||
let got = cmd.supports_renderer("not-supported");
|
||||
|
||||
assert_eq!(got, false);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[preprocessor.nop-preprocessor]
|
||||
command = "cargo run --quiet --example nop-preprocessor --"
|
||||
blow-up = true
|
||||
|
||||
2
tests/testsuite/preprocessor/nop_preprocessor/book.toml
Normal file
2
tests/testsuite/preprocessor/nop_preprocessor/book.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[preprocessor.nop-preprocessor]
|
||||
command = "cargo run --quiet --example nop-preprocessor --"
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue