From d341651b206b9b06be6962ffb9bbec3afefbbbc2 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 6 Oct 2026 09:51:07 -0400 Subject: [PATCH] Tests: one wgpu device compiles shaders at a time in view.rs (WARP ARM64 crash) On GitHub's Windows 11 ARM64 runner (no GPU, so wgpu uses Microsoft's WARP) tests/view.rs died with STATUS_ACCESS_VIOLATION in about 1 run in 4. A crash dump puts it inside WARP's pixel-shader JIT (d3d10warp!PixelJitProgram::ClassifyVars, on WARP's own thread pool) when the two tests that render pixels compile shaders on separate devices at once. Single-threaded, or each test alone, it never crashed (180 runs); bigger thread stacks didn't help (6/30 vs 7/30), so it's that race, not our code. The app has one device on a real GPU and can't hit it. Those two tests now hold a per-binary lock for their whole run (taken before the harness, so released after its device drops). Other test binaries render from at most one test. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/tests/view.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/ui-egui/tests/view.rs b/crates/ui-egui/tests/view.rs index ad22e0325..5d017f1f8 100644 --- a/crates/ui-egui/tests/view.rs +++ b/crates/ui-egui/tests/view.rs @@ -15,6 +15,18 @@ const PAGES: &[u8] = b"%PDF-1.7 trailer << /Root 1 0 R >> %%EOF"; +/// Tests that render pixels (`h.render()`) take this first, so only one wgpu device compiles +/// shaders at a time. On machines without a GPU, wgpu falls back to Microsoft's WARP, whose ARM64 +/// pixel-shader JIT crashes (access violation in `d3d10warp!PixelJitProgram::ClassifyVars`) when +/// two devices compile at once, as on GitHub's Windows 11 ARM64 runner. Hold it for the whole +/// test (declared before the harness, so it's released after the device is dropped). +static GPU: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +fn gpu() -> std::sync::MutexGuard<'static, ()> { + // A test that panicked while holding it leaves nothing to clean up. + GPU.lock().unwrap_or_else(std::sync::PoisonError::into_inner) +} + fn harness(options: &'static [(&'static str, &'static str)]) -> Harness<'static, PrintCraftApp> { let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| { let mut app = PrintCraftApp::new(); @@ -145,6 +157,7 @@ fn field_list_jumps_to_a_fields_page() { #[test] fn highlight_fields_tints_the_field_area() { use egui_kittest::kittest::Queryable; + let _gpu = gpu(); let mut h = form_harness(); h.state_mut().set_option("panel", "none").unwrap(); for _ in 0..100 { @@ -204,6 +217,7 @@ fn the_hand_tool_pans_by_dragging() { #[test] fn required_fields_get_a_red_border_when_highlighting() { + let _gpu = gpu(); let mut h = form_harness(); h.state_mut().set_option("panel", "none").unwrap(); h.state_mut().set_option("fields", "on").unwrap();