From bf9386ecb2e07dc4fbc031524c80f2533cfee60b Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 26 Jul 2026 10:13:49 +0200 Subject: [PATCH] test(pf-encode): pin the typed-EINVAL classifier's chain-survival contract (Phase 8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rides on 25765c53 (the concurrent session landed the same typed-errno fix first — this branch's twin commit was dropped in the rebase; these are its surviving residuals): - Two tests pinning what the ladder's step-down rests on: the typed ffmpeg::Error survives with_context layers as a downcastable source (an eager format! anywhere between open_with and the ladder would silently break it), and the phrase WITHOUT the type no longer classifies. - The CudaHw::new eager-format guard note: those bail!s must NOT be converted to typed errors — a hwdevice/hwframes EINVAL is a config error no bitrate can fix, and enrolling it would burn ~10 doomed opens before surfacing the real failure. Co-Authored-By: Claude Opus 5 (1M context) --- crates/pf-encode/src/enc/linux/mod.rs | 5 +++++ crates/pf-encode/src/lib.rs | 32 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/crates/pf-encode/src/enc/linux/mod.rs b/crates/pf-encode/src/enc/linux/mod.rs index fa93bbe7..fd04bd30 100644 --- a/crates/pf-encode/src/enc/linux/mod.rs +++ b/crates/pf-encode/src/enc/linux/mod.rs @@ -66,6 +66,11 @@ struct CudaHw { impl CudaHw { /// Build a CUDA hwdevice wrapping `cu_ctx` and a frames pool (`sw_format` = `pixel`). + /// + /// The `bail!`s below format raw AVERROR ints eagerly BY DESIGN — do not convert them to + /// typed errors: `open_nvenc_probed`'s bitrate ladder steps down on a typed EINVAL + /// (`nvenc_open_einval`), and a hwdevice/hwframes EINVAL is a config error no bitrate can + /// fix — enrolling it would burn ~10 doomed encoder opens before surfacing the real failure. unsafe fn new(cu_ctx: *mut std::ffi::c_void, sw_format: Pixel, w: u32, h: u32) -> Result { let mut device_ref = ffi::av_hwdevice_ctx_alloc(ffi::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA); if device_ref.is_null() { diff --git a/crates/pf-encode/src/lib.rs b/crates/pf-encode/src/lib.rs index ad25d6e4..c053dcb4 100644 --- a/crates/pf-encode/src/lib.rs +++ b/crates/pf-encode/src/lib.rs @@ -1814,6 +1814,38 @@ mod tests { assert_eq!(trait_fns, impl_fns); } + /// The typed-EINVAL classifier the bitrate ladder keys on (Phase 8): the `ffmpeg::Error` + /// must survive `with_context` layers as a downcastable source — pinned here because the + /// entire ladder's step-down behavior rests on it, and an eager `format!` anywhere between + /// `open_with` and the ladder would silently break it (the ladder would stop stepping and + /// 4K sessions would surface errors instead of degrading). + #[cfg(target_os = "linux")] + #[test] + fn nvenc_open_einval_survives_context_layers() { + use ffmpeg_next as ffmpeg; + let e = anyhow::Error::from(ffmpeg::Error::Other { + errno: ffmpeg::util::error::EINVAL, + }) + .context("open hevc_nvenc (3840x2160@120, 400000000 bps)") + .context("outer"); + assert!(nvenc_open_einval(&e)); + // ENOSYS (or any other errno) must not step the ladder. + let e = anyhow::Error::from(ffmpeg::Error::Other { + errno: ffmpeg::util::error::ENOSYS, + }) + .context("open"); + assert!(!nvenc_open_einval(&e)); + } + + /// The phrase WITHOUT the type no longer classifies — the fragility that was removed: the + /// old string match trusted any error whose rendering contained "Invalid argument". + #[cfg(target_os = "linux")] + #[test] + fn nvenc_open_einval_ignores_untyped_text() { + let e = anyhow::anyhow!("driver said: Invalid argument (not a typed libav errno)"); + assert!(!nvenc_open_einval(&e)); + } + /// WP7.6: the resolver's full alias table, pinned. The panicking closure is the laziness /// contract — an explicit pref must resolve WITHOUT running the auto probe (`/serverinfo` /// polls through the mirrors; `gamestream/serverinfo.rs` litigated exactly that cost).