use labstream_time::Dejitterer;
use serde_json::Value as J;
use std::path::{Path, PathBuf};
fn same(got: f64, want: f64) -> bool {
got.to_bits() == want.to_bits()
}
fn sequence_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/sequences")
}
fn load() -> Vec<(String, J)> {
let dir = sequence_dir();
let mut paths: Vec<_> = match std::fs::read_dir(&dir) {
Ok(e) => e
.filter_map(|x| x.ok())
.map(|x| x.path())
.filter(|p| p.extension().is_some_and(|x| x == "json"))
.collect(),
Err(_) => return Vec::new(),
};
paths.sort();
paths
.iter()
.map(|p| {
let j: J = serde_json::from_str(&std::fs::read_to_string(p).unwrap()).unwrap();
(p.file_stem().unwrap().to_string_lossy().into_owned(), j)
})
.collect()
}
fn bits(s: &str) -> f64 {
f64::from_bits(u64::from_str_radix(s, 16).expect("a bit pattern"))
}
struct Sequence {
name: String,
srate: f64,
halftime: f64,
input: Vec<f64>,
expected: Vec<f64>,
final_state: J,
}
fn parse(name: &str, j: &J) -> Sequence {
let arr = j["samples"].as_array().expect("samples");
let mut input = Vec::with_capacity(arr.len());
let mut expected = Vec::with_capacity(arr.len());
for pair in arr {
let p = pair.as_array().expect("a pair");
input.push(bits(p[0].as_str().unwrap()));
expected.push(bits(p[1].as_str().unwrap()));
}
Sequence {
name: name.to_string(),
srate: bits(j["srate_bits"].as_str().unwrap()),
halftime: bits(j["halftime_bits"].as_str().unwrap()),
input,
expected,
final_state: j["final"].clone(),
}
}
#[test]
fn sequences_exist() {
let all = load();
assert!(
!all.is_empty(),
"no sequences in {}. Build oracle/gentime.cpp and run it first.",
sequence_dir().display()
);
let total: usize = all
.iter()
.map(|(_, j)| j["samples"].as_array().unwrap().len())
.sum();
println!("loaded {} sequences, {total} samples", all.len());
assert!(
total >= 10_000,
"the plan asks for sequences of 10,000 samples or more. Found {total}."
);
}
#[test]
fn every_sequence_matches_the_oracle() {
let all = load();
if all.is_empty() {
eprintln!("no sequences; skipping");
return;
}
let mut checked = 0usize;
for (name, j) in &all {
let s = parse(name, j);
let mut d = Dejitterer::new(s.input[0], s.srate, s.halftime);
for (i, t) in s.input.iter().enumerate() {
let got = d.dejitter(*t);
let want = s.expected[i];
if same(got, want) {
continue;
}
let scale = want.abs().max(1.0);
let diff = (got - want).abs() / scale;
let ulps = diff / f64::EPSILON;
panic!(
"{}: sample {i} differs\n input {t:?}\n \
expected {want:?} ({:016x})\n got {got:?} ({:016x})\n \
relative difference {diff:e}, about {ulps:.1} units in the last place\n \
A difference near one unit means the operation order differs. \
Read the float discipline note in the crate documentation.",
s.name,
want.to_bits(),
got.to_bits()
);
}
checked += s.input.len();
}
println!("{checked} samples match across {} sequences", all.len());
}
#[test]
fn the_final_state_matches_the_oracle() {
let all = load();
if all.is_empty() {
return;
}
for (name, j) in &all {
let s = parse(name, j);
let mut d = Dejitterer::new(s.input[0], s.srate, s.halftime);
for t in &s.input {
d.dejitter(*t);
}
let f = &s.final_state;
let cmp = |field: &str, got: f64| {
let want = bits(f[field].as_str().unwrap());
assert!(
same(got, want),
"{}: the field {field} differs\n expected {want:?} ({:016x})\n \
got {got:?} ({:016x})",
s.name,
want.to_bits(),
got.to_bits()
);
};
cmp("w0", d.w0);
cmp("w1", d.w1);
cmp("p00", d.p00);
cmp("p01", d.p01);
cmp("p11", d.p11);
cmp("lam", d.lam);
assert_eq!(d.t0 as u64, f["t0"].as_u64().unwrap(), "{}: t0", s.name);
assert_eq!(
d.samples_since_t0 as u64,
f["samples_since_t0"].as_u64().unwrap(),
"{}: the sample counter",
s.name
);
}
println!("the final state matches for all {} sequences", all.len());
}
#[test]
fn report_worst() {
let all = load();
if all.is_empty() {
return;
}
let mut worst = 0f64;
let mut worst_name = String::new();
let mut exact = 0usize;
let mut total = 0usize;
for (name, j) in &all {
let s = parse(name, j);
let mut d = Dejitterer::new(s.input[0], s.srate, s.halftime);
for (i, t) in s.input.iter().enumerate() {
let got = d.dejitter(*t);
let want = s.expected[i];
total += 1;
if got.to_bits() == want.to_bits() {
exact += 1;
continue;
}
let scale = want.abs().max(1.0);
let diff = (got - want).abs() / scale;
if diff > worst {
worst = diff;
worst_name = format!("{}:{i}", s.name);
}
}
}
println!(
"bit-exact on {exact} of {total} samples ({:.4}%)",
100.0 * exact as f64 / total as f64
);
if worst > 0.0 {
println!("largest relative difference {worst:e} at {worst_name}");
} else {
println!("every sample agrees to the bit");
}
}