corpus.rs
⎇
Raw
1//! Differential test of `expand` over calcard's fixture corpus.
2//!
3//! `corpus/oracle.tsv` holds the instances recurring-ical-events computes
4//! (see `corpus/oracle.py`). `corpus/deviations.tsv` overrides it where the
5//! oracle fails or where this crate deliberately differs, with the reason.
6//! On failure the test prints deviation lines for the files that differ.
7//!
8//! `PIMDAV_CORPUS_FULL=<path>` writes every start to that file, in the format
9//! of `oracle.py --full`, for review.
10
11use std::collections::HashMap;
12use std::fmt::Write as _;
13use std::path::PathBuf;
14
15use chrono::{DateTime, Utc};
16use pimdav::calcard::icalendar::ICalendarComponentType;
17use pimdav::calcard::{Entry, Parser};
18use pimdav::expand::expand;
19use pimdav::zone::Zone;
20
21/// Keep equal to the pinned calcard version in Cargo.toml.
22const CALCARD: &str = "calcard-0.3.14";
23
24fn fnv(data: &[u8]) -> String {
25 let mut h: u64 = 0xcbf29ce484222325;
26 for b in data {
27 h = (h ^ u64::from(*b)).wrapping_mul(0x100000001b3);
28 }
29 format!("{h:016x}")
30}
31
32fn fixtures() -> PathBuf {
33 let home = std::env::var_os("CARGO_HOME")
34 .map(PathBuf::from)
35 .unwrap_or_else(|| PathBuf::from(std::env::var_os("HOME").unwrap()).join(".cargo"));
36 std::fs::read_dir(home.join("registry/src"))
37 .expect("no cargo registry")
38 .map(|e| e.unwrap().path().join(CALCARD).join("resources/ical"))
39 .find(|p| p.is_dir())
40 .expect("calcard sources not in the cargo registry")
41}
42
43/// `name -> (columns...)` of a tab-separated file.
44fn table(name: &str) -> HashMap<String, Vec<String>> {
45 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
46 .join("tests/corpus")
47 .join(name);
48 std::fs::read_to_string(path)
49 .unwrap()
50 .lines()
51 .filter(|l| !l.is_empty() && !l.starts_with('#'))
52 .map(|l| {
53 let mut cols = l.split('\t').map(str::to_string);
54 (cols.next().unwrap(), cols.collect())
55 })
56 .collect()
57}
58
59fn starts(input: &str) -> Vec<i64> {
60 let lo = DateTime::<Utc>::from_timestamp(0, 0).unwrap();
61 let hi = DateTime::<Utc>::from_timestamp(2_208_988_800, 0).unwrap();
62 let mut parser = Parser::new(input);
63 let mut out = Vec::new();
64 loop {
65 match parser.entry() {
66 Entry::ICalendar(cal) => {
67 let expansion = expand(&cal, lo..hi, Zone::Utc);
68 out.extend(
69 expansion
70 .instances
71 .iter()
72 .filter(|i| {
73 cal.components[i.component].component_type
74 == ICalendarComponentType::VEvent
75 && i.start >= lo
76 && i.start < hi
77 })
78 .map(|i| i.start.timestamp()),
79 );
80 }
81 Entry::Eof => break,
82 _ => {}
83 }
84 }
85 out.sort_unstable();
86 out
87}
88
89#[test]
90fn corpus_matches_oracle() {
91 let dir = fixtures();
92 let oracle = table("oracle.tsv");
93 let deviations = table("deviations.tsv");
94 let mut names: Vec<_> = std::fs::read_dir(&dir)
95 .unwrap()
96 .map(|e| e.unwrap().file_name().into_string().unwrap())
97 .filter(|n| n.ends_with(".ics"))
98 .collect();
99 names.sort();
100 assert_eq!(
101 names.len(),
102 oracle.len(),
103 "fixture set changed, rerun oracle.py"
104 );
105
106 let mut full = String::new();
107 let mut failures = String::new();
108 for name in &names {
109 let raw = std::fs::read(dir.join(name)).unwrap();
110 let o = &oracle[name];
111 assert_eq!(o[0], fnv(&raw), "{name} changed, rerun oracle.py");
112 let input = String::from_utf8(raw.clone())
113 .unwrap_or_else(|_| raw.iter().map(|&b| b as char).collect());
114 let starts = starts(&input);
115 let list = starts
116 .iter()
117 .map(i64::to_string)
118 .collect::<Vec<_>>()
119 .join(",");
120 let (count, hash) = (starts.len(), fnv(list.as_bytes()));
121 writeln!(full, "{name}\t{}\tok\t{count}\t{list}", o[0]).unwrap();
122
123 let expected = match deviations.get(name) {
124 Some(d) => Some((d[0].as_str(), d[1].as_str())),
125 None if o[1] == "ok" => Some((o[2].as_str(), o[3].as_str())),
126 None => None,
127 };
128 if expected != Some((count.to_string().as_str(), hash.as_str())) {
129 let why = deviations.get(name).map_or("?", |d| d[2].as_str());
130 writeln!(failures, "{name}\t{count}\t{hash}\t{why}").unwrap();
131 }
132 }
133 if let Some(path) = std::env::var_os("PIMDAV_CORPUS_FULL") {
134 std::fs::write(path, full).unwrap();
135 }
136 assert!(failures.is_empty(), "differs from expectation:\n{failures}");
137}
138