From 67cea4844c992a926e0bda2bb4d8d4d6032bca92 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 24 Sep 2026 15:22:36 +0800 Subject: [PATCH] fix(macos): gate real GL for the whole test build; add Windows diagnostics - The macOS SIGSEGV moved from the gl_bridge unit tests (already gated) to other real-GL users in the same binary (suites::gl_render, render_driver): make the gate systemic in the test build on macOS. gl_available() reports unavailable and acquire() fails unless OAK_GPU_TESTS is set, so every unit test takes its documented CPU fallback; release builds are untouched. - Dump Apple's crash reports on macOS failure: a SIGSEGV in a test binary prints nothing, and the .ips report carries the native stack. - The Windows-only multicam graph test failure now prints both decoded media probes and both rendered source pixels: that separates a broken test-media encode from a broken graph switch in one run. --- .github/workflows/ci.yml | 11 +++++++++ crates/oak-app/src/oakui/renderops.rs | 32 +++++++++++++++++++++++++++ crates/oak-plugin/src/gl_bridge.rs | 20 +++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47b2a6aef..325741044 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -505,6 +505,17 @@ jobs: cargo test --workspace --locked fi + # A SIGSEGV in a test binary gives no Rust backtrace; Apple's crash + # reports carry the native stack, so surface the newest ones. + - name: Crash reports + if: failure() + run: | + for f in $(ls -t ~/Library/Logs/DiagnosticReports/*.ips 2>/dev/null | head -3); do + echo "===== $f" + head -c 6000 "$f" + echo + done + # second upstream target: openKylin (Debian/Ubuntu-derived) in its own # image. The base image is bare (no sudo, no make, no python3) and the # steps run as root, so there is no sudo prefix anywhere below. diff --git a/crates/oak-app/src/oakui/renderops.rs b/crates/oak-app/src/oakui/renderops.rs index 5b0c2c889..2e45d0996 100644 --- a/crates/oak-app/src/oakui/renderops.rs +++ b/crates/oak-app/src/oakui/renderops.rs @@ -2500,6 +2500,32 @@ mod tests { oak_codec::testmedia::write_test_clip_solid(&blue, 64, 64, 10, 10, [0.0, 0.0, 1.0, 1.0]) .expect("blue angle"); + // Diagnostic (the Windows-only switch failure): decode each file + // directly, so a red-ish source 1 can be blamed on the generated + // media or on the graph. + let probe = |name: &str, path: &std::path::Path| -> (f32, f32) { + let texture = oak_render::eval::render_footage_frame( + &path.to_string_lossy(), + 0, + oak_core::Rational::new(0, 1), + (64, 64), + oak_core::PixelFormat::F32, + ) + .unwrap_or_else(|e| panic!("probe {name}: {e}")); + let frame = texture.to_frame().expect("probe readback"); + let stride = frame.linesize_bytes() as usize; + let off = 8 * stride + 8 * 16; + ( + f32::from_le_bytes(frame.data[off..off + 4].try_into().unwrap()), + f32::from_le_bytes(frame.data[off + 8..off + 12].try_into().unwrap()), + ) + }; + println!( + "multicam media probe: red={:?} blue={:?}", + probe("red", &red), + probe("blue", &blue) + ); + let (project, seq, _rfoot) = project_with_clip(&red); let bfoot = graphops::import_footage(&project, &blue).expect("import blue"); graphops::add_track(&project, seq, TrackType::Video).expect("track 1"); @@ -2623,6 +2649,12 @@ mod tests { } } let (r1, b1) = render_rgb(0); + // Diagnostic on failure: identical pairs mean the second render was + // served the first frame; a red-ish source 1 means the selected + // element resolved wrong. + println!( + "multicam graph render: source 0 -> (r={r0:.4}, b={b0:.4}), source 1 -> (r={r1:.4}, b={b1:.4})" + ); assert!( b1 > 0.4 && r1 < 0.4, "source 1 switches the node-graph frame to blue (r={r1} b={b1})" diff --git a/crates/oak-plugin/src/gl_bridge.rs b/crates/oak-plugin/src/gl_bridge.rs index 7e3ef9835..66d9b62d2 100644 --- a/crates/oak-plugin/src/gl_bridge.rs +++ b/crates/oak-plugin/src/gl_bridge.rs @@ -88,10 +88,24 @@ use crate::render::VideoParams; // ---- 对外 API(平台无关签名;实现按平台 cfg 分派)---------------------- +/// Whether the TEST build may touch the real CGL context on macOS. The +/// headless CI runner cannot exercise Apple's GL reliably (parallel unit +/// tests have segfaulted inside the driver), so the test build behaves as +/// if GL were unavailable unless `OAK_GPU_TESTS` asks for it — the same +/// gate the integration GL tests use. Release builds are untouched. +#[cfg(all(test, target_os = "macos"))] +fn real_gl_gated() -> bool { + std::env::var_os("OAK_GPU_TESTS").is_none() +} + /// GL 上下文是否可用(离屏上下文可创建)。use_opengl 决策用它当 /// "目标纹理有有效 GL 名" 的门:桥能为目标帧建出真实 GL 纹理 ⟺ /// 上下文可用。惰性创建,幂等。 pub fn gl_available() -> bool { + #[cfg(all(test, target_os = "macos"))] + if real_gl_gated() { + return false; + } imp::gl_available() } @@ -99,6 +113,12 @@ pub fn gl_available() -> bool { /// 返回的 guard 在 drop 时清 current 并放锁。失败(无 GL / 上下文 /// 创建失败)→ 调用方回退 CPU。 pub fn acquire() -> crate::error::Result { + #[cfg(all(test, target_os = "macos"))] + if real_gl_gated() { + return Err(crate::error::Error::Failed( + "gl_bridge: real GL is gated behind OAK_GPU_TESTS in tests".to_string(), + )); + } imp::acquire() }