#![allow(dead_code)]
use std::{fs, path::PathBuf};
use ical::tree::cst::IcalCst;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Identical,
Normalised,
Refused,
Empty,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Tally {
pub identical: usize,
pub normalised: usize,
pub refused: usize,
pub empty: usize,
}
pub fn each_fixture(corpus: &str, expected: usize, mut check: impl FnMut(&str, &[u8])) {
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/corpus")
.join(corpus);
let mut total = 0;
for entry in fs::read_dir(&dir).expect("read corpus dir") {
let path = entry.expect("dir entry").path();
if path.extension().and_then(|e| e.to_str()) != Some("ics") {
continue;
}
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let input = fs::read(&path).expect("read fixture");
total += 1;
check(&name, &input);
}
assert_eq!(
total, expected,
"expected {expected} fixtures in {corpus}, found {total}"
);
}
pub fn classify_corpus(corpus: &str, expected: usize) -> Tally {
let mut tally = Tally::default();
each_fixture(corpus, expected, |name, input| {
match classify(name, input) {
Outcome::Identical => tally.identical += 1,
Outcome::Normalised => tally.normalised += 1,
Outcome::Refused => tally.refused += 1,
Outcome::Empty => tally.empty += 1,
};
});
tally
}
pub fn classify(name: &str, input: &[u8]) -> Outcome {
if input.iter().all(|byte| byte.is_ascii_whitespace()) {
return Outcome::Empty;
}
let Some(output) = parse_whole(input) else {
return Outcome::Refused;
};
let reparsed = parse_whole(&output).unwrap_or_else(|| panic!("reparse {name}"));
assert_eq!(reparsed, output, "not a serialize fixpoint: {name}");
for cst in calendars(input).expect("already parsed") {
let encoded = IcalCst::from(cst.decode());
let redecoded = encoded.decode();
assert_eq!(
IcalCst::from(redecoded).to_bytes(),
encoded.to_bytes(),
"decode is not stable: {name}"
);
}
if output == input {
Outcome::Identical
} else {
Outcome::Normalised
}
}
fn calendars(input: &[u8]) -> Option<Vec<IcalCst<'_>>> {
let mut all = Vec::new();
for result in IcalCst::parse_many(input) {
match result {
Ok(cst) => all.push(cst),
Err(_) if all.is_empty() => return IcalCst::parse(input).ok().map(|cst| vec![cst]),
Err(_) => return None,
}
}
Some(all)
}
fn parse_whole(input: &[u8]) -> Option<Vec<u8>> {
let mut out = Vec::new();
for cst in calendars(input)? {
out.extend_from_slice(&cst.to_bytes());
}
Some(out)
}