Skip to content

Commit 52be151

Browse files
committed
tests: add JSON diff reports for sparse test snapshot failures
1 parent 2d4526c commit 52be151

File tree

4 files changed

+98
-5
lines changed

4 files changed

+98
-5
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,5 @@ console_log = "1.0"
154154
proc-macro2 = "1.0.95"
155155
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
156156
quote = "1.0.40"
157+
serde = { version = "1.0", default-features = false }
158+
serde_json = "1.0"

sparse_strips/vello_sparse_tests/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ vello_dev_macros = { workspace = true }
2525
bytemuck = { workspace = true }
2626
oxipng = { workspace = true, features = ["freestanding", "parallel"] }
2727
image = { workspace = true, features = ["png"] }
28+
serde = { workspace = true, features = ["derive"] }
29+
serde_json = { workspace = true }
2830
skrifa = { workspace = true }
2931
smallvec = { workspace = true }
3032

sparse_strips/vello_sparse_tests/tests/util.rs

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ use vello_cpu::{Level, RenderMode};
2121
#[cfg(not(target_arch = "wasm32"))]
2222
use std::path::PathBuf;
2323

24+
/// Aggregate diff report with statistics and individual pixel differences.
25+
#[cfg(not(target_arch = "wasm32"))]
26+
#[derive(Debug, serde::Serialize)]
27+
pub(crate) struct DiffReport {
28+
/// Total number of pixels that differ.
29+
pub pixel_count: usize,
30+
/// Maximum absolute difference per channel [R, G, B, A].
31+
pub max_difference: [i16; 4],
32+
/// Individual pixel differences.
33+
pub pixels: Vec<PixelDiff>,
34+
}
35+
36+
/// Represents a single pixel difference between reference and actual images.
37+
#[derive(Debug, serde::Serialize)]
38+
pub(crate) struct PixelDiff {
39+
/// The x coordinate of the differing pixel.
40+
pub x: u32,
41+
/// The y coordinate of the differing pixel.
42+
pub y: u32,
43+
/// The RGBA values from the reference image.
44+
pub reference: [u8; 4],
45+
/// The RGBA values from the actual image.
46+
pub actual: [u8; 4],
47+
/// Per-channel difference (actual - reference) as signed values.
48+
pub difference: [i16; 4],
49+
}
50+
2451
#[cfg(not(target_arch = "wasm32"))]
2552
static REFS_PATH: std::sync::LazyLock<PathBuf> = std::sync::LazyLock::new(|| {
2653
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../vello_sparse_tests/snapshots")
@@ -321,9 +348,9 @@ pub(crate) fn check_ref(
321348
.into_rgba8();
322349
let actual = load_from_memory(&encoded_image).unwrap().into_rgba8();
323350

324-
let diff_image = get_diff(&ref_image, &actual, threshold, diff_pixels);
351+
let diff_result = get_diff(&ref_image, &actual, threshold, diff_pixels);
325352

326-
if let Some(diff_image) = diff_image {
353+
if let Some((diff_image, diff_data)) = diff_result {
327354
if should_replace() && is_reference {
328355
write_ref_image();
329356
panic!("test was replaced");
@@ -338,6 +365,22 @@ pub(crate) fn check_ref(
338365
.save_with_format(&diff_path, image::ImageFormat::Png)
339366
.unwrap();
340367

368+
// Save diff data as JSON
369+
let json_path = DIFFS_PATH.join(format!("{specific_name}.json"));
370+
let max_difference: [i16; 4] = diff_data.iter().fold([0; 4], |mut max, p| {
371+
for (m, d) in max.iter_mut().zip(&p.difference) {
372+
*m = (*m).max(d.abs());
373+
}
374+
max
375+
});
376+
let report = DiffReport {
377+
pixel_count: diff_data.len(),
378+
max_difference,
379+
pixels: diff_data,
380+
};
381+
let json_data = serde_json::to_string_pretty(&report).unwrap();
382+
std::fs::write(&json_path, json_data).unwrap();
383+
341384
panic!("test didnt match reference image");
342385
}
343386
}
@@ -365,7 +408,7 @@ pub(crate) fn check_ref(
365408
let ref_image = load_from_memory(ref_data).unwrap().into_rgba8();
366409

367410
let diff_image = get_diff(&ref_image, &actual, threshold, diff_pixels);
368-
if let Some(ref img) = diff_image {
411+
if let Some((ref img, _)) = diff_image {
369412
append_diff_image_to_browser_document(specific_name, img);
370413
panic!("test didn't match reference image. Scroll to bottom of browser to view diff.");
371414
}
@@ -445,11 +488,12 @@ fn get_diff(
445488
actual_image: &RgbaImage,
446489
threshold: u8,
447490
diff_pixels: u16,
448-
) -> Option<RgbaImage> {
491+
) -> Option<(RgbaImage, Vec<PixelDiff>)> {
449492
let width = max(expected_image.width(), actual_image.width());
450493
let height = max(expected_image.height(), actual_image.height());
451494

452495
let mut diff_image = RgbaImage::new(width * 3, height);
496+
let mut diff_data = Vec::new();
453497

454498
let mut pixel_diff = 0;
455499

@@ -465,6 +509,18 @@ fn get_diff(
465509
if is_pix_diff(expected, actual, threshold) {
466510
pixel_diff += 1;
467511
diff_image.put_pixel(x + width, y, Rgba([255, 0, 0, 255]));
512+
diff_data.push(PixelDiff {
513+
x,
514+
y,
515+
reference: expected.0,
516+
actual: actual.0,
517+
difference: [
518+
i16::from(actual.0[0]) - i16::from(expected.0[0]),
519+
i16::from(actual.0[1]) - i16::from(expected.0[1]),
520+
i16::from(actual.0[2]) - i16::from(expected.0[2]),
521+
i16::from(actual.0[3]) - i16::from(expected.0[3]),
522+
],
523+
});
468524
} else {
469525
diff_image.put_pixel(x + width, y, Rgba([0, 0, 0, 255]));
470526
}
@@ -473,23 +529,54 @@ fn get_diff(
473529
pixel_diff += 1;
474530
diff_image.put_pixel(x + 2 * width, y, *actual);
475531
diff_image.put_pixel(x + width, y, Rgba([255, 0, 0, 255]));
532+
diff_data.push(PixelDiff {
533+
x,
534+
y,
535+
reference: [0, 0, 0, 0],
536+
actual: actual.0,
537+
difference: [
538+
i16::from(actual.0[0]),
539+
i16::from(actual.0[1]),
540+
i16::from(actual.0[2]),
541+
i16::from(actual.0[3]),
542+
],
543+
});
476544
}
477545
(None, Some(expected)) => {
478546
pixel_diff += 1;
479547
diff_image.put_pixel(x, y, *expected);
480548
diff_image.put_pixel(x + width, y, Rgba([255, 0, 0, 255]));
549+
diff_data.push(PixelDiff {
550+
x,
551+
y,
552+
reference: expected.0,
553+
actual: [0, 0, 0, 0],
554+
difference: [
555+
-i16::from(expected.0[0]),
556+
-i16::from(expected.0[1]),
557+
-i16::from(expected.0[2]),
558+
-i16::from(expected.0[3]),
559+
],
560+
});
481561
}
482562
_ => {
483563
pixel_diff += 1;
484564
diff_image.put_pixel(x, y, Rgba([255, 0, 0, 255]));
485565
diff_image.put_pixel(x + width, y, Rgba([255, 0, 0, 255]));
566+
diff_data.push(PixelDiff {
567+
x,
568+
y,
569+
reference: [0, 0, 0, 0],
570+
actual: [0, 0, 0, 0],
571+
difference: [0, 0, 0, 0],
572+
});
486573
}
487574
}
488575
}
489576
}
490577

491578
if pixel_diff > diff_pixels {
492-
Some(diff_image)
579+
Some((diff_image, diff_data))
493580
} else {
494581
None
495582
}

0 commit comments

Comments
 (0)