`egui_kittest`: add `failed_pixel_count_threshold` (#7092)
I thought about this - so we have two options here: 1. adding it to `SnapshotOptions` 2. adding it to every function which I do not like as this would be a huge breaking change ## Summary This pull request introduces a new feature to the `SnapshotOptions` struct in the `egui_kittest` crate, allowing users to specify a permissible percentage of pixel differences (`diff_percentage`) before a snapshot comparison is considered a failure. This feature provides more flexibility in handling minor visual discrepancies during snapshot testing. ### Additions to `SnapshotOptions`: * Added a new field `diff_percentage` of type `Option<f64>` to the `SnapshotOptions` struct. This field allows users to define a tolerance for pixel differences, with a default value of `None` (interpreted as 0% tolerance). * Updated the `Default` implementation of `SnapshotOptions` to initialize `diff_percentage` to `None`. ### Integration into snapshot comparison logic: * Updated the `try_image_snapshot_options` function to handle the new `diff_percentage` field. If a `diff_percentage` is specified, the function calculates the percentage of differing pixels and allows the snapshot to pass if the difference is within the specified tolerance. [[1]](diffhunk://#diff-6f481b5866b82a4fe126b7df2e6c9669040c79d1d200d76b87f376de5dec5065R204) [[2]](diffhunk://#diff-6f481b5866b82a4fe126b7df2e6c9669040c79d1d200d76b87f376de5dec5065R294-R301) * Closes <https://github.com/emilk/egui/issues/5683> * [x] I have followed the instructions in the PR template --------- Co-authored-by: lucasmerlin <hi@lucasmerlin.me> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
ba577602a4
commit
77df407f50
|
|
@ -373,7 +373,7 @@ mod tests {
|
|||
use crate::{Demo as _, demo::demo_app_windows::DemoGroups};
|
||||
|
||||
use egui_kittest::kittest::{NodeT as _, Queryable as _};
|
||||
use egui_kittest::{Harness, SnapshotOptions, SnapshotResults};
|
||||
use egui_kittest::{Harness, OsThreshold, SnapshotOptions, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn demos_should_match_snapshot() {
|
||||
|
|
@ -410,9 +410,10 @@ mod tests {
|
|||
harness.run_ok();
|
||||
|
||||
let mut options = SnapshotOptions::default();
|
||||
// The Bézier Curve demo needs a threshold of 2.1 to pass on linux
|
||||
|
||||
if name == "Bézier Curve" {
|
||||
options.threshold = 2.1;
|
||||
// The Bézier Curve demo needs a threshold of 2.1 to pass on linux:
|
||||
options = options.threshold(OsThreshold::new(0.0).linux(2.1));
|
||||
}
|
||||
|
||||
results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options));
|
||||
|
|
|
|||
|
|
@ -13,16 +13,117 @@ pub struct SnapshotOptions {
|
|||
/// wgpu backends).
|
||||
pub threshold: f32,
|
||||
|
||||
/// The number of pixels that can differ before the snapshot is considered a failure.
|
||||
/// Preferably, you should use `threshold` to control the sensitivity of the image comparison.
|
||||
/// As a last resort, you can use this to allow a certain number of pixels to differ.
|
||||
/// If `None`, the default is `0` (meaning no pixels can differ).
|
||||
/// If `Some`, the value can be set per OS
|
||||
pub failed_pixel_count_threshold: usize,
|
||||
|
||||
/// The path where the snapshots will be saved.
|
||||
/// The default is `tests/snapshots`.
|
||||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Helper struct to define the number of pixels that can differ before the snapshot is considered a failure.
|
||||
/// This is useful if you want to set different thresholds for different operating systems.
|
||||
///
|
||||
/// The default values are 0 / 0.0
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```no_run
|
||||
/// use egui_kittest::{OsThreshold, SnapshotOptions};
|
||||
/// let mut harness = egui_kittest::Harness::new_ui(|ui| {
|
||||
/// ui.label("Hi!");
|
||||
/// });
|
||||
/// harness.snapshot_options(
|
||||
/// "os_threshold_example",
|
||||
/// &SnapshotOptions::new()
|
||||
/// .threshold(OsThreshold::new(0.0).windows(10.0))
|
||||
/// .failed_pixel_count_threshold(OsThreshold::new(0).windows(10).macos(53)
|
||||
/// ))
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OsThreshold<T> {
|
||||
pub windows: T,
|
||||
pub macos: T,
|
||||
pub linux: T,
|
||||
pub fallback: T,
|
||||
}
|
||||
|
||||
impl From<usize> for OsThreshold<usize> {
|
||||
fn from(value: usize) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OsThreshold<T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
/// Use the same value for all
|
||||
pub fn new(same: T) -> Self {
|
||||
Self {
|
||||
windows: same,
|
||||
macos: same,
|
||||
linux: same,
|
||||
fallback: same,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the threshold for Windows.
|
||||
#[inline]
|
||||
pub fn windows(mut self, threshold: T) -> Self {
|
||||
self.windows = threshold;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the threshold for macOS.
|
||||
#[inline]
|
||||
pub fn macos(mut self, threshold: T) -> Self {
|
||||
self.macos = threshold;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the threshold for Linux.
|
||||
#[inline]
|
||||
pub fn linux(mut self, threshold: T) -> Self {
|
||||
self.linux = threshold;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the threshold for the current operating system.
|
||||
pub fn threshold(&self) -> T {
|
||||
if cfg!(target_os = "windows") {
|
||||
self.windows
|
||||
} else if cfg!(target_os = "macos") {
|
||||
self.macos
|
||||
} else if cfg!(target_os = "linux") {
|
||||
self.linux
|
||||
} else {
|
||||
self.fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsThreshold<Self>> for usize {
|
||||
fn from(threshold: OsThreshold<Self>) -> Self {
|
||||
threshold.threshold()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsThreshold<Self>> for f32 {
|
||||
fn from(threshold: OsThreshold<Self>) -> Self {
|
||||
threshold.threshold()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SnapshotOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
threshold: 0.6,
|
||||
output_path: PathBuf::from("tests/snapshots"),
|
||||
failed_pixel_count_threshold: 0, // Default is 0, meaning no pixels can differ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,8 +138,8 @@ impl SnapshotOptions {
|
|||
/// The default is `0.6` (which is enough for most egui tests to pass across different
|
||||
/// wgpu backends).
|
||||
#[inline]
|
||||
pub fn threshold(mut self, threshold: f32) -> Self {
|
||||
self.threshold = threshold;
|
||||
pub fn threshold(mut self, threshold: impl Into<f32>) -> Self {
|
||||
self.threshold = threshold.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +150,20 @@ impl SnapshotOptions {
|
|||
self.output_path = output_path.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Change the number of pixels that can differ before the snapshot is considered a failure.
|
||||
///
|
||||
/// Preferably, you should use [`Self::threshold`] to control the sensitivity of the image comparison.
|
||||
/// As a last resort, you can use this to allow a certain number of pixels to differ.
|
||||
#[inline]
|
||||
pub fn failed_pixel_count_threshold(
|
||||
mut self,
|
||||
failed_pixel_count_threshold: impl Into<OsThreshold<usize>>,
|
||||
) -> Self {
|
||||
let failed_pixel_count_threshold = failed_pixel_count_threshold.into().threshold();
|
||||
self.failed_pixel_count_threshold = failed_pixel_count_threshold;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -58,7 +173,7 @@ pub enum SnapshotError {
|
|||
/// Name of the test
|
||||
name: String,
|
||||
|
||||
/// Count of pixels that were different
|
||||
/// Count of pixels that were different (above the per-pixel threshold).
|
||||
diff: i32,
|
||||
|
||||
/// Path where the diff image was saved
|
||||
|
|
@ -201,6 +316,7 @@ pub fn try_image_snapshot_options(
|
|||
let SnapshotOptions {
|
||||
threshold,
|
||||
output_path,
|
||||
failed_pixel_count_threshold,
|
||||
} = options;
|
||||
|
||||
let parent_path = if let Some(parent) = PathBuf::from(name).parent() {
|
||||
|
|
@ -280,19 +396,24 @@ pub fn try_image_snapshot_options(
|
|||
let result =
|
||||
dify::diff::get_results(previous, new.clone(), *threshold, true, None, &None, &None);
|
||||
|
||||
if let Some((diff, result_image)) = result {
|
||||
if let Some((num_wrong_pixels, result_image)) = result {
|
||||
result_image
|
||||
.save(diff_path.clone())
|
||||
.map_err(|err| SnapshotError::WriteSnapshot {
|
||||
path: diff_path.clone(),
|
||||
err,
|
||||
})?;
|
||||
|
||||
if should_update_snapshots() {
|
||||
update_snapshot()
|
||||
} else {
|
||||
if num_wrong_pixels as i64 <= *failed_pixel_count_threshold as i64 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(SnapshotError::Diff {
|
||||
name: name.to_owned(),
|
||||
diff,
|
||||
diff: num_wrong_pixels,
|
||||
diff_path,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue