diff --git a/crates/gpu/src/tests.rs b/crates/gpu/src/tests.rs index 696402d6..84f64a6d 100644 --- a/crates/gpu/src/tests.rs +++ b/crates/gpu/src/tests.rs @@ -49,18 +49,29 @@ pub(crate) fn hold_gpu_lock() { /// The shared test device (the calling test holds [`hold_gpu_lock`]). pub(crate) fn gpu() -> Option<&'static Gpu> { - static G: OnceLock> = OnceLock::new(); + static G: OnceLock> = OnceLock::new(); hold_gpu_lock(); - G.get_or_init(|| { - let g = Gpu::headless(); - if g.is_none() { - eprintln!("effectcraft-gpu tests: no GPU adapter, skipping GPU comparisons"); - } else if let Some(g) = &g { - eprintln!("effectcraft-gpu tests: adapter {}", g.ctx.name); + let g = G.get_or_init(|| { + let g = Gpu::try_headless(); + match &g { + Ok(g) => eprintln!("effectcraft-gpu tests: adapter {}", g.ctx.name), + Err(e) => eprintln!("effectcraft-gpu tests: no GPU ({e}), skipping GPU comparisons"), } g - }) - .as_ref() + }); + require_gpu("effectcraft-gpu tests", g.as_ref()) +} + +/// `g`, or `None` (the GPU tests skip) when there is no GPU, unless `EFFECTCRAFT_REQUIRE_GPU=1` +/// (as in `tests_init`): then a GPU that didn't start fails the test, with why. +pub(crate) fn require_gpu(tests: &str, g: Result) -> Option { + match g { + Ok(g) => Some(g), + Err(e) if std::env::var("EFFECTCRAFT_REQUIRE_GPU").is_ok_and(|v| v == "1") => { + panic!("{tests}: EFFECTCRAFT_REQUIRE_GPU=1 but GPU initialization failed: {e}") + } + Err(_) => None, + } } /// The test device is on wgpu's OpenGL backend (Mesa's llvmpipe on the FreeBSD CI job). diff --git a/crates/gpu/src/tests_adv3d.rs b/crates/gpu/src/tests_adv3d.rs index ee99c5c0..869c60aa 100644 --- a/crates/gpu/src/tests_adv3d.rs +++ b/crates/gpu/src/tests_adv3d.rs @@ -17,11 +17,13 @@ use crate::Gpu; /// A device of the test's own (the test holds [`crate::tests::hold_gpu_lock`]). fn gpu() -> Option { crate::tests::hold_gpu_lock(); - let g = Gpu::headless(); - if g.is_none() { - eprintln!("effectcraft-gpu adv3d tests: no GPU adapter, skipping"); + match Gpu::try_headless() { + Ok(g) => Some(g), + Err(e) => { + eprintln!("effectcraft-gpu adv3d tests: no GPU ({e}), skipping"); + crate::tests::require_gpu("effectcraft-gpu adv3d tests", Err(&e)) + } } - g } fn add(p: &mut Project, cid: ItemId, src: LayerSource, edit: impl FnOnce(&mut effectcraft_project::Layer)) { diff --git a/crates/gpu/src/tests_deferred.rs b/crates/gpu/src/tests_deferred.rs index e798bd8f..b6e43b55 100644 --- a/crates/gpu/src/tests_deferred.rs +++ b/crates/gpu/src/tests_deferred.rs @@ -17,19 +17,19 @@ use crate::tests::{Pattern, Scene, diff, n, set, tolerance, v3}; /// The device, held exclusively: deferred state is per device and a frame renders at a time /// (a frame worker renders one frame at a time too). pub(crate) fn deferred_gpu() -> Option<(&'static Gpu, std::sync::MutexGuard<'static, ()>)> { - static G: OnceLock> = OnceLock::new(); + static G: OnceLock> = OnceLock::new(); static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); // Also no other GPU test while it runs (see `hold_gpu_lock`). crate::tests::hold_gpu_lock(); - let g = G - .get_or_init(|| { - let g = pollster::block_on(Gpu::request_deferred()).map_err(|e| eprintln!("deferred gpu tests skipped: {e}")).ok(); - if let Some(g) = &g { - eprintln!("deferred gpu tests: adapter {}", g.ctx.name); - } - g - }) - .as_ref()?; + let g = G.get_or_init(|| { + let g = pollster::block_on(Gpu::request_deferred()); + match &g { + Ok(g) => eprintln!("deferred gpu tests: adapter {}", g.ctx.name), + Err(e) => eprintln!("deferred gpu tests: no GPU ({e}), skipping"), + } + g + }); + let g = crate::tests::require_gpu("deferred gpu tests", g.as_ref())?; Some((g, LOCK.lock().unwrap_or_else(|e| e.into_inner()))) }