Skip to main content

pixelflow_test_support/
assertions.rs

1//! Golden-frame assertion helpers.
2
3use pixelflow_core::Frame;
4
5/// Absolute per-sample tolerance for deterministic golden-frame comparisons.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct GoldenTolerance {
8    /// Maximum allowed absolute difference for `u8` samples.
9    pub u8_abs: u8,
10    /// Maximum allowed absolute difference for `u16` samples.
11    pub u16_abs: u16,
12    /// Maximum allowed absolute difference for `f32` samples.
13    pub f32_abs: f32,
14}
15
16/// Exact comparison tolerance for structural filters such as crop and trim.
17pub const EXACT_GOLDEN_TOLERANCE: GoldenTolerance = GoldenTolerance {
18    u8_abs: 0,
19    u16_abs: 0,
20    f32_abs: 0.0,
21};
22
23/// Asserts one `u8` plane matches expected rows within tolerance.
24pub fn assert_plane_u8_near(
25    frame: &Frame,
26    plane_index: usize,
27    expected: &[&[u8]],
28    tolerance: GoldenTolerance,
29) {
30    let plane = frame
31        .plane::<u8>(plane_index)
32        .expect("frame plane should have u8 storage");
33    assert_eq!(plane.height(), expected.len(), "plane height mismatch");
34
35    for (y, expected_row) in expected.iter().enumerate() {
36        let row = plane.row(y).expect("row should exist");
37        assert_eq!(
38            row.len(),
39            expected_row.len(),
40            "plane row width mismatch at row {y}"
41        );
42
43        for (x, (&actual, &want)) in row.iter().zip(expected_row.iter()).enumerate() {
44            let delta = actual.abs_diff(want);
45            assert!(
46                delta <= tolerance.u8_abs,
47                "sample mismatch at plane {plane_index} ({x}, {y}): expected {want}, got {actual}, tolerance {}",
48                tolerance.u8_abs
49            );
50        }
51    }
52}
53
54/// Asserts one `u16` plane matches expected rows within tolerance.
55pub fn assert_plane_u16_near(
56    frame: &Frame,
57    plane_index: usize,
58    expected: &[&[u16]],
59    tolerance: GoldenTolerance,
60) {
61    let plane = frame
62        .plane::<u16>(plane_index)
63        .expect("frame plane should have u16 storage");
64    assert_eq!(plane.height(), expected.len(), "plane height mismatch");
65
66    for (y, expected_row) in expected.iter().enumerate() {
67        let row = plane.row(y).expect("row should exist");
68        assert_eq!(
69            row.len(),
70            expected_row.len(),
71            "plane row width mismatch at row {y}"
72        );
73
74        for (x, (&actual, &want)) in row.iter().zip(expected_row.iter()).enumerate() {
75            let delta = actual.abs_diff(want);
76            assert!(
77                delta <= tolerance.u16_abs,
78                "sample mismatch at plane {plane_index} ({x}, {y}): expected {want}, got {actual}, tolerance {}",
79                tolerance.u16_abs
80            );
81        }
82    }
83}
84
85/// Asserts one `f32` plane matches expected rows within tolerance.
86pub fn assert_plane_f32_near(
87    frame: &Frame,
88    plane_index: usize,
89    expected: &[&[f32]],
90    tolerance: GoldenTolerance,
91) {
92    let plane = frame
93        .plane::<f32>(plane_index)
94        .expect("frame plane should have f32 storage");
95    assert_eq!(plane.height(), expected.len(), "plane height mismatch");
96
97    for (y, expected_row) in expected.iter().enumerate() {
98        let row = plane.row(y).expect("row should exist");
99        assert_eq!(
100            row.len(),
101            expected_row.len(),
102            "plane row width mismatch at row {y}"
103        );
104
105        for (x, (&actual, &want)) in row.iter().zip(expected_row.iter()).enumerate() {
106            let delta = (actual - want).abs();
107            assert!(
108                delta <= tolerance.f32_abs,
109                "sample mismatch at plane {plane_index} ({x}, {y}): expected {want}, got {actual}, tolerance {}",
110                tolerance.f32_abs
111            );
112        }
113    }
114}
115
116/// Asserts all `u8` planes match expected rows within tolerance.
117pub fn assert_frame_u8_near(
118    frame: &Frame,
119    expected_planes: &[&[&[u8]]],
120    tolerance: GoldenTolerance,
121) {
122    assert_eq!(
123        frame.format().planes().len(),
124        expected_planes.len(),
125        "plane count mismatch"
126    );
127    for (plane_index, expected) in expected_planes.iter().enumerate() {
128        assert_plane_u8_near(frame, plane_index, expected, tolerance);
129    }
130}
131
132/// Asserts all `u16` planes match expected rows within tolerance.
133pub fn assert_frame_u16_near(
134    frame: &Frame,
135    expected_planes: &[&[&[u16]]],
136    tolerance: GoldenTolerance,
137) {
138    assert_eq!(
139        frame.format().planes().len(),
140        expected_planes.len(),
141        "plane count mismatch"
142    );
143    for (plane_index, expected) in expected_planes.iter().enumerate() {
144        assert_plane_u16_near(frame, plane_index, expected, tolerance);
145    }
146}
147
148/// Asserts all `f32` planes match expected rows within tolerance.
149pub fn assert_frame_f32_near(
150    frame: &Frame,
151    expected_planes: &[&[&[f32]]],
152    tolerance: GoldenTolerance,
153) {
154    assert_eq!(
155        frame.format().planes().len(),
156        expected_planes.len(),
157        "plane count mismatch"
158    );
159    for (plane_index, expected) in expected_planes.iter().enumerate() {
160        assert_plane_f32_near(frame, plane_index, expected, tolerance);
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use crate::{synthetic_f32_frame, synthetic_u8_frame, synthetic_u16_frame};
167
168    use super::{
169        EXACT_GOLDEN_TOLERANCE, GoldenTolerance, assert_frame_f32_near, assert_frame_u8_near,
170        assert_frame_u16_near, assert_plane_u8_near,
171    };
172
173    #[test]
174    fn exact_golden_tolerance_is_zero_for_all_storage_types() {
175        assert_eq!(EXACT_GOLDEN_TOLERANCE.u8_abs, 0);
176        assert_eq!(EXACT_GOLDEN_TOLERANCE.u16_abs, 0);
177        assert_eq!(EXACT_GOLDEN_TOLERANCE.f32_abs, 0.0);
178    }
179
180    #[test]
181    fn plane_u8_assertion_accepts_declared_tolerance() {
182        let frame = synthetic_u8_frame("gray8", 2, 1, |_plane, x, _y| {
183            u8::try_from(x).expect("fixture sample fits u8")
184        })
185        .expect("synthetic frame should build");
186
187        assert_plane_u8_near(
188            &frame,
189            0,
190            &[&[1, 2]],
191            GoldenTolerance {
192                u8_abs: 1,
193                u16_abs: 0,
194                f32_abs: 0.0,
195            },
196        );
197    }
198
199    #[test]
200    fn frame_u8_assertion_checks_all_planes() {
201        let frame = synthetic_u8_frame("yuv444p8", 2, 1, |plane, x, _y| match plane {
202            0 => match x {
203                0 => 1,
204                1 => 2,
205                _ => unreachable!("fixture width is 2"),
206            },
207            1 => match x {
208                0 => 3,
209                1 => 4,
210                _ => unreachable!("fixture width is 2"),
211            },
212            2 => match x {
213                0 => 5,
214                1 => 6,
215                _ => unreachable!("fixture width is 2"),
216            },
217            _ => unreachable!("yuv444p8 has exactly 3 planes"),
218        })
219        .expect("synthetic frame should build");
220
221        let p0 = [&[1_u8, 2][..]];
222        let p1 = [&[3_u8, 4][..]];
223        let p2 = [&[5_u8, 6][..]];
224        let expected = [&p0[..], &p1[..], &p2[..]];
225
226        assert_frame_u8_near(&frame, &expected, EXACT_GOLDEN_TOLERANCE);
227    }
228
229    #[test]
230    fn frame_u16_assertion_checks_all_planes() {
231        let frame = synthetic_u16_frame("gray10", 2, 1, |_plane, x, _y| match x {
232            0 => 100,
233            1 => 101,
234            _ => unreachable!("fixture width is 2"),
235        })
236        .expect("synthetic frame should build");
237        let p0 = [&[100_u16, 101][..]];
238        let expected = [&p0[..]];
239
240        assert_frame_u16_near(&frame, &expected, EXACT_GOLDEN_TOLERANCE);
241    }
242
243    #[test]
244    fn frame_f32_assertion_checks_all_planes() {
245        let frame = synthetic_f32_frame("grayf32", 2, 1, |_plane, x, _y| match x {
246            0 => 0.25,
247            1 => 1.25,
248            _ => unreachable!("fixture width is 2"),
249        })
250        .expect("synthetic frame should build");
251        let p0 = [&[0.25_f32, 1.25][..]];
252        let expected = [&p0[..]];
253
254        assert_frame_f32_near(&frame, &expected, EXACT_GOLDEN_TOLERANCE);
255    }
256}