复审在 `git archive 72f20d8` 上做了变异:删掉 `respawn` 的
`.env("ICED_BACKEND", fallback.env_value())`,**327 passed / 0 failed**,那条名为
`retry_fallback_flips_the_tier_to_cpu` 的测试照样绿。
名字说它守住了「降级后档位翻转」,实际它只把 `"wgpu"` / `"tiny-skia"` 两个字面量喂给
`backend_from_env` 和 `effective_panel_alpha`,**`respawn` 一行都没执行**(它 `-> !`,
走 `process::exit`)。这和 M97 要修的那行常量日志是同一个结构:**在需要结果的地方验了旁边的东西**。
## 改了什么
1. `respawn` 的两组 env 抽成纯函数 `fallback_env(fallback)`,返回值是
**`[(&'static str, &'static str); 2]`**——数量由类型钉住(删一条直接编译不过,
比测试更早的闸门),键与值由测试钉住。
2. 子进程那一轮的决策抽成 `renderer::decision::fallback_round(kind)`,**兜底恒为 `None`**
(防无限递归重开),`boot_probe` 不再就地拼 `Decision`。
3. 失败分流抽成纯函数 `on_failure(decision, evidence)` → `Respawn(Kind) | Refuse`:
交互降级 / 取证拒绝两条路各自被测试钉住(判据表第 8 行 vs 第 9 行)。
4. 补上复审点名的两处 0 覆盖:`renderer::strict`(剥兜底)与 `renderer::run_once`
(传下去的是 `decision.primary`,且进程内只跑一次)。
5. 改名并改文档 `retry_fallback_flips_the_tier_to_cpu` →
`each_backend_value_maps_to_its_tier_and_alpha`,明确写「本测试**不**守翻转」,
并指到真正守它的那条。
## 变异验证(8/8 被抓到,全部在实现行上做,测试里的同一字面量不动)
| 变异 | 结果 |
|---|---|
| M1 删掉 `ICED_BACKEND` 那条(=复审的变异) | **编译期**红:`expected an array with a size of 2, found one with a size of 1` |
| M2 键打错 `ICED_BAKEND` | `fallback_env_carries_the_backend_and_the_gate` FAILED |
| M3 闸门键打错 `ULL_GPU_FALLBACK_TYPO` | 同上 FAILED |
| M4 闸门值写死(不跟 fallback 走) | 同上 FAILED |
| M5 `fallback_round` 又给兜底 | `fallback_round_never_degrades_again` FAILED |
| M6 取证启动改成也降级 | `evidence_start_refuses_instead_of_degrading` FAILED |
| M7 `strict` 忘了清兜底 | `strict_strips_the_fallback` FAILED |
| M8 `run_once` 传死值 | `run_once_passes_the_decision_primary` FAILED |
复原后 `cargo test -p unlucky-app-iced --lib` = 334 passed。第一次跑变异时 A/B/C 三个
「照样绿」是我脚本自己的 bug:`sed` 把测试期望里的同一字面量一起改了(自指式变异盲区),
改成只在函数体内替换才暴露真实结果——这条也记进报告。
## 顺带(p3-1)
`boot.rs` 模块文档里「字体栈不重算」是旧实现的残留:改成换进程之后,子进程会把字体栈、
配置、presenter 全部重算(实测首帧 493~531 ms),文档已按实际行为重写,并把判据表
第 8/9 行两条路的差异写进模块头部。
运行时行为未变(refactor 只做抽取),已复测:取证启动 + wgpu 不可用 → exit 1、无 PNG、
具名错误 + 「拒绝降级出图」;交互启动 → 具名错误 + 换进程降级 + `[probe] 本次是降级重开`。
178 lines
8.6 KiB
Rust
178 lines
8.6 KiB
Rust
//! 本进程**实际**用的是哪个渲染后端(D36 的「CPU 档」判据)。
|
||
//!
|
||
//! ## 为什么不能直接看 `--renderer` 的请求值
|
||
//!
|
||
//! `Options::backend` 是**请求**(默认 `Auto`)。真正落到哪个渲染器由 `probe::apply` 决定,
|
||
//! 它把结论写进进程环境变量 `ICED_BACKEND`(iced 只认这个)。于是默认参数 + 没有独显的机器上:
|
||
//! 请求是 `Auto`,实际是 tiny-skia(CPU)——如果面板 α / 玻璃的降级判据只看请求值,
|
||
//! D36 明写的「CPU 档面板 α 强制 1.0」**恰好在它最该生效的场景失效**(复审 r1 P2-2)。
|
||
//!
|
||
//! 所以判据统一走 [`effective_backend`]:它把 `ICED_BACKEND` 翻译回 [`Backend`]。
|
||
//! 这里是**唯一**的翻译点,别在别处再读一遍环境变量。
|
||
//!
|
||
//! ## M97 之后这条链是准的(以前不是)
|
||
//!
|
||
//! 以前 `ICED_BACKEND` 可能是**候选列表**(`wgpu,tiny-skia`),运行期 wgpu 静默回退到
|
||
//! tiny-skia 时这个变量还停在 wgpu ⇒ 本模块按 **GPU 档**判 ⇒ **半透明在 CPU 渲染器上照开**。
|
||
//! 也就是说「静默降级」不只让日志说谎,还让 D36 的降级判据一起说谎。
|
||
//!
|
||
//! 现在 `ICED_BACKEND` **永远是单项**([`crate::renderer::Kind`]),而且 `app::boot` 的重试
|
||
//! 在降级重开前会把它改成 `tiny-skia`,于是:**真降级到 CPU 的那一次,面板 α 会正确地强制 1.0**。
|
||
//!
|
||
//! ## 已知边界(诚实登记)
|
||
//!
|
||
//! `iced` 0.14 没有公开的「当前后端」查询,所以本模块读的仍是**我们请求的那一项**。
|
||
//! 单项 + 重试([`crate::renderer`] 判据表第 8 行)把「请求 ≠ 实际」的窗口压到只剩一种:
|
||
//! 重试那次自己也失败——那时候进程会带着具名错误退出,不会带着错的档位继续画。
|
||
|
||
use crate::renderer::Kind;
|
||
use crate::Backend;
|
||
|
||
/// `ICED_BACKEND` 的取值 → 后端(纯函数,好测)。
|
||
///
|
||
/// 逗号列表按**首项**解释:兼容 M97 之前写下的值(以及外部手设的实验值),
|
||
/// 但本 crate 不再产出那种值(守门测试见 `probe::tests::apply_writes_exactly_one_backend`)。
|
||
#[must_use]
|
||
pub(super) fn parse_kind(env: &str) -> Option<Kind> {
|
||
match env.trim().split(',').next()?.trim() {
|
||
"tiny-skia" => Some(Kind::TinySkia),
|
||
"wgpu" => Some(Kind::Wgpu),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// `ICED_BACKEND` → 实际档位(纯函数,好测)。
|
||
///
|
||
/// | `ICED_BACKEND` | 含义(`probe::apply` 的产物) | 结论 |
|
||
/// |---|---|---|
|
||
/// | `tiny-skia` | 只有 CPU(`--renderer=cpu` / 未编 wgpu / 探测无硬件 / wgpu 失败后重开) | [`Backend::Cpu`] |
|
||
/// | `wgpu` | 判据表第 3、6 行:起步就是 GPU(起不来会打印具名错误并重开成 `tiny-skia`) | [`Backend::Gpu`] |
|
||
/// | 未设 / 别的值 | 不是本 crate 写的(外部手设 / M97 之前的旧进程) | 请求值(不猜) |
|
||
#[must_use]
|
||
pub(super) fn backend_from_env(request: Backend, env: Option<&str>) -> Backend {
|
||
env.and_then(parse_kind).map_or(request, Kind::tier)
|
||
}
|
||
|
||
/// 本进程实际生效的后端(判据 = `probe::apply` 写下的 `ICED_BACKEND`)。
|
||
#[must_use]
|
||
pub(super) fn effective_backend(request: Backend) -> Backend {
|
||
backend_from_env(request, std::env::var("ICED_BACKEND").ok().as_deref())
|
||
}
|
||
|
||
/// 本进程实际生效的**后端名**(`None` = 变量没写 / 不认识)。
|
||
///
|
||
/// 与 [`effective_backend`] 的区别:那个给「档位」,这个给「名字」,用于**取证日志**
|
||
/// (`[shot] … renderer=wgpu`)。两张图分辨不出 GPU/CPU 的时候,靠的就是这一行。
|
||
#[must_use]
|
||
pub(super) fn effective_kind() -> Option<Kind> {
|
||
std::env::var("ICED_BACKEND")
|
||
.ok()
|
||
.as_deref()
|
||
.and_then(parse_kind)
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::backend_from_env;
|
||
use crate::Backend;
|
||
use unlucky_presenter::prefs::{PANEL_ALPHA_MIN, WINDOW_ALPHA_MIN};
|
||
|
||
/// 抓得住:`probe::apply` 现在产出的**每一种单项值**都被翻译成正确的档位,
|
||
/// 且 M97 之前写下的逗号列表仍按首项解释(向后兼容)。
|
||
/// 抓不住:运行期重试降级那一刻的时序(那由 `app::boot` 的重试保证,见 `renderer` 判据表第 8 行)。
|
||
#[test]
|
||
fn every_iced_backend_value_maps_to_a_tier() {
|
||
// 本 crate 现在只产单项(`probe::tests::apply_writes_exactly_one_backend` 守这一条)。
|
||
assert_eq!(
|
||
backend_from_env(Backend::Auto, Some("tiny-skia")),
|
||
Backend::Cpu,
|
||
"M97 判据表第 5 行:探测无硬件 ⇒ CPU 档"
|
||
);
|
||
assert_eq!(
|
||
backend_from_env(Backend::Auto, Some("wgpu")),
|
||
Backend::Gpu,
|
||
"M97 判据表第 6 行:有硬件 ⇒ GPU 档"
|
||
);
|
||
assert_eq!(
|
||
backend_from_env(Backend::Cpu, Some("tiny-skia")),
|
||
Backend::Cpu,
|
||
"强制 CPU 也是 CPU 档"
|
||
);
|
||
// 兼容 M97 之前的逗号列表(外部手设 / 旧进程留下):按首项解释。
|
||
assert_eq!(
|
||
backend_from_env(Backend::Auto, Some("tiny-skia,wgpu")),
|
||
Backend::Cpu
|
||
);
|
||
assert_eq!(
|
||
backend_from_env(Backend::Auto, Some("wgpu,tiny-skia")),
|
||
Backend::Gpu
|
||
);
|
||
assert_eq!(
|
||
backend_from_env(Backend::Gpu, Some("wgpu,tiny-skia")),
|
||
Backend::Gpu
|
||
);
|
||
// 未设 / 空白:保持请求值(不是本 crate 写的,不猜)。
|
||
assert_eq!(backend_from_env(Backend::Auto, None), Backend::Auto);
|
||
assert_eq!(backend_from_env(Backend::Auto, Some(" ")), Backend::Auto);
|
||
// 不认识的取值不猜:保留请求值。
|
||
assert_eq!(
|
||
backend_from_env(Backend::Auto, Some("metal")),
|
||
Backend::Auto
|
||
);
|
||
}
|
||
|
||
/// **复审 r1 P2-2 的守门**:默认档(`Auto`)在无独显机器上必须落进 CPU 档,
|
||
/// 从而让「面板 α / 玻璃强制 1.0」在默认路径上真的生效。
|
||
///
|
||
/// 抓得住:判据走 `effective_backend()` 这条链(谁把 `State::new` 改回只吃请求值,这条会红)。
|
||
/// 抓不住:整窗 α 在 CPU 档仍要生效(那是另一条判据,见 `alpha` 模块真值表)。
|
||
#[test]
|
||
fn auto_with_a_software_renderer_forces_the_panel_opaque() {
|
||
use crate::pages::appearance::alpha::{effective_glass, effective_panel_alpha};
|
||
|
||
let backend = backend_from_env(Backend::Auto, Some("tiny-skia"));
|
||
assert_eq!(backend, Backend::Cpu);
|
||
assert_eq!(
|
||
effective_panel_alpha(backend, 0.55),
|
||
1.0,
|
||
"D36:CPU 档面板 1.0"
|
||
);
|
||
assert!(!effective_glass(backend, true), "D36:CPU 档玻璃强制关");
|
||
// 但整窗 α 不受限(D36 对 D27 的修订)。
|
||
assert!(crate::platform::effective_window_alpha(backend, 0.8).is_some());
|
||
|
||
// 反方向:GPU 档不做这个降级(否则「强制」就没意义了)。
|
||
let gpu = backend_from_env(Backend::Auto, Some("wgpu"));
|
||
assert_eq!(effective_panel_alpha(gpu, 0.55), 0.55);
|
||
assert!((PANEL_ALPHA_MIN - 0.4).abs() < 1e-9 && (WINDOW_ALPHA_MIN - 0.08).abs() < 1e-9);
|
||
}
|
||
|
||
/// **M97 的新守门**:wgpu 失败重开成 `tiny-skia` 之后,档位必须跟着变成 CPU 档。
|
||
///
|
||
/// 这是修之前做不到的一条:那时变量停在 `wgpu,tiny-skia`,静默回退到 tiny-skia 之后
|
||
/// 判据仍然读 `Gpu` ⇒ 半透明在 CPU 渲染器上照开。现在 `app::boot` 重开前会改写这个值,
|
||
/// 这条测试钉的是「改写成 tiny-skia 之后判据确实翻转」。
|
||
/// **两个后端取值各自对应哪个档位、面板 α 各是多少。**
|
||
///
|
||
/// ⚠️ 这条**不**守「降级后档位翻转」——它只是把两个字面量喂给纯函数,`respawn` 一行都没执行。
|
||
/// 复审 M97 r1 p2-1 就是靠这一点把 `respawn` 里的 `.env("ICED_BACKEND", …)` 删掉而测试全绿的。
|
||
/// 真正的翻转由 `app::boot::tests::fallback_env_carries_the_backend_and_the_gate`
|
||
/// (respawn 到底传了什么)钉住——那条红了才是「翻转坏了」。
|
||
#[test]
|
||
fn each_backend_value_maps_to_its_tier_and_alpha() {
|
||
use crate::pages::appearance::alpha::effective_panel_alpha;
|
||
|
||
let gpu = backend_from_env(Backend::Auto, Some("wgpu"));
|
||
assert_eq!(gpu, Backend::Gpu);
|
||
assert_eq!(effective_panel_alpha(gpu, 0.55), 0.55);
|
||
|
||
let cpu = backend_from_env(Backend::Auto, Some("tiny-skia"));
|
||
assert_eq!(cpu, Backend::Cpu);
|
||
assert_eq!(
|
||
effective_panel_alpha(cpu, 0.55),
|
||
1.0,
|
||
"D36:CPU 档面板 α 强制 1.0"
|
||
);
|
||
}
|
||
}
|