From 634955d32339a48fa12672c78f88706bfbae283a Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 12 Nov 2026 11:28:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(app-iced):=20=E6=8E=A2=E9=92=88=E4=B8=8E?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E6=B5=8B=E8=AF=95=E7=9A=84=E5=AD=90=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E6=94=B9=E8=B5=B0=20platform::child::quiet=EF=BC=88M2?= =?UTF-8?q?72=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cargo test --workspace` 在合并 tip f8d83d1 上被 child_quiet_guard 抓住两处绕开 `platform::child::quiet` 的子进程构造: ① `platform/motion.rs` 的 OS「减少动效」探针(M256 新增,Linux gsettings / macOS defaults)是产品代码真回归:`run()` 里裸 `Command::new(program)`。改走 `quiet(program)`。 `quiet` 返回的就是普通 `Command`,`.spawn()` 的 `Child` 仍归 `run` 自己,所以 **300ms 超时 + 超时 kill + wait 一个字没丢**;`OnceLock` 缓存、单调用点、不在帧路径 这三条性质也原样保留(这次只换了构造器)。 ② `fonts/tests.rs` 里重起自身 test binary 的那处同样改走 `quiet`;`.output()` 的管道 语义不变(已实测子进程断言仍然「1 passed」)。没有往 ALLOWED_ROLES 加白。 --- crates/app-iced/src/fonts/tests.rs | 6 +++++- crates/app-iced/src/platform/motion.rs | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/app-iced/src/fonts/tests.rs b/crates/app-iced/src/fonts/tests.rs index 8d565ec..26ca8e0 100644 --- a/crates/app-iced/src/fonts/tests.rs +++ b/crates/app-iced/src/fonts/tests.rs @@ -270,10 +270,14 @@ fn evaluation_starts_no_download() { } /// 用 `current_exe()` 把本 test binary 再起一次,只跑 [`resolving_fonts_never_starts_a_download`]。 +/// +/// 子进程经 `unlucky_core::platform::child::quiet` 起(M272):仓规是全树硬约束,测试代码不是 +/// 例外——这条 test binary 本身是控制台程序,父进程将来若换到 GUI 侧(或 CI 用别的方式直接跑 +/// 二进制)也照样不该新建窗口。`quiet` 只动创建 flag,`.output()` 的管道语义一个字不变。 fn rerun_in_own_process() { let exe = std::env::current_exe().expect("取本 test binary 的路径"); let name = "fonts::tests::resolving_fonts_never_starts_a_download"; - let output = std::process::Command::new(&exe) + let output = unlucky_core::platform::child::quiet(&exe) .args(["--exact", "--nocapture", "--test-threads=1", name]) .env(CHILD_ENV, "1") // 截图通道的钉子会直接替换 `status()` 的返回值(`install.rs:157` 先看它), diff --git a/crates/app-iced/src/platform/motion.rs b/crates/app-iced/src/platform/motion.rs index b5ea1d1..99bc4f1 100644 --- a/crates/app-iced/src/platform/motion.rs +++ b/crates/app-iced/src/platform/motion.rs @@ -23,6 +23,15 @@ //! 它在 `on_boot` 跑一次;配置重载时读的是同一个缓存,不会重复 spawn)。 //! - 子进程有 [`COMMAND_TIMEOUT`] 上限:`gsettings` / `defaults` 在坏环境里会挂住, //! 超时按 `Unknown` 处理并 `kill` 掉。 +//! - 子进程经 `unlucky_core::platform::child::quiet` 起(M272):仓规「spawn 控制台程序必须走 +//! `quiet`」是全树硬约束(`crates/core/tests/child_quiet_guard.rs` 钉着),探针不是例外—— +//! 唯一调用点在这里。 +//! +//! ## `quiet` 不影响「可杀」 +//! +//! `child::quiet` 返回的就是普通 [`std::process::Command`](Windows 上多一个 +//! `CREATE_NO_WINDOW`,其它平台原样返回),`.spawn()` 拿到的 [`std::process::Child`] 仍归 +//! `run` 自己,所以 **300 ms 超时 + 超时 `kill` + `wait` 一个字没丢**。 //! //! | 平台 | 来源 | `unsafe` | //! |---|---|---| @@ -33,6 +42,9 @@ use std::sync::OnceLock; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use unlucky_core::platform::child::quiet; + /// 系统偏好:「减少动效」是否要生效。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReduceMotion { @@ -169,13 +181,17 @@ fn detect() -> ReduceMotion { /// /// 为什么自己写而不是 `Command::output()`:`output()` 会一直等到子进程退出,坏环境里(没有 /// session bus 的 `gsettings`)就是无限期挂住——那会让**启动**卡死,是比"没有动效"严重得多的事故。 +/// +/// 子进程经 [`quiet`] 起(M272,仓规硬约束)。`quiet` 只改创建 flag、返回的仍是普通 +/// `std::process::Command`,所以下面这套自己写的等待循环拿得到 `Child` 句柄,**超时 `kill` +/// 照旧**——「安静」与「可杀」在这里不冲突。 #[cfg(any(target_os = "linux", target_os = "macos"))] fn run(program: &str, args: &[&str]) -> Option { use std::io::Read; - use std::process::{Command, Stdio}; + use std::process::Stdio; use std::time::{Duration, Instant}; - let mut child = Command::new(program) + let mut child = quiet(program) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped())