Add `Harness::debug_open_snapshot` helper (#7590)

Adds a helper to quickly see whats going on in a kittest test.

Not all test have snapshots, but when debugging tests it might still be
useful to see whats actually going on, so this adds a helper fn that
renders a snapshot image to a temporary file and opens it with the
default image viewer:



https://github.com/user-attachments/assets/08785850-0a12-4572-b9b5-cea36951081c
This commit is contained in:
Lucas Meurer 2025-10-07 14:39:22 +02:00 committed by GitHub
parent ab461f4115
commit d83f4500a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 11 deletions

View File

@ -1422,7 +1422,9 @@ dependencies = [
"egui_extras", "egui_extras",
"image", "image",
"kittest", "kittest",
"open",
"pollster", "pollster",
"tempfile",
"wgpu", "wgpu",
] ]
@ -2318,6 +2320,15 @@ dependencies = [
"hashbrown 0.15.2", "hashbrown 0.15.2",
] ]
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.13" version = "0.4.13"
@ -2329,6 +2340,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -3065,6 +3086,17 @@ version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "open"
version = "5.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -3143,6 +3175,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -4064,15 +4102,15 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.13.0" version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [ dependencies = [
"cfg-if",
"fastrand", "fastrand",
"getrandom 0.3.1",
"once_cell", "once_cell",
"rustix 0.38.38", "rustix 1.0.8",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]

View File

@ -95,6 +95,7 @@ objc2 = "0.5.1"
objc2-app-kit = { version = "0.2.0", default-features = false } objc2-app-kit = { version = "0.2.0", default-features = false }
objc2-foundation = { version = "0.2.0", default-features = false } objc2-foundation = { version = "0.2.0", default-features = false }
objc2-ui-kit = { version = "0.2.0", default-features = false } objc2-ui-kit = { version = "0.2.0", default-features = false }
open = "5"
parking_lot = "0.12" parking_lot = "0.12"
percent-encoding = "2.1" percent-encoding = "2.1"
pollster = "0.4" pollster = "0.4"
@ -107,6 +108,7 @@ serde = { version = "1", features = ["derive"] }
similar-asserts = "1.4.2" similar-asserts = "1.4.2"
smallvec = "1" smallvec = "1"
static_assertions = "1.1.0" static_assertions = "1.1.0"
tempfile = "3"
thiserror = "1.0.37" thiserror = "1.0.37"
type-map = "0.5.0" type-map = "0.5.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
@ -114,7 +116,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = "0.3.73" web-sys = "0.3.73"
web-time = "1.1.0" # Timekeeping for native and web web-time = "1.1.0" # Timekeeping for native and web
wgpu = { version = "27.0.0", default-features = false } wgpu = { version = "27.0.0", default-features = false, features = ["std"] }
windows-sys = "0.60" windows-sys = "0.60"
winit = { version = "0.30.12", default-features = false } winit = { version = "0.30.12", default-features = false }

View File

@ -39,7 +39,7 @@ disallowed-methods = [
# but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406)
# so we do that in `clipppy_wasm.toml` instead. # so we do that in `clipppy_wasm.toml` instead.
{ path = "std::env::temp_dir", readon = "Use the tempdir crate instead" }, { path = "std::env::temp_dir", readon = "Use the tempfile crate instead" },
{ path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" }, { path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" },
{ path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" }, { path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" },
] ]

View File

@ -33,7 +33,7 @@ wgpu = [
] ]
## Adds a dify-based image snapshot utility. ## Adds a dify-based image snapshot utility.
snapshot = ["dep:dify", "dep:image", "image/png"] snapshot = ["dep:dify", "dep:image", "dep:open", "dep:tempfile", "image/png"]
## Allows testing eframe::App ## Allows testing eframe::App
eframe = ["dep:eframe", "eframe/accesskit"] eframe = ["dep:eframe", "eframe/accesskit"]
@ -61,6 +61,8 @@ wgpu = { workspace = true, features = [
# snapshot dependencies # snapshot dependencies
dify = { workspace = true, optional = true } dify = { workspace = true, optional = true }
open = { workspace = true, optional = true }
tempfile = { workspace = true, optional = true }
# Enable this when generating docs. # Enable this when generating docs.
document-features = { workspace = true, optional = true } document-features = { workspace = true, optional = true }

View File

@ -544,7 +544,7 @@ pub fn image_snapshot(current: &image::RgbaImage, name: impl Into<String>) {
} }
} }
#[cfg(feature = "wgpu")] #[cfg(any(feature = "wgpu", feature = "snapshot"))]
impl<State> Harness<'_, State> { impl<State> Harness<'_, State> {
/// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot /// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot
/// with custom options. /// with custom options.
@ -635,6 +635,49 @@ impl<State> Harness<'_, State> {
} }
} }
} }
/// Render a snapshot, save it to a temp file and open it in the default image viewer.
///
/// This method is marked as deprecated to trigger errors in CI (so that it's not accidentally
/// committed).
#[deprecated = "Only for debugging, don't commit this."]
pub fn debug_open_snapshot(&mut self) {
let image = self
.render()
.map_err(|err| SnapshotError::RenderError { err })
.unwrap();
let temp_file = tempfile::Builder::new()
.disable_cleanup(true) // we keep the file so it's accessible even after the test ends
.prefix("kittest-snapshot")
.suffix(".png")
.tempfile()
.expect("Failed to create temp file");
let path = temp_file.path();
image
.save(temp_file.path())
.map_err(|err| SnapshotError::WriteSnapshot {
err,
path: path.to_path_buf(),
})
.unwrap();
#[expect(clippy::print_stdout)]
{
println!("Wrote debug snapshot to: {}", path.display());
}
let result = open::that(path);
if let Err(err) = result {
#[expect(clippy::print_stderr)]
{
eprintln!(
"Failed to open image {} in default image viewer: {err}",
path.display()
);
}
}
}
} }
/// Utility to collect snapshot errors and display them at the end of the test. /// Utility to collect snapshot errors and display them at the end of the test.

View File

@ -48,13 +48,14 @@ skip = [
{ name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5 { name = "bit-set" }, # wgpu's naga depends on 0.8, syntect's (used by egui_extras) fancy-regex depends on 0.5
{ name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5 { name = "bit-vec" }, # dependency of bit-set in turn, different between 0.6 and 0.5
{ name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, …
{ name = "core-foundation" }, # version conflict between winit and wgpu ecosystems
{ name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems
{ name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2
{ name = "quick-xml" }, # old version via wayland-scanner { name = "quick-xml" }, # old version via wayland-scanner
{ name = "redox_syscall" }, # old version via winit { name = "redox_syscall" }, # old version via winit
{ name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x { name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x
{ name = "thiserror-impl" }, # same as above { name = "thiserror-impl" }, # same as above
{ name = "windows-sys" }, # mostly hopeless to avoid { name = "windows-sys" }, # mostly hopeless to avoid
{ name = "core-foundation" }, # version conflict between winit and wgpu ecosystems
{ name = "core-graphics-types" }, # version conflict between winit and wgpu ecosystems
] ]
skip-tree = [ skip-tree = [
{ name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15 { name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15