-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Regression tests #422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Regression tests #422
Changes from 6 commits
95609cf
d11e3f6
dc9c1c9
d86f960
f8a3382
4266a93
1bcd914
65191c3
2a1ae82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
//! 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)] | ||
extern crate mdbook; | ||
extern crate tempdir; | ||
extern crate walkdir; | ||
|
||
use std::path::Path; | ||
use std::fs::{self, File}; | ||
use std::io::{Read, Write}; | ||
use std::error::Error; | ||
|
||
// The funny `self::` here is because we've got an `extern crate ...` and are | ||
// in a submodule | ||
use self::tempdir::TempDir; | ||
use self::mdbook::MDBook; | ||
use self::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::default() | ||
} | ||
|
||
/// 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 Self { | ||
self.passing_test = test_passes; | ||
self | ||
} | ||
|
||
/// Write a book to a temporary directory using the provided settings. | ||
/// | ||
/// # Note | ||
/// | ||
/// If this fails for any reason it will `panic!()`. If we can't write to a | ||
/// temporary directory then chances are you've got bigger problems... | ||
pub fn build(&self) -> TempDir { | ||
let temp = TempDir::new("dummy_book").unwrap(); | ||
|
||
let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book"); | ||
recursive_copy(&dummy_book_root, temp.path()).expect("Couldn't copy files into a \ | ||
temporary directory"); | ||
|
||
let sub_pattern = if self.passing_test { "true" } else { "false" }; | ||
let file_containing_test = temp.path().join("src/first/nested.md"); | ||
poor_mans_sed(&file_containing_test, "$TEST_STATUS", sub_pattern); | ||
|
||
temp | ||
} | ||
} | ||
|
||
fn poor_mans_sed(filename: &Path, from: &str, to: &str) { | ||
|
||
let contents = read_file(filename).unwrap(); | ||
File::create(filename).unwrap() | ||
.write_all(contents.replace(from, to).as_bytes()) | ||
.unwrap(); | ||
} | ||
|
||
impl Default for DummyBook { | ||
|
||
fn default() -> DummyBook { | ||
DummyBook { passing_test: true } | ||
} | ||
} | ||
|
||
/// 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 mut content = String::new(); | ||
File::open(&filename).expect("Couldn't open the provided file") | ||
|
||
.read_to_string(&mut content) | ||
.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); | ||
} | ||
} | ||
|
||
|
||
|
||
/// 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<(), Box<Error>> { | ||
|
||
let from = from.as_ref(); | ||
let to = to.as_ref(); | ||
|
||
for entry in WalkDir::new(&from) { | ||
let entry = entry?; | ||
|
||
let original_location = entry.path(); | ||
let relative = original_location.strip_prefix(&from)?; | ||
let new_location = to.join(relative); | ||
|
||
if original_location.is_file() { | ||
if let Some(parent) = new_location.parent() { | ||
fs::create_dir_all(parent)?; | ||
} | ||
|
||
fs::copy(&original_location, &new_location)?; | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn read_file<P: AsRef<Path>>(filename: P) -> Result<String, Box<Error>> { | ||
|
||
let mut contents = String::new(); | ||
File::open(filename)?.read_to_string(&mut contents)?; | ||
|
||
Ok(contents) | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,26 @@ | ||
extern crate mdbook; | ||
extern crate tempdir; | ||
#[macro_use] | ||
extern crate pretty_assertions; | ||
extern crate select; | ||
extern crate walkdir; | ||
|
||
mod dummy; | ||
mod helpers; | ||
mod dummy_book; | ||
|
||
use dummy::DummyBook; | ||
use helpers::assert_contains_strings; | ||
use dummy_book::{assert_contains_strings, DummyBook}; | ||
use mdbook::MDBook; | ||
|
||
use std::path::Path; | ||
use walkdir::{DirEntry, WalkDir, WalkDirIterator}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we probably should move to new walkdir which does not have WalkDirIterator (but It's best left for separate PR) |
||
use select::document::Document; | ||
use select::predicate::{Class, Name, Predicate}; | ||
|
||
|
||
const BOOK_ROOT: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book"); | ||
const TOC_TOP_LEVEL: &[&'static str] = &["1. First Chapter", | ||
"2. Second Chapter", | ||
"Conclusion", | ||
"Introduction"]; | ||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter"]; | ||
|
||
/// Make sure you can load the dummy book and build it without panicking. | ||
#[test] | ||
|
@@ -111,3 +124,100 @@ fn chapter_content_appears_in_rendered_document() { | |
assert_contains_strings(path, &[text]); | ||
} | ||
} | ||
|
||
|
||
/// Apply a series of predicates to some root predicate, where each | ||
/// successive predicate is the descendant of the last one. | ||
macro_rules! descendants { | ||
($root:expr, $($child:expr),*) => { | ||
$root | ||
$( | ||
.descendant($child) | ||
)* | ||
}; | ||
} | ||
|
||
|
||
/// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered | ||
/// and placed in the `book` directory with their extensions set to `*.html`. | ||
#[test] | ||
fn chapter_files_were_rendered_to_html() { | ||
let temp = DummyBook::new().build(); | ||
let src = Path::new(BOOK_ROOT).join("src"); | ||
|
||
let chapter_files = WalkDir::new(&src).into_iter() | ||
.filter_entry(|entry| entry_ends_with(entry, ".md")) | ||
.filter_map(|entry| entry.ok()) | ||
.map(|entry| entry.path().to_path_buf()) | ||
.filter(|path| path.file_name().unwrap() != "SUMMARY"); | ||
|
||
|
||
for chapter in chapter_files { | ||
let rendered_location = temp.path().join(chapter.strip_prefix(&src).unwrap()) | ||
.with_extension("html"); | ||
assert!(rendered_location.exists(), | ||
"{} doesn't exits", | ||
rendered_location.display()); | ||
} | ||
} | ||
|
||
fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool { | ||
entry.file_name().to_string_lossy().ends_with(ending) | ||
} | ||
|
||
/// Read the main page (`book/index.html`) and expose it as a DOM which we | ||
/// can search with the `select` crate | ||
fn root_index_html() -> Document { | ||
let temp = DummyBook::new().build(); | ||
MDBook::new(temp.path()).build().unwrap(); | ||
|
||
|
||
let index_page = temp.path().join("book").join("index.html"); | ||
println!("{}", index_page.display()); | ||
for thing in temp.path().read_dir().unwrap() { | ||
println!("{:?}", thing); | ||
} | ||
let html = dummy_book::read_file(&index_page).unwrap(); | ||
Document::from(html.as_str()) | ||
} | ||
|
||
#[test] | ||
fn check_second_toc_level() { | ||
let doc = root_index_html(); | ||
let mut should_be = Vec::from(TOC_SECOND_LEVEL); | ||
should_be.sort(); | ||
|
||
let pred = descendants!(Class("chapter"), Name("li"), Name("li"), Name("a")); | ||
|
||
let mut children_of_children: Vec<String> = | ||
|
||
doc.find(pred).map(|elem| elem.text().trim().to_string()) | ||
.collect(); | ||
children_of_children.sort(); | ||
|
||
assert_eq!(children_of_children, should_be); | ||
} | ||
|
||
#[test] | ||
fn check_first_toc_level() { | ||
let doc = root_index_html(); | ||
let mut should_be = Vec::from(TOC_TOP_LEVEL); | ||
|
||
should_be.extend(TOC_SECOND_LEVEL); | ||
should_be.sort(); | ||
|
||
let pred = descendants!(Class("chapter"), Name("li"), Name("a")); | ||
|
||
let mut children: Vec<String> = doc.find(pred).map(|elem| elem.text().trim().to_string()) | ||
|
||
.collect(); | ||
children.sort(); | ||
|
||
assert_eq!(children, should_be); | ||
} | ||
|
||
#[test] | ||
fn check_spacers() { | ||
let doc = root_index_html(); | ||
let should_be = 1; | ||
|
||
let num_spacers = | ||
doc.find(Class("chapter").descendant(Name("li").and(Class("spacer")))).count(); | ||
assert_eq!(num_spacers, should_be); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not implement it directly? It's a single bool.