#![cfg(feature = "snapshot")] use std::io; use std::path::PathBuf; /// Configuration for `egui_kittest`. /// /// It's loaded once (per process) by searching for a `kittest.toml` file in the project root /// (the directory containing `Cargo.lock`). #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(default, deny_unknown_fields)] pub struct Config { /// The output path for image snapshots. /// /// Default is "tests/snapshots" (relative to the working directory / crate root). output_path: PathBuf, /// The per-pixel threshold. /// /// Default is 0.6. threshold: f32, /// The number of pixels that can differ before the test is considered failed. /// /// Default is 0. failed_pixel_count_threshold: usize, windows: OsConfig, mac: OsConfig, linux: OsConfig, } impl Default for Config { fn default() -> Self { Self { output_path: PathBuf::from("tests/snapshots"), threshold: 0.6, failed_pixel_count_threshold: 0, windows: Default::default(), mac: Default::default(), linux: Default::default(), } } } #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(default, deny_unknown_fields)] pub struct OsConfig { /// Override the per-pixel threshold for this OS. threshold: Option, /// Override the failed pixel count threshold for this OS. failed_pixel_count_threshold: Option, } fn find_kittest_toml() -> io::Result { let mut current_dir = std::env::current_dir()?; loop { let current_kittest = current_dir.join("kittest.toml"); // Check if Cargo.toml exists in this directory if current_kittest.exists() { return Ok(current_kittest); } // Move up one directory if !current_dir.pop() { return Err(io::Error::new( io::ErrorKind::NotFound, "kittest.toml not found", )); } } } fn load_config() -> Config { if let Ok(config_path) = find_kittest_toml() { match std::fs::read_to_string(&config_path) { Ok(config_str) => match toml::from_str(&config_str) { Ok(config) => config, Err(e) => panic!("Failed to parse {}: {e}", &config_path.display()), }, Err(err) => { panic!("Failed to read {}: {}", config_path.display(), err); } } } else { Config::default() } } /// Get the global configuration. /// /// See [`Config::global`] for details. pub fn config() -> &'static Config { Config::global() } impl Config { /// Get or load the global configuration. /// /// This is either /// - Based on a `kittest.toml`, found by searching from the current working directory /// (for tests that is the crate root) upwards. /// - The default [Config], if no `kittest.toml` is found. pub fn global() -> &'static Self { static INSTANCE: std::sync::LazyLock = std::sync::LazyLock::new(load_config); &INSTANCE } /// The output path for image snapshots. /// /// Default is "tests/snapshots". pub fn output_path(&self) -> PathBuf { self.output_path.clone() } } #[cfg(feature = "snapshot")] impl Config { pub fn os_threshold(&self) -> crate::OsThreshold { let fallback = self.threshold; crate::OsThreshold { windows: self.windows.threshold.unwrap_or(fallback), macos: self.mac.threshold.unwrap_or(fallback), linux: self.linux.threshold.unwrap_or(fallback), fallback, } } pub fn os_failed_pixel_count_threshold(&self) -> crate::OsThreshold { let fallback = self.failed_pixel_count_threshold; crate::OsThreshold { windows: self .windows .failed_pixel_count_threshold .unwrap_or(fallback), macos: self.mac.failed_pixel_count_threshold.unwrap_or(fallback), linux: self.linux.failed_pixel_count_threshold.unwrap_or(fallback), fallback, } } /// The threshold. /// /// Default is 1.0. pub fn threshold(&self) -> f32 { self.os_threshold().threshold() } /// The number of pixels that can differ before the test is considered failed. /// /// Default is 0. pub fn failed_pixel_count_threshold(&self) -> usize { self.os_failed_pixel_count_threshold().threshold() } }