//! Golden tests against the output of the former Python driver //! (`hpcs6500.py`), generated from real USB captures. use hpcs6500::format::{csv_header, csv_row, format_quick, format_reading, pcap_report}; use hpcs6500::{Reading, pcap, pyfmt}; use serde_json::Value; use std::path::{Path, PathBuf}; fn dir(sub: &str) -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests") .join(sub) } fn fixture(name: &str) -> Reading { let meas = std::fs::read(dir("fixtures").join(format!("{name}_meas.bin"))).unwrap(); let elec = std::fs::read(dir("fixtures").join(format!("{name}_elec.bin"))).ok(); Reading::parse(&meas, elec.as_deref()).unwrap() } fn golden() -> Vec { serde_json::from_str(&std::fs::read_to_string(dir("golden").join("readings.json")).unwrap()) .unwrap() } fn text(name: &str) -> String { std::fs::read_to_string(dir("golden").join(name)) .unwrap() .replace("\r\n", "\n") } /// Python values in the golden file: floats as {"f": repr}, lists, ints, strings. fn same(rust: &Value, py: &Value, path: &str) { match py { Value::Object(o) if o.contains_key("f") => { let want = o["f"].as_str().unwrap(); let got = pyfmt::repr( rust.as_f64() .unwrap_or_else(|| panic!("{path}: not a number: {rust}")), ); assert_eq!(got, want, "{path}"); } Value::Array(a) => { let r = rust .as_array() .unwrap_or_else(|| panic!("{path}: not a list")); assert_eq!(r.len(), a.len(), "{path}: length"); for (i, (x, y)) in r.iter().zip(a).enumerate() { same(x, y, &format!("{path}[{i}]")); } } other => assert_eq!(rust, other, "{path}"), } } #[test] fn parsed_blocks_match_python_exactly() { for g in golden() { let name = g["name"].as_str().unwrap(); let r = fixture(name); let v = serde_json::to_value(&r).unwrap(); let keys: Vec<&str> = v.as_object().unwrap().keys().map(|k| k.as_str()).collect(); let want: Vec<&str> = g["keys"] .as_array() .unwrap() .iter() .map(|k| k.as_str().unwrap()) .collect(); assert_eq!(keys, want, "{name}: key order"); for k in want { same(&v[k], &g["values"][k], &format!("{name}.{k}")); } assert_eq!( r.electrical.as_ref().unwrap().harmonics.is_some(), g["harmonics"].as_bool().unwrap(), "{name}" ); } } #[test] fn report_text_matches_python() { for g in golden() { let name = g["name"].as_str().unwrap(); let r = fixture(name); assert_eq!( format_reading(&r, false), text(&format!("format_{name}.txt")), "{name}" ); assert_eq!( format_reading(&r, true), text(&format!("format_{name}_spectrum.txt")), "{name} spectrum" ); let n: usize = name[1..].parse().unwrap(); assert_eq!( format!("{}\n{}", format_quick(&r, Some(n)), format_quick(&r, None)), text(&format!("quick_{name}.txt")) ); } } #[test] fn csv_matches_python() { for g in golden() { let name = g["name"].as_str().unwrap(); let want = std::fs::read_to_string(dir("golden").join(format!("csv_{name}.txt"))).unwrap(); assert_eq!(csv_header() + &csv_row("T", &fixture(name)), want, "{name}"); } } #[test] fn float_formatting_matches_python() { let cases: Vec = serde_json::from_str(&std::fs::read_to_string(dir("golden").join("pyfmt.json")).unwrap()) .unwrap(); assert!(cases.len() > 1000); for c in &cases { let x = f64::from_bits(c["bits"].as_u64().unwrap()); assert_eq!(pyfmt::repr(x), c["repr"].as_str().unwrap(), "repr {x:e}"); assert_eq!(pyfmt::g(x, 6), c["g6"].as_str().unwrap(), "g6 {x:e}"); for (k, p) in [("f2", 2), ("f1", 1), ("f0", 0)] { if let Some(w) = c[k].as_str() { assert_eq!(pyfmt::fixed(x, p), w, "{k} {x:e}"); } } if let Some(w) = c["round1"].as_str() { assert_eq!(pyfmt::repr(pyfmt::round(x, 1)), w, "round1 {x:e}"); } } } #[test] fn pcap_round_trip_and_report() { // a capture with two readings, written in memory let (a, b) = (fixture("r01"), fixture("r02")); let block = |n: &str, k: &str| std::fs::read(dir("fixtures").join(format!("{n}_{k}.bin"))).unwrap(); let file = pcap::write(&[ (pcap::Dir::Tx, vec![0x8C, 0x13]), (pcap::Dir::Rx, vec![0x8C, 0x13, 0x0F, 0x40]), (pcap::Dir::Rx, block("r01", "meas")), (pcap::Dir::Rx, block("r01", "elec")), (pcap::Dir::Rx, block("r02", "meas")), (pcap::Dir::Rx, block("r02", "elec")), ]); let msgs = pcap::messages(&file).unwrap(); assert_eq!(msgs.len(), 6); assert_eq!(msgs[0].dir, pcap::Dir::Tx); assert_eq!(pcap::readings(&msgs), vec![a, b]); let report = pcap_report(&msgs, true, false); assert_eq!(report.lines().count(), 2); assert!(pcap::messages(&[]).is_err()); assert!(pcap::messages(&[0u8; 30]).is_err()); } /// The `--parse` reports of real captures. The captures are not in git /// (`captures/` is ignored); the test runs where they are. #[test] fn parse_reports_of_real_captures() { let caps = Path::new(env!("CARGO_MANIFEST_DIR")).join("captures"); let mut checked = 0; for cap in [ "usb_capture_20260204_123655_hub1", "usb_capture_20260204_134301_hub1", ] { let path = caps.join(format!("{cap}.pcap")); if !path.exists() { eprintln!("skipped: {} not here", path.display()); continue; } let msgs = pcap::read_messages(&path).unwrap(); assert_eq!( pcap_report(&msgs, false, false), text(&format!("parse_{cap}.txt")) ); assert_eq!( pcap_report(&msgs, true, false), text(&format!("parse_{cap}_quick.txt")) ); assert_eq!( pcap_report(&msgs, false, true), text(&format!("parse_{cap}_harmonics.txt")) ); checked += 1; } eprintln!("{checked} capture(s) checked"); }