Skip to content

Commit a119f89

Browse files
committed
tests: Validate known-failure image tests against current Ruffle output
1 parent 8617bc6 commit a119f89

File tree

3 files changed

+109
-93
lines changed

3 files changed

+109
-93
lines changed
Lines changed: 107 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::environment::RenderInterface;
22
use crate::options::image_comparison::ImageComparison;
33
use crate::util::{read_bytes, write_bytes};
4-
use anyhow::anyhow;
4+
use anyhow::{Context as _, anyhow};
55
use image::{EncodableLayout, ImageBuffer, ImageFormat, Pixel, PixelWithColorType};
66
use ruffle_core::Player;
7+
use std::borrow::Cow;
78
use std::io::Cursor;
89
use std::ops::Deref;
910
use std::sync::{Arc, Mutex};
@@ -12,12 +13,10 @@ use vfs::VfsPath;
1213
pub fn capture_and_compare_image(
1314
base_path: &VfsPath,
1415
player: &Arc<Mutex<Player>>,
15-
name: &String,
16+
name: &str,
1617
image_comparison: ImageComparison,
1718
render_interface: Option<&dyn RenderInterface>,
1819
) -> anyhow::Result<()> {
19-
use anyhow::Context;
20-
2120
let Some(render_interface) = render_interface else {
2221
return Ok(());
2322
};
@@ -30,69 +29,89 @@ pub fn capture_and_compare_image(
3029

3130
let expected_image = {
3231
let path = base_path.join(format!("{name}.expected.png"))?;
33-
if path.is_file()? {
32+
if path.exists()? {
3433
image::load_from_memory(&read_bytes(&path)?)
3534
.context("Failed to open expected image")?
3635
.into_rgba8()
3736
} else if image_comparison.known_failure {
3837
// If we're expecting this to be wrong, don't save a likely wrong image
3938
return Err(anyhow!("Image '{name}': No image to compare to!"));
4039
} else {
41-
write_image(&path, &actual_image, ImageFormat::Png)?;
40+
write_image(&path, &actual_image)?;
4241
return Err(anyhow!(
4342
"Image '{name}': No image to compare to! Saved actual image as expected."
4443
));
4544
}
4645
};
4746

48-
let result = test(
49-
&image_comparison,
50-
name,
51-
actual_image,
52-
expected_image,
53-
base_path,
54-
render_interface.name(),
55-
// If we're expecting failure, spamming files isn't productive.
56-
!image_comparison.known_failure,
57-
);
47+
let diff = test(&image_comparison, name, &actual_image, expected_image)?;
48+
let (failure, failure_name) = match (diff, image_comparison.known_failure) {
49+
(None, false) => return Ok(()),
50+
(None, true) => {
51+
return Err(anyhow!(
52+
"Image '{name}': Check was known to be failing, but now passes successfully. \
53+
Please update the test, and remove `known_failure = true` and `{name}.ruffle.png`!",
54+
));
55+
}
56+
(Some(diff), false) => (diff, Cow::Borrowed(name)),
57+
(Some(_), true) => {
58+
let ruffle_name = format!("{name}.ruffle");
59+
let path = base_path.join(format!("{ruffle_name}.png"))?;
60+
let image = if path.exists()? {
61+
image::load_from_memory(&read_bytes(&path)?)
62+
.context("Failed to open Ruffle-expected image")?
63+
.into_rgba8()
64+
} else {
65+
write_image(&path, &actual_image)?;
66+
return Err(anyhow!(
67+
"Image '{ruffle_name}': No image to compare to! Saved actual image as Ruffle-expected."
68+
));
69+
};
70+
71+
if let Some(diff) = test(&image_comparison, &ruffle_name, &actual_image, image)? {
72+
(diff, Cow::Owned(ruffle_name))
73+
} else {
74+
return Ok(());
75+
}
76+
}
77+
};
5878

59-
match (result, image_comparison.known_failure) {
60-
(result, false) => result,
61-
(Ok(()), true) => Err(anyhow!(
62-
"Image '{name}': Check was known to be failing, but now passes successfully. \
63-
Please update the test and remove `known_failure = true`!",
64-
)),
65-
(Err(_), true) => Ok(()),
79+
// A comparison failed: write difference images and return an error.
80+
let env_name = render_interface.name();
81+
write_image(
82+
&base_path.join(format!("{name}.actual-{env_name}.png"))?,
83+
&actual_image,
84+
)?;
85+
write_image(
86+
&base_path.join(format!("{failure_name}.difference-color-{env_name}.png"))?,
87+
&failure.difference_color()?,
88+
)?;
89+
if let Some(diff_alpha) = failure.difference_alpha()? {
90+
write_image(
91+
&base_path.join(format!("{failure_name}.difference-alpha-{env_name}.png"))?,
92+
&diff_alpha,
93+
)?;
6694
}
95+
96+
Err(anyhow!(
97+
"{failure_name} failed: \
98+
Number of outliers ({}) is bigger than allowed limit of {}. \
99+
Max difference is {}",
100+
failure.outliers,
101+
failure.max_outliers,
102+
failure.max_difference,
103+
))
67104
}
68105

69-
pub fn test(
106+
fn test(
70107
comparison: &ImageComparison,
71108
name: &str,
72-
actual_image: image::RgbaImage,
109+
actual_image: &image::RgbaImage,
73110
expected_image: image::RgbaImage,
74-
test_path: &VfsPath,
75-
environment_name: String,
76-
save_failures: bool,
77-
) -> anyhow::Result<()> {
78-
use anyhow::Context;
79-
80-
let save_actual_image = || {
81-
if save_failures {
82-
write_image(
83-
&test_path.join(format!("{name}.actual-{environment_name}.png"))?,
84-
&actual_image,
85-
ImageFormat::Png,
86-
)
87-
} else {
88-
Ok(())
89-
}
90-
};
91-
111+
) -> anyhow::Result<Option<ImageDiff>> {
92112
if actual_image.width() != expected_image.width()
93113
|| actual_image.height() != expected_image.height()
94114
{
95-
save_actual_image()?;
96115
return Err(anyhow!(
97116
"'{}' image is not the right size. Expected = {}x{}, actual = {}x{}.",
98117
name,
@@ -106,7 +125,7 @@ pub fn test(
106125
let mut is_alpha_different = false;
107126

108127
let difference_data: Vec<u8> =
109-
calculate_difference_data(&actual_image, &expected_image, &mut is_alpha_different);
128+
calculate_difference_data(actual_image, &expected_image, &mut is_alpha_different);
110129

111130
let checks = comparison
112131
.checks()
@@ -138,63 +157,61 @@ pub fn test(
138157
}
139158

140159
// The image failed a check :(
160+
return Ok(Some(ImageDiff {
161+
width: actual_image.width(),
162+
height: actual_image.height(),
163+
difference_data,
164+
outliers,
165+
max_outliers,
166+
max_difference,
167+
is_alpha_different,
168+
}));
169+
}
141170

142-
save_actual_image()?;
171+
if !any_check_executed {
172+
return Err(anyhow!("Image '{name}' failed: No checks executed."));
173+
}
143174

175+
Ok(None)
176+
}
177+
178+
struct ImageDiff {
179+
width: u32,
180+
height: u32,
181+
difference_data: Vec<u8>,
182+
outliers: usize,
183+
max_outliers: usize,
184+
max_difference: u8,
185+
is_alpha_different: bool,
186+
}
187+
188+
impl ImageDiff {
189+
fn difference_color(&self) -> anyhow::Result<image::RgbImage> {
144190
let mut difference_color =
145-
Vec::with_capacity(actual_image.width() as usize * actual_image.height() as usize * 3);
146-
for p in difference_data.chunks_exact(4) {
191+
Vec::with_capacity(self.width as usize * self.height as usize * 3);
192+
for p in self.difference_data.chunks_exact(4) {
147193
difference_color.extend_from_slice(&p[..3]);
148194
}
149195

150-
if save_failures {
151-
let difference_image = image::RgbImage::from_raw(
152-
actual_image.width(),
153-
actual_image.height(),
154-
difference_color,
155-
)
156-
.context("Couldn't create color difference image")?;
157-
write_image(
158-
&test_path.join(format!("{name}.difference-color-{environment_name}.png"))?,
159-
&difference_image,
160-
ImageFormat::Png,
161-
)?;
162-
}
196+
image::RgbImage::from_raw(self.width, self.height, difference_color)
197+
.context("Couldn't create color difference image")
198+
}
163199

164-
if is_alpha_different {
200+
fn difference_alpha(&self) -> anyhow::Result<Option<image::GrayImage>> {
201+
if self.is_alpha_different {
165202
let mut difference_alpha =
166-
Vec::with_capacity(actual_image.width() as usize * actual_image.height() as usize);
167-
for p in difference_data.chunks_exact(4) {
203+
Vec::with_capacity(self.width as usize * self.height as usize);
204+
for p in self.difference_data.chunks_exact(4) {
168205
difference_alpha.push(p[3])
169206
}
170207

171-
if save_failures {
172-
let difference_image = image::GrayImage::from_raw(
173-
actual_image.width(),
174-
actual_image.height(),
175-
difference_alpha,
176-
)
177-
.context("Couldn't create alpha difference image")?;
178-
write_image(
179-
&test_path.join(format!("{name}.difference-alpha-{environment_name}.png"))?,
180-
&difference_image,
181-
ImageFormat::Png,
182-
)?;
183-
}
208+
image::GrayImage::from_raw(self.width, self.height, difference_alpha)
209+
.context("Couldn't create alpha difference image")
210+
.map(Some)
211+
} else {
212+
Ok(None)
184213
}
185-
186-
return Err(anyhow!(
187-
"{check_name} failed: \
188-
Number of outliers ({outliers}) is bigger than allowed limit of {max_outliers}. \
189-
Max difference is {max_difference}",
190-
));
191214
}
192-
193-
if !any_check_executed {
194-
return Err(anyhow!("Image '{name}' failed: No checks executed.",));
195-
}
196-
197-
Ok(())
198215
}
199216

200217
fn calculate_difference_data(
@@ -248,15 +265,14 @@ fn calc_difference(lhs: u8, rhs: u8) -> u8 {
248265
fn write_image<P, Container>(
249266
path: &VfsPath,
250267
image: &ImageBuffer<P, Container>,
251-
format: ImageFormat,
252268
) -> anyhow::Result<()>
253269
where
254270
P: Pixel + PixelWithColorType,
255271
[P::Subpixel]: EncodableLayout,
256272
Container: Deref<Target = [P::Subpixel]>,
257273
{
258274
let mut buffer = vec![];
259-
image.write_to(&mut Cursor::new(&mut buffer), format)?;
275+
image.write_to(&mut Cursor::new(&mut buffer), ImageFormat::Png)?;
260276
write_bytes(path, &buffer)?;
261277
Ok(())
262278
}

tests/tests/swfs/visual/cache_as_bitmap/oversize/swf_10_too_big/test.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ num_frames = 1
22

33
[image_comparisons.output]
44
known_failure = true
5-
tolerance = 0
5+
tolerance = 1 # Should be set to 0 when removing known_failure
66

77
[player_options]
88
with_renderer = { optional = false, sample_count = 4 }

tests/tests/swfs/visual/cache_as_bitmap/oversize/swf_9_too_big/test.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ num_frames = 1
22

33
[image_comparisons.output]
44
known_failure = true
5-
tolerance = 0
5+
tolerance = 1 # Should be set to 0 when removing known_failure
66

77
[player_options]
88
with_renderer = { optional = false, sample_count = 4 }

0 commit comments

Comments
 (0)