fenestra_shell/
testing.rs1use std::path::Path;
15
16use image::RgbaImage;
17
18const CHANNEL_TOLERANCE: u8 = 3;
20const MAX_DIFFERING_FRACTION: f64 = 0.002;
23
24pub const UPDATE_ENV: &str = "FENESTRA_UPDATE_SNAPSHOTS";
26
27pub const BUDGET_ENV: &str = "FENESTRA_SNAPSHOT_BUDGET";
30
31fn differing_budget() -> f64 {
32 std::env::var(BUDGET_ENV)
33 .ok()
34 .and_then(|v| v.parse::<f64>().ok())
35 .filter(|b| b.is_finite() && (0.0..=1.0).contains(b))
36 .unwrap_or(MAX_DIFFERING_FRACTION)
37}
38
39pub fn assert_png_snapshot(dir: impl AsRef<Path>, name: &str, actual: &RgbaImage) {
45 let dir = dir.as_ref();
46 let golden_path = dir.join(format!("{name}.png"));
47 let update = std::env::var(UPDATE_ENV).is_ok_and(|v| v == "1");
48
49 if update {
50 std::fs::create_dir_all(dir).expect("create snapshot dir");
51 actual.save(&golden_path).expect("write golden");
52 return;
53 }
54
55 let golden = match image::open(&golden_path) {
56 Ok(img) => img.into_rgba8(),
57 Err(_) => panic!(
58 "missing golden {}; run with {UPDATE_ENV}=1 to create it",
59 golden_path.display()
60 ),
61 };
62
63 if golden.dimensions() != actual.dimensions() {
64 let actual_path = dir.join(format!("{name}.actual.png"));
65 actual.save(&actual_path).ok();
66 panic!(
67 "golden {} is {:?} but actual is {:?} (actual written to {})",
68 golden_path.display(),
69 golden.dimensions(),
70 actual.dimensions(),
71 actual_path.display()
72 );
73 }
74
75 let total = u64::from(golden.width()) * u64::from(golden.height());
76 let mut differing: u64 = 0;
77 let mut max_delta: u8 = 0;
78 for (g, a) in golden.pixels().zip(actual.pixels()) {
79 let mut pixel_exceeds = false;
80 for c in 0..4 {
81 let delta = g.0[c].abs_diff(a.0[c]);
82 max_delta = max_delta.max(delta);
83 if delta > CHANNEL_TOLERANCE {
84 pixel_exceeds = true;
85 }
86 }
87 if pixel_exceeds {
88 differing += 1;
89 }
90 }
91
92 #[expect(clippy::cast_precision_loss, reason = "image pixel counts are small")]
93 let fraction = differing as f64 / total as f64;
94 if fraction > differing_budget() {
95 let actual_path = dir.join(format!("{name}.actual.png"));
96 actual.save(&actual_path).ok();
97 panic!(
98 "snapshot {name}: {differing}/{total} pixels ({:.3}%) exceed channel tolerance \
99 {CHANNEL_TOLERANCE} (max delta {max_delta}); actual written to {} — \
100 run with {UPDATE_ENV}=1 to update",
101 fraction * 100.0,
102 actual_path.display()
103 );
104 }
105}