//! Runs the `resolved` binary on the example scenes. use std::path::{Path, PathBuf}; use std::process::Command; fn repo() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") } fn out_dir() -> PathBuf { let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("cli"); std::fs::create_dir_all(&dir).unwrap(); dir } /// Runs the binary from the repository root with `ASTROKIT_KERNELS` cleared, so the /// kernel search falls back the way it does for someone who has not set it. fn resolved(args: &[&str]) -> std::process::Output { Command::new(env!("CARGO_BIN_EXE_resolved")) .args(args) .current_dir(repo()) .env_remove("ASTROKIT_KERNELS") .output() .unwrap() } /// Renders `scene` on `adapter` to a 16-bit PNG and returns its pixels as DN (the /// file holds 12-bit DN shifted up by 4). fn render(scene: &str, adapter: &str) -> image::ImageBuffer, Vec> { let out = out_dir().join(format!( "{}-{adapter}.png", Path::new(scene).file_stem().unwrap().to_str().unwrap() )); let preview = out.with_extension("preview.png"); let result = resolved(&[ "render", "--scene", scene, "--adapter", adapter, "--out", out.to_str().unwrap(), "--preview", preview.to_str().unwrap(), ]); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert!(preview.is_file()); let mut pixels = image::open(&out).unwrap().into_rgb16(); for p in pixels.pixels_mut() { assert!(p.0.iter().all(|v| v & 0xf == 0), "low 4 bits are padding"); p.0 = p.0.map(|v| v >> 4); } pixels } #[test] fn example_scene_on_gpu_and_cpu() { let gpu = render("examples/scenes/boxwing.toml", "gpu"); let cpu = render("examples/scenes/boxwing.toml", "cpu"); assert_eq!(gpu.dimensions(), (1920, 1080)); // The image center is on the sunlit gold bus: brighter in red than in blue. let [r, _, b] = gpu.get_pixel(960, 540).0; assert!(r > 500 && r > 2 * b, "{r} {b}"); // Above the limb is empty space, which reads the 64 DN bias; below it, Earth's // dayside, lit and blue. assert_eq!(gpu.get_pixel(0, 0).0, [64; 3]); let [r, g, b] = gpu.get_pixel(0, 1079).0; assert!(r > 64 && g > r && b > g, "{r} {g} {b}"); let differing = gpu .pixels() .zip(cpu.pixels()) .filter(|(a, b)| a.0.iter().zip(b.0).any(|(x, y)| x.abs_diff(y) > 1)) .count(); // Only pixels on silhouette edges may land differently. assert!( differing < 500, "{differing} pixels differ between GPU and CPU" ); } #[cfg(feature = "astrokit")] #[test] fn epoch_scene_renders() { let frame = render("examples/scenes/boxwing-epoch.toml", "gpu"); // The same view as boxwing.toml, rotated to the real Sun: the sunlit bus at the // center, space above the limb. assert!(frame.get_pixel(960, 540).0[0] > 500); assert_eq!(frame.get_pixel(0, 0).0, [64; 3]); } #[test] fn errors_are_reported() { let missing = resolved(&["render", "--scene", "no/such.toml", "--preview", "x.png"]); assert!(!missing.status.success()); assert!(String::from_utf8_lossy(&missing.stderr).contains("no/such.toml")); let idle = resolved(&["render", "--scene", "examples/scenes/boxwing.toml"]); assert!(!idle.status.success()); assert!(String::from_utf8_lossy(&idle.stderr).contains("nothing to do")); } #[cfg(feature = "astrokit")] #[test] fn missing_kernels_say_where_they_looked() { let result = resolved(&[ "render", "--scene", "examples/scenes/boxwing-epoch.toml", "--kernels", "no/such/dir", "--preview", "x.png", ]); assert!(!result.status.success()); let stderr = String::from_utf8_lossy(&result.stderr); assert!( stderr.contains("--kernels") && stderr.contains("no/such/dir"), "{stderr}" ); } #[test] fn adapters_lists_something() { let result = resolved(&["adapters"]); assert!(result.status.success()); assert!(!result.stdout.is_empty()); }