fix(app-iced): M100 r4 P2-1 —— 终态判据加「取证模式」前提,交互预览关窗不再 exit 1
复审在 `d298691` 的导出上实测:交互预览模式(不给 `--screenshot`)关窗会被新判据判成失败, exit 1 + 一句「取证结束但没有任何图落盘(窗口提前消失 / 落盘失败)」的**假消息**。 那是把「从未要求出图」判成「取证失败」,退出码因此失去区分力 (真崩溃 / 真取证失败 / 正常关个预览窗全是 1)。 - `no_evidence(capture_mode, written) = capture_mode && written == 0`;调用点传 `request.screenshot.is_some()`。判据里写明:交互预览模式下 `subscription` 是 `None`、 永远不会有 `Captured`,所以「没写盘」在那里不是失败。 - 注释补一条「为什么用计数器而不是 `path.exists()`」:旧 PNG 会让 exists 骗过判据。 - 断言改名 `finishing_without_a_single_write_is_a_failure_only_while_capturing`, 三条:`(false,0)=false`(这次的红线)/ `(true,0)=true` / `(true,1)=false`。 顺手 p3: - p3-1 `CAPTURE_GIVE_UP_FRAMES` 注释删掉「约 3~4 秒(~120ms/帧)」这个没测准的数字, 改成写明帧间隔量级差异(复审实测 2~3ms/帧 vs 本机 `[perf]` 的 ≈200~300ms/帧), 「要调按第 3 帧派发→第 4~5 帧落盘的结构关系调,别按秒」。 - p3-2 旧字段名 `writes` 的注释改成「这个计数器(旧字段名叫 `writes`)」;顺带把 `capture_stalled` 的形参 `writes` 改名 `written`,与字段对齐。 - p3-3 `examples/pages_demo.rs` 的 usage 漏 `--appearance`/`--passthrough`:该文件**不在本 mission scope**(scope 只有 `pages/harness*`),不扩 scope,欠账仍记在已开的 finding 里。 实测: - 交互预览(WM_CLOSE 关窗,取真实退出码):最终二进制 5/5 有效跑 exit=0、`没有任何图落盘` 行 0; 另 2 次 exit=0xC000013A(STATUS_CONTROL_C_EXIT,控制台 Ctrl 事件打断,日志停在半途)不计。 修前同场景 4/4 exit=1。 - M3 变异(第 3 帧 `window::screenshot` → `window::close`)3/3 exit=1 + 具名行(没被误伤)。 - M4 变异(`CAPTURE_GIVE_UP_FRAMES` 30→0)2/2 exit=1 走「采样超时」(没被误伤)。 - 正常取证 `--appearance=real` ×6:exit=0 / png=yes / `落盘次数=1` / `passthrough_ext=on` / 错误行 0 / sha256 `414a71fcb77e9b38…`(与 r1~r4 基线逐字节相同,像素没动)。
This commit is contained in:
1 parent
d298691c17
commit
f67b24cc7b
3 files changed
+45
-20
No files matched your search
@@ -97,15 +97,18 @@ fn should_request_capture(frames: u32, dispatched: bool, capture_mode: bool) ->
|
||||
/// ⇒ 这里的判据根本求值不到**。那一路由 `run()` 里的**终态不变量**兜住(跑完没图 ⇒ 红着退出);
|
||||
/// 而后端报错是另一回事:`run()` 直接返回 `Err`,本来就是非 0。
|
||||
///
|
||||
/// 30 帧在取证窗口里约 3~4 秒(实测 ~120ms/帧),远大于正常路径「第 3 帧派发 → 第 4~5 帧落盘」。
|
||||
/// 30 帧**不换算成秒**(M100 r4 P3-1):帧间隔在不同机器/负载下量级差异极大——复审实测 2~3ms/帧,
|
||||
/// 而本机 `[perf]` 日志里是 `frames=4 elapsed=1133ms` / `frames=5 elapsed=1605ms`(≈200~300ms/帧)。
|
||||
/// 所以这里只承诺「若干帧的宽限」,不写时间预算;要调就按「正常路径是第 3 帧派发、第 4~5 帧落盘」
|
||||
/// 这个结构关系调,别按秒。
|
||||
const CAPTURE_GIVE_UP_FRAMES: u32 = 30;
|
||||
|
||||
/// 采样是不是**卡住了**:过了宽限帧数,一次都没落盘。
|
||||
///
|
||||
/// 不区分「一直没派发」(窗口始终没就绪)与「派发了没回来」——两者对使用者的表现都是
|
||||
/// 「没有图」,都该红着退出而不是挂死。
|
||||
fn capture_stalled(frames: u32, writes: u32) -> bool {
|
||||
writes == 0 && frames > CAPTURE_AT_FRAME + CAPTURE_GIVE_UP_FRAMES
|
||||
fn capture_stalled(frames: u32, written: u32) -> bool {
|
||||
written == 0 && frames > CAPTURE_AT_FRAME + CAPTURE_GIVE_UP_FRAMES
|
||||
}
|
||||
|
||||
struct Harness {
|
||||
@@ -246,8 +249,8 @@ impl Harness {
|
||||
let Some(path) = self.request.screenshot.clone() else {
|
||||
return Task::none();
|
||||
};
|
||||
// 只在真的写成功时计数(U1):`writes` 若在 save 之前自增,一次落盘失败也会被
|
||||
// 记成「有图了」,而 `run()` 的终态不变量正是拿它判「有没有证据」。
|
||||
// 只在真的写成功时计数(U1):这个计数器(旧字段名叫 `writes`)若在 save **之前**
|
||||
// 自增,一次落盘失败也会被记成「有图了」,而 `run()` 的终态不变量正是拿它判「有没有证据」。
|
||||
match shot::save(&screenshot, &path) {
|
||||
Ok(summary) => {
|
||||
eprintln!("{}", summary.describe());
|
||||
|
||||
@@ -22,13 +22,26 @@ 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
|
||||
///
|
||||
/// ## 为什么必须有 `capture_mode` 前提(M100 r4 P2-1)
|
||||
///
|
||||
/// 不给 `--screenshot` 是文档明确支持的**交互预览模式**(「没给 `--screenshot` 就只是开窗看」):
|
||||
/// 该模式 `subscription()` 直接 `Subscription::none()`,**永远不会有 `Captured`** ⇒ `written` 恒 0。
|
||||
/// 没有这个前提,用户**正常关掉预览窗**会拿到 exit 1 和一句「取证结束但没有任何图落盘」的
|
||||
/// 假消息——那是把"从未要求出图"判成"取证失败",通道的退出码就此失去区分力
|
||||
/// (真崩溃 / 真取证失败 / 正常关个预览窗全是 1)。
|
||||
///
|
||||
/// ## 为什么用计数器而不是 `path.exists()`
|
||||
///
|
||||
/// `written` 是**本进程**成功落盘的次数,不会被执行前就存在的旧 PNG 骗过;换成「目标文件存在」
|
||||
/// 会在脏路径下给出「exit 0 + 上一次的旧图」。这一点写在这里,免得后来人「顺手改成 exists」。
|
||||
pub(super) fn no_evidence(capture_mode: bool, written: u32) -> bool {
|
||||
capture_mode && written == 0
|
||||
}
|
||||
|
||||
/// 启动(阻塞到出图或关窗)。
|
||||
@@ -209,7 +222,13 @@ pub fn run(request: Request) -> Result<(), String> {
|
||||
//
|
||||
// 所以判据从「超时」升级成「终态」:**跑完了没有图就是失败**。它比超时强,因为覆盖
|
||||
// 「帧不再产生」这一类;帧超时那条保留,管「有帧但采样回不来」。
|
||||
if no_evidence(written.load(Ordering::Relaxed)) {
|
||||
//
|
||||
// **前提是这次真的是取证运行**(M100 r4 P2-1):不给 `--screenshot` 的交互预览模式本来
|
||||
// 就不出图,判成失败会打出假消息、并让退出码失去区分力。前提写在判据里,不写在这里。
|
||||
if no_evidence(
|
||||
request.screenshot.is_some(),
|
||||
written.load(Ordering::Relaxed),
|
||||
) {
|
||||
eprintln!("[harness] 取证结束但没有任何图落盘(窗口提前消失 / 落盘失败),按失败退出");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -222,23 +222,26 @@ fn the_stall_guard_fires_only_after_the_grace_window() {
|
||||
assert!(grace >= 10, "宽限帧数 {grace} 太小,正常路径会误触发");
|
||||
}
|
||||
|
||||
/// **终态不变量:跑完了没有图就是失败**(M100 r3 P2-1)。
|
||||
/// **终态不变量:跑完了没有图就是失败 —— 但只在取证运行里这么判**(r3 P2-1 → r4 P2-1)。
|
||||
///
|
||||
/// 抓得住:谁把 `run()` 里那条后置检查删掉/改弱(删掉之后,窗口在第 3 帧消失会让进程
|
||||
/// **exit 0 且没有 PNG** —— 复审变异实测就是这个形状,只看退出码的脚本会读到「成功 + 零证据」)。
|
||||
/// 抓不住:这条检查**有没有被走到**(窗口消失的时序要真窗口,见报告里的变异复现);
|
||||
/// 也抓不住「图写到了错误的路径」(那要校验目标文件,属另一件事)。
|
||||
/// 抓得住:① 判据被改成恒 false(终态检查成摆设);② **判据丢掉 `capture_mode` 前提**
|
||||
/// (r4 实测到的真缺陷:不给 `--screenshot` 的交互预览模式本来就不出图,却会被判失败 ⇒
|
||||
/// exit 1 + 一句「取证结束但没有任何图落盘」的假消息,退出码因此失去区分力)。
|
||||
/// 抓不住:`run()` 里那个**调用点**被删掉(判据还在、本测试照绿)——那一层只能靠变异实测
|
||||
/// (M3:第 3 帧改关窗 ⇒ 期望 exit 1),测试替不了。
|
||||
#[test]
|
||||
fn finishing_without_a_single_write_is_a_failure() {
|
||||
fn finishing_without_a_single_write_is_a_failure_only_while_capturing() {
|
||||
use super::app::no_evidence;
|
||||
|
||||
// 红线(r4 P2-1):交互预览模式关窗**不是**失败
|
||||
assert!(
|
||||
no_evidence(0),
|
||||
"一张图都没落盘时,取证结束必须判失败(不许 exit 0)"
|
||||
!no_evidence(false, 0),
|
||||
"交互预览模式(没给 --screenshot)从未要求出图,关窗必须 exit 0"
|
||||
);
|
||||
assert!(!no_evidence(1), "落过盘就算有证据");
|
||||
assert!(
|
||||
!no_evidence(2),
|
||||
"重复落盘也算有证据(虽然不该发生,见一次性闸门)"
|
||||
no_evidence(true, 0),
|
||||
"取证运行一张图都没落盘 ⇒ 必须判失败(不许 exit 0)"
|
||||
);
|
||||
assert!(!no_evidence(true, 1), "落过盘就算有证据");
|
||||
assert!(!no_evidence(false, 1), "预览模式更不该报错");
|
||||
}
|
||||
Reference in new issue
Block a user