fix(app-iced): M100 r3 P2-1 —— 判据从「帧超时」升级成「终态无证据即失败」
复审在 `95530a5` 的导出上用变异实测证明:把第 3 帧的采样换成 `window::close(id)` 时, 进程 **exit 0 且没有 PNG**,日志与成功跑同一形状只少了出图那几行 —— 只看退出码的脚本会读到 「成功 + 零证据」。 根因:`window::frames()` 只在 `Event::Window(RedrawRequested)` 时产消息,窗口消失 ⇒ 不再有帧 ⇒ `Message::Frame` 不再产生 ⇒ `capture_stalled` **永远求值不到**;同时 iced_winit 对「最后一个 窗口 Destroyed」直接 `Control::Exit`,`run()` 返回 `Ok(())` ⇒ 例子返回 SUCCESS。 我原来在 `CAPTURE_GIVE_UP_FRAMES` 的注释里把「窗口消失」写进了覆盖清单,实测是**说反的**。 改法(后置不变量,比超时强): - 落盘计数改成共享 `Arc<AtomicU32>`,**只在 `shot::save` 成功后**自增(U1:不是「打算写」)。 - `run()` 里 `outcome?;` 之后加终态判据 `no_evidence(written) == 0` ⇒ 打具名错误并 `exit(1)`。 - 帧超时兜底保留(管「有帧但采样回不来」);注释订正为三路:后端报错 ⇒ Err(本就非 0)、 窗口消失 ⇒ 帧不再产生 ⇒ 由终态判据兜住、有帧但采样卡住 ⇒ 帧超时。 实测: - 正常路径(最终二进制 ×6):exit 0、PNG 存在、`落盘次数=1`、`passthrough_ext=on`、sha `0d7b4f5b`、错误行 0。 - **M3 变异复现**(采样换成 `window::close(id)`,×3):**exit=1**、**PNG 不存在**、 `[harness] 取证结束但没有任何图落盘(窗口提前消失 / 落盘失败),按失败退出`。 - **M4 变异**(`CAPTURE_GIVE_UP_FRAMES` 改 0,×2):仍 exit=1 + `采样超时` ⇒ 旧兜底没被改坏。 - 新断言 `finishing_without_a_single_write_is_a_failure` 守终态判据(变异:删掉 `run()` 里那条 检查不会让它红——它守的是判据本身,不是调用点,报告里写清了「抓不住」)。
This commit is contained in:
1 parent
95530a5311
commit
d298691c17
3 files changed
+76
-10
No files matched your search
@@ -4,6 +4,8 @@
|
||||
//! 参数解析在 [`super::request`],窗口状态机与采样闸门在 [`super`],OS 侧密闭指标在
|
||||
//! [`super::caps`]。搬出来没有改行为(同一 argv 的 PNG sha256 不变)。
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use iced::{window, Point, Task};
|
||||
@@ -20,6 +22,15 @@ use crate::theme::Ui;
|
||||
|
||||
use super::{demo_model, Harness, Message, Request, CAPTURE_POSITION};
|
||||
|
||||
/// **终态判据**:取证跑完了却一张图都没落盘 ⇒ 失败。
|
||||
///
|
||||
/// 单独拎成纯函数是为了让它能被断言(`run()` 要真窗口才能跑)。它守的是 M100 r3 P2-1 那个
|
||||
/// 真实缺陷:窗口在第 3 帧消失时,`window::frames()` 不再产消息 ⇒ 帧超时兜底**求值不到**,
|
||||
/// 而 iced_winit 对「最后一个窗口 Destroyed」直接退出 ⇒ 曾经是 **exit 0 + 没有 PNG**。
|
||||
pub(super) fn no_evidence(written: u32) -> bool {
|
||||
written == 0
|
||||
}
|
||||
|
||||
/// 启动(阻塞到出图或关窗)。
|
||||
///
|
||||
/// **取证通道的硬规则(M97)**:宁可红着退出,也不能悄悄换一种后端继续出图(以前 wgpu 挂掉时
|
||||
@@ -97,9 +108,15 @@ pub fn run(request: Request) -> Result<(), String> {
|
||||
state
|
||||
});
|
||||
|
||||
// 落盘计数器(终态不变量 + 日志共用):只在 `shot::save` 成功后自增。
|
||||
let written = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let application = |backend: renderer::Kind| {
|
||||
let boot = request.clone();
|
||||
let appearance = appearance.clone();
|
||||
// 落盘次数要能被 `run()` 读到(终态不变量用它),所以走共享计数器而不是 `Harness` 的私有字段:
|
||||
// 值只在 `shot::save` **成功**之后自增,是「真的写出去了」而不是「我们打算写」(U1)。
|
||||
let written = Arc::clone(&written);
|
||||
iced::application(
|
||||
move || {
|
||||
let mut model = demo_model(boot.screen, boot.width);
|
||||
@@ -120,7 +137,7 @@ pub fn run(request: Request) -> Result<(), String> {
|
||||
placed: None,
|
||||
frames: 0,
|
||||
dispatched: false,
|
||||
writes: 0,
|
||||
written: Arc::clone(&written),
|
||||
pointer: 0,
|
||||
started,
|
||||
first_frame: None,
|
||||
@@ -182,6 +199,21 @@ pub fn run(request: Request) -> Result<(), String> {
|
||||
|
||||
outcome?;
|
||||
|
||||
// **终态不变量(M100 r3 P2-1)**:取证跑完了却一张图都没落盘 = 失败。
|
||||
//
|
||||
// 为什么要有这一条(复审变异实测):兜底原来只有「帧数超时」,而 `window::frames()` 只在
|
||||
// `Event::Window(RedrawRequested)` 时产消息 —— 窗口一旦消失就不再 redraw ⇒ `Message::Frame`
|
||||
// 不再产生 ⇒ `capture_stalled` **永远求值不到**;同时 iced_winit 对「最后一个窗口 Destroyed」
|
||||
// 直接 `Control::Exit`,`run()` 返回 `Ok(())`。表现是 **exit 0 + 没有 PNG**,与成功跑同一形状
|
||||
// 只少了出图那几行 —— 只看退出码的脚本会读到「成功 + 零证据」。
|
||||
//
|
||||
// 所以判据从「超时」升级成「终态」:**跑完了没有图就是失败**。它比超时强,因为覆盖
|
||||
// 「帧不再产生」这一类;帧超时那条保留,管「有帧但采样回不来」。
|
||||
if no_evidence(written.load(Ordering::Relaxed)) {
|
||||
eprintln!("[harness] 取证结束但没有任何图落盘(窗口提前消失 / 落盘失败),按失败退出");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[harness] 用时 {:.0} ms",
|
||||
started.elapsed().as_secs_f64() * 1000.0
|
||||
|
||||
Reference in new issue
Block a user