use crate::includes::DebugIncluder;
use crate::parsing::{ParseWarning, ParseWarningKind, Token};
use crate::tree::{Element, SyntaxTree};
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use void::ResultVoidExt;
const SKIP_TESTS: &[&str] = &[];
lazy_static! {
static ref TEST_DIRECTORY: PathBuf = {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("test");
path
};
}
macro_rules! file_name {
($entry:expr) => {
$entry.file_name().to_string_lossy()
};
}
#[derive(Serialize, Deserialize, Debug)]
struct Test<'a> {
#[serde(skip)]
name: String,
input: String,
tree: SyntaxTree<'a>,
warnings: Vec<ParseWarning>,
}
impl Test<'_> {
pub fn load(path: &Path, name: &str) -> Self {
assert!(path.is_absolute());
let mut file = match File::open(path) {
Ok(file) => file,
Err(error) => panic!("Unable to open file '{}': {}", path.display(), error),
};
let mut test: Self = match serde_json::from_reader(&mut file) {
Ok(test) => test,
Err(error) => {
panic!("Unable to parse JSON file '{}': {}", path.display(), error)
}
};
test.name = str!(name);
test
}
pub fn run(&self, log: &slog::Logger) {
info!(
&log,
"Running syntax tree test case";
"name" => &self.name,
"input" => &self.input,
);
if SKIP_TESTS.contains(&&*self.name) {
println!("+ {} [SKIPPED]", self.name);
return;
}
println!("+ {}", self.name);
let (mut text, _pages) =
crate::include(log, &self.input, DebugIncluder, || unreachable!())
.void_unwrap();
crate::preprocess(log, &mut text);
let tokens = crate::tokenize(log, &text);
let result = crate::parse(log, &tokens);
let (tree, warnings) = result.into();
fn json<T>(object: &T) -> String
where
T: serde::Serialize,
{
let mut output = serde_json::to_string_pretty(object)
.expect("Unable to serialize JSON to stdout");
output.insert_str(0, "Generated JSON: ");
output
}
if tree != self.tree {
panic!(
"Running test '{}' failed! AST did not match:\nExpected: {:#?}\nActual: {:#?}\n{}\nWarnings: {:#?}",
self.name,
self.tree,
tree,
json(&tree),
&warnings,
);
}
if warnings != self.warnings {
panic!(
"Running test '{}' failed! Warnings did not match:\nExpected: {:#?}\nActual: {:#?}\n{}\nTree (correct): {:#?}",
self.name,
self.warnings,
warnings,
json(&warnings),
&tree,
);
}
}
}
#[test]
fn ast() {
let log = crate::build_logger();
if !SKIP_TESTS.is_empty() {
println!("=========");
println!(" WARNING ");
println!("=========");
println!();
println!("The following tests are being SKIPPED:");
for test in SKIP_TESTS {
println!("- {}", test);
}
println!();
}
let entries = fs::read_dir(&*TEST_DIRECTORY) .expect("Unable to read directory");
let tests_iter = entries.filter_map(|entry| {
let entry = entry.expect("Unable to read directory entry");
let ftype = entry.file_type().expect("Unable to get file type");
if !ftype.is_file() {
println!("Skipping non-file {}", file_name!(entry));
return None;
}
let path = entry.path();
let stem = path
.file_stem()
.expect("Unable to get file stem")
.to_string_lossy();
if path.extension() != Some(OsStr::new("json")) {
println!("Skipping non-JSON file {}", file_name!(entry));
return None;
}
Some(Test::load(&path, &stem))
});
let mut tests: Vec<Test> = tests_iter.collect();
tests.sort_by(|a, b| (a.name).cmp(&b.name));
println!("Running tests:");
for test in tests {
test.run(&log);
}
}
#[test]
fn recursion_depth() {
let log = crate::build_logger();
let mut input = String::new();
for _ in 0..101 {
input.push_str("[[div]]\n");
}
for _ in 0..101 {
input.push_str("[[/div]]\n");
}
crate::preprocess(&log, &mut input);
let tokens = crate::tokenize(&log, &input);
let (tree, warnings) = crate::parse(&log, &tokens).into();
let warning = warnings.get(0).expect("No warnings produced");
assert_eq!(warning.token(), Token::LeftBlock);
assert_eq!(warning.rule(), "block-div");
assert_eq!(warning.span(), 800..802);
assert_eq!(warning.kind(), ParseWarningKind::RecursionDepthExceeded);
let SyntaxTree { elements, .. } = tree;
assert_eq!(elements.len(), 1);
let element = elements.get(0).expect("No elements produced");
let input_cow = Cow::Borrowed(input.as_ref());
assert_eq!(element, &Element::Text(input_cow));
}
#[test]
fn large_payload() {
const ITERATIONS: usize = 50;
let log = crate::build_logger();
let mut input = String::new();
for _ in 0..ITERATIONS {
input.push_str("
[[div]]
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas sed risus sed ex suscipit ultricies ac quis metus.
Mauris facilisis dui quam, in mollis velit ultrices vitae. Nam pretium accumsan arcu eu ultricies. Sed viverra eleifend elit at blandit. Aenean tempor vitae ipsum vitae lacinia.
Proin eu maximus nulla, id imperdiet libero. Duis convallis posuere arcu vitae sodales. Cras porta ac ligula non porttitor.
Proin et sodales arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Mauris eget ante maximus, tincidunt enim nec, dignissim mi.
Quisque tincidunt convallis faucibus. Praesent vel semper dolor, vel tincidunt mi.
In hac habitasse platea dictumst. Vestibulum fermentum libero nec erat porttitor fermentum. Etiam at convallis odio, gravida commodo ipsum. Phasellus consequat nisl vitae ultricies pulvinar. Integer scelerisque eget nisl id fermentum. Pellentesque pretium, enim non molestie rhoncus, dolor diam porta mauris, eu cursus dolor est condimentum nisi. Phasellus tellus est, euismod non accumsan at, congue eget erat.
% ]] ! $ * -- @< _
[[/div]]
");
}
crate::preprocess(&log, &mut input);
let tokens = crate::tokenize(&log, &input);
let (_tree, warnings) = crate::parse(&log, &tokens).into();
assert_eq!(warnings.len(), ITERATIONS * 3);
}