mod loader;
mod runner;
use crate::parsing::ParseError;
use crate::tree::SyntaxTree;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::LazyLock;
use std::{env, process};
const SKIP_TESTS: &[&str] = &[];
const ONLY_TESTS: &[&str] = &[];
const UPDATE_TESTS: bool = false;
static TEST_DIRECTORY: LazyLock<PathBuf> = LazyLock::new(|| {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("test");
path
});
#[derive(Debug, Copy, Clone)]
pub enum TestResult {
Pass,
Fail,
Skip,
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct TestStats {
pub passed: u32,
pub failed: u32,
pub skipped: u32,
}
impl TestStats {
#[inline]
pub fn new() -> TestStats {
TestStats::default()
}
pub fn add(&mut self, result: TestResult) {
match result {
TestResult::Pass => self.passed += 1,
TestResult::Fail => self.failed += 1,
TestResult::Skip => self.skipped += 1,
}
}
pub fn print(self) {
let total = self.passed + self.failed + self.skipped;
if self.failed + self.skipped == 0 {
println!("Ran a total of {total} tests, all of which passed.");
} else {
let percent = |value| (value as f32) / (total as f32) * 100.0;
println!("Ran a total of {total} tests. Of these:");
println!("* {} passed ({:.1}%)", self.passed, percent(self.passed));
if self.failed != 0 {
println!("* {} failed ({:.1}%)", self.failed, percent(self.failed));
}
if self.skipped != 0 {
println!("* {} skipped ({:.1}%)", self.skipped, percent(self.skipped));
}
}
}
pub fn exit_code(self) -> i32 {
(self.failed + self.skipped).try_into().ok().unwrap_or(-1)
}
pub fn exit(self) -> ! {
process::exit(self.exit_code());
}
}
#[derive(Debug)]
pub struct Test {
pub name: String,
pub input: String,
pub tree: Option<SyntaxTree<'static>>,
pub errors: Option<Vec<ParseError>>,
pub wikidot_output: Option<String>,
pub html_output: Option<String>,
pub text_output: Option<String>,
}
#[derive(Debug)]
pub struct TestUniverse {
pub tests: BTreeMap<String, Test>,
}
fn env_update_tests() -> bool {
match env::var("FTML_UPDATE_TESTS").ok() {
Some(value) => matches!(value.as_str(), "true" | "1"),
_ => false,
}
}
#[test]
fn ast() {
if UPDATE_TESTS || env_update_tests() {
let tests = TestUniverse::load_permissive(&TEST_DIRECTORY);
println!("=========");
println!(" WARNING ");
println!("=========");
println!();
println!("You are running in UPDATE MODE!");
println!();
println!(
"This will run tests and save whatever results as the new \"expected\" value."
);
println!("Carefully inspect the diff and only save changes that are correct.");
println!();
tests.update(&TEST_DIRECTORY, SKIP_TESTS, ONLY_TESTS);
println!();
println!("Failing test, you must unset update mode to let CI pass");
println!("This is either:");
println!("* The constant UPDATE_TESTS");
println!("* The environment variable FTML_UPDATE_TESTS");
process::exit(-1);
}
let tests = TestUniverse::load(&TEST_DIRECTORY);
#[allow(clippy::const_is_empty)]
if !SKIP_TESTS.is_empty() {
println!("=========");
println!(" WARNING ");
println!("=========");
println!();
println!("Tests matching the following are being SKIPPED:");
for test in SKIP_TESTS {
println!("- {}", test);
}
println!();
}
#[allow(clippy::const_is_empty)]
if !ONLY_TESTS.is_empty() {
println!("=========");
println!(" WARNING ");
println!("=========");
println!();
println!("Only tests matching the following will being run.");
println!("All others are being SKIPPED!");
for test in ONLY_TESTS {
println!("> {}", test);
}
println!();
}
let stats = tests.run(SKIP_TESTS, ONLY_TESTS);
stats.print();
stats.exit();
}