From b13ef477a8a6db4a65f300b65a9c1fccee27860a Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 24 Sep 2026 14:45:12 +0800 Subject: [PATCH] fix(tests): make the platform-specific suites portable and deterministic Four independent CI failures the first real cross-platform run surfaced: - macOS SIGSEGV: the real-GL unit tests in gl_bridge ran wherever CGL is available, including the headless CI runner. Gate them with the same OAK_GPU_TESTS switch the integration GL tests already use (skip on CI, opt in on a real Mac). - Windows build: examples compile under `cargo test`, and bench_playback used libc::getrusage unconditionally. Keep the Unix CPU accounting behind #[cfg(unix)] and report zero CPU seconds elsewhere. - openKylin arm64: engine_without_a_project_hits_the_guard_paths assumed the library backend was unconfigured while a parallel config test transiently set Storage/Backend=sqlite. Take the shared config lock and pin the key off for the test's duration. - openKylin x64: the prefetch smoke test asserted an exact decode count, but the hand-off LRU holds only DECODE_LRU_CAP (2) frames, so a request can miss the prefetched copy under scheduling pressure and re-run the producer (the eval cache still serves the pixels). Bound the count instead of pinning it; the deterministic sibling test pins read-ahead usage. --- crates/oak-app/src/oakui/real.rs | 7 +++++++ crates/oak-plugin/src/gl_bridge.rs | 9 ++++++++ crates/oak-render/examples/bench_playback.rs | 12 ++++++++++- .../oak-render/tests/render_threads_test.rs | 21 ++++++++++++++++--- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/crates/oak-app/src/oakui/real.rs b/crates/oak-app/src/oakui/real.rs index a884f30c2..cb69fb3eb 100644 --- a/crates/oak-app/src/oakui/real.rs +++ b/crates/oak-app/src/oakui/real.rs @@ -12576,6 +12576,13 @@ mod tests { /// (early return / Err / empty data source) without panicking. #[gpui::test] async fn engine_without_a_project_hits_the_guard_paths(cx: &mut gpui::TestAppContext) { + // The library guards below depend on the process-global storage + // config: serialize with the config tests (the shared lock) and pin + // the backend OFF so a concurrent test's transient "sqlite" cannot + // make `library_create_project` succeed mid-assertion. + let _guard = crate::oakui::graphops::test_lock(); + let _backend = ConfigRestore::of(CONFIG_KEY_STORAGE_BACKEND); + crate::oakui::real::config_set_string(CONFIG_KEY_STORAGE_BACKEND, ""); let engine = cx.update(|cx| cx.new(RealEngine::create)); // Data sources on an empty engine. diff --git a/crates/oak-plugin/src/gl_bridge.rs b/crates/oak-plugin/src/gl_bridge.rs index ccfa85458..7e3ef9835 100644 --- a/crates/oak-plugin/src/gl_bridge.rs +++ b/crates/oak-plugin/src/gl_bridge.rs @@ -801,7 +801,16 @@ mod tests { use crate::render::VideoParams; /// GL 不可用时 skip(无 GPU 无头环境);可用时返回 guard。 + /// + /// macOS 的真实 CGL 验收与集成 GL 测试同一门禁:CI 无头且用例并行, + /// 默认只做可用性探测;设 `OAK_GPU_TESTS=1` 才跑真实 GL 用例 + /// (`crates/oak-plugin/tests/gl_render_test.rs` 同约定)。 fn maybe_gl() -> Option { + #[cfg(target_os = "macos")] + if std::env::var_os("OAK_GPU_TESTS").is_none() { + println!("SKIP: 真实 GL 桥用例需 OAK_GPU_TESTS(本机 GPU 验收;CI 一律跳过)"); + return None; + } match acquire() { Ok(g) => Some(g), Err(e) => { diff --git a/crates/oak-render/examples/bench_playback.rs b/crates/oak-render/examples/bench_playback.rs index 8b7dcb841..b6da3aa52 100644 --- a/crates/oak-render/examples/bench_playback.rs +++ b/crates/oak-render/examples/bench_playback.rs @@ -66,7 +66,10 @@ fn worker_bin() -> PathBuf { PathBuf::from("oak-worker") } -/// `(user, system)` CPU seconds of this process and its children. +/// `(user, system)` CPU seconds of this process and its children (Unix +/// `getrusage`; the benchmark's CPU accounting is Unix-only, the rest of +/// the harness runs everywhere). +#[cfg(unix)] fn cpu_times() -> (f64, f64) { fn rusage(who: i32) -> (f64, f64) { let mut usage: libc::rusage = unsafe { std::mem::zeroed() }; @@ -81,6 +84,13 @@ fn cpu_times() -> (f64, f64) { (self_times.0 + children.0, self_times.1 + children.1) } +/// Windows has no `getrusage`; the harness still builds and runs, it just +/// reports zero CPU seconds (`cargo test` compiles examples on every OS). +#[cfg(not(unix))] +fn cpu_times() -> (f64, f64) { + (0.0, 0.0) +} + /// One footage ticket over the whole timeline. fn footage_params(media: &str, time: Rational, width: i32, height: i32) -> VideoTicketParams { VideoTicketParams { diff --git a/crates/oak-render/tests/render_threads_test.rs b/crates/oak-render/tests/render_threads_test.rs index 96cef1e37..52cd17ec1 100644 --- a/crates/oak-render/tests/render_threads_test.rs +++ b/crates/oak-render/tests/render_threads_test.rs @@ -913,10 +913,25 @@ fn pipeline_playback_prefetches_ahead_of_the_render() { times.len() as u64, "every playback post queued its footage prefetch" ); - assert_eq!( + // Each distinct frame is produced at least once. The hand-off LRU holds + // only `DECODE_LRU_CAP` (2) frames, so under scheduling pressure a + // request can miss the prefetched copy and re-run the producer; the + // eval-side cache still serves the pixels without touching the media, + // but the counter moves. Bound this to one production per command + // (prefetch + request) instead of asserting an exact count; the + // deterministic `pipeline_prefetch_is_the_frame_the_render_request_uses` + // pins that the read-ahead is actually used. + assert!( + stats.decodes >= times.len() as u64, + "each distinct frame is produced at least once ({} < {})", stats.decodes, - times.len() as u64, - "each distinct frame decodes exactly once (prefetch or rendezvous)" + times.len() + ); + assert!( + stats.decodes <= 2 * times.len() as u64, + "at most one production per prefetch and per request ({} > {})", + stats.decodes, + 2 * times.len() ); // Note: `procpool::main_heap_frame_copies` only counts the shm path, // which the thread pipeline never touches, so asserting it here would