fix(m198): JAVAGUARD——首屏自动扫 / 子进程回收 / 参数长度三条守卫 + 设置页正文结构断言

- 首屏自动扫:删掉 `cfg!(test)` 分叉,生产入口在测试里真跑;新增
  `the_first_hydrate_starts_a_real_scan_by_itself`(删掉那一行、或把实参翻成 `true`
  都红)。顺手删掉恒为假的 `seeded && !live`:截图种子档在上方就 return 了,编译器也
  报 "assigned but never used"。
- proc:`kill()` + `wait()` 收进 `terminate()`(三个分支共用);用例先用同一条命令
  真跑一次证明子进程起得来(`spawn().ok()?` 的空跑假绿),并新增 Linux/Android 的
  僵尸回收用例(只 kill 不 wait ⇒ `/proc/<pid>` 留成僵尸)。
- java.extra_args:`MAX_JVM_ARGS = 1024` 镜像 schema,`Refusal::JvmTooLong` + 中英
  文案 + JVM 输入框变红;App 级用例断言回执是具名的「太长」而不是通用的「写不进去」。
- 设置页正文:判据①从「数 `stack = stack.push(` 的出现次数」升级成按角色断言
  (唯一来源 section_columns、`push(` 恰一次且实参是循环变量、尾表达式、禁
  extend/chain/once/concat/append),单行折叠变异体现在会红。
This commit is contained in:
AskaEth committed 2026-11-09 06:46:29 +08:00
1 parent 7f697f5762
commit aae9a233ff
9 files changed
+385 -81

No files matched your search

+59 -17
View File
@@ -230,8 +230,8 @@ fn setting_row<'a>(ui: &'a Ui, label: &'a str, value: &'a str) -> Element<'a, Me
/// 正文由两半拼成:`mod.rs` 的 [`section_columns`](super::section_columns) **生产**「每组一件」的列,
/// `form` 只把它们堆起来再滚出去。于是:
///
/// - [`the_form_stops_at_its_last_group`] 守**收尾**:`form()` 里 `stack = stack.push(` 恰好一处、
/// 且以 `frame::scroll(ui, stack)` 收尾 —— 尾部多一件、或整段换成别的拼装(push 数变 0)都红;
/// - [`the_form_stacks_nothing_but_its_sections`] 守**收尾**:正文的唯一来源是 `section_columns`
/// (`push(` 恰好一次、实参必须是循环变量、尾表达式是 `frame::scroll`)—— 多一件就红;
/// - [`the_form_holds_exactly_one_column_per_group`] 守**生产**:真调 `section_columns` 数它交出的
/// 列数(窄窗 == `GROUPS.len()`,单选各组 == 1)—— 多塞一件就红,**不管那件叫什么名字**。
///
@@ -244,9 +244,11 @@ fn setting_row<'a>(ui: &'a Ui, label: &'a str, value: &'a str) -> Element<'a, Me
///
/// ## 抓得住 / 抓不住(据实写)
///
/// 抓得住(三种变异都实测过):①按原样把卡加回循环之后(`stack = stack.push(...)`)⇒ 收尾那条红;
/// ②改成「塞进列清单」(`section_columns` 里多 push 一件)⇒ 列数那条红,换任何名字都躲不掉;
/// ③把拼装整段重写成别的形状(不再用上面那个惯用法)⇒ 收尾那条数的 1 变成 0,同样红。
/// 抓得住(四种变异都实测过):①把卡折叠回循环
/// (`stack = stack.push(section).push(frame::demo_card(…))`)⇒ 收尾那条数到 2 处 `push(`,红
/// (与 `cargo fmt` 是否拆行无关);②换成别的来源(`stack = stack.push(frame::text_role(…))`)⇒
/// 实参不是 `section`,红;③塞进列清单(`section_columns` 里多 push 一件)⇒ 列数那条红;
/// ④整段改写成 `extend` / `chain` / 尾表达式再包一层 ⇒ 收尾那条红。
/// 抓不住:**某一组自己的列里**换内容(比如某个分组真的需要自己的空态)。那不是本缺陷——本页允许
/// 组内有空态,前提是它长在该组的列里、用本页语义的文案。`DevDocs/artifacts/m196/` 那组图是**当时**
/// 的证据,不是将来的防线(静态图只能证明「现在没有」)。
@@ -287,6 +289,15 @@ mod no_stray_empty_state {
Ui::with_platform(SchemeFlavor::Light, Platform::Windows, FAMILIES)
}
/// `form()` 的函数体,**剥掉每行 `//` 之后的部分**:判据数的是代码,注释里的 `push(` 不算。
fn form_code() -> String {
form_body()
.lines()
.map(|line| line.split("//").next().unwrap_or_default())
.collect::<Vec<_>>()
.join("\n")
}
/// 演示 / harness 路径那份只读状态(与 `pages::page` 同源,不伪造第二份)。
fn states() -> States<'static> {
States {
@@ -295,24 +306,55 @@ mod no_stray_empty_state {
}
}
/// 判据 ①(**收尾**):循环之后没有任何额外的 `push`,且函数以滚出去收尾。
/// 判据 ①(**收尾**):正文的柱子只有一个来源——`section_columns` 交出的那一列。
///
/// 循环体里只有一件(一组一列),所以「`stack = stack.push(` 恰好一次」等于「尾部空着」;
/// 拼装被整段重写成别的形状时计数变 0,同样红。
/// M195 r2 复审实测的盲区:原来数的是 `stack = stack.push(` 的出现**次数**,于是
/// `stack = stack.push(section).push(frame::demo_card(ui, &[]));` 一句折叠里塞第二件时计数仍是 1
/// (第二个 `.push(` 前面没有 `stack = `)⇒ 判据 ① 与 ② 同时绿。那是在数**写法**。现在按**角色**
/// 断言:循环头直接迭代 `section_columns`、`push(` 恰好一次且实参是那个循环变量、尾表达式恰好是
/// `frame::scroll(ui, stack)`、没有 `extend`/`chain`/`once`/`concat`/`append`。`push(` 按**次数**
/// 数(拆行不影响),注释里的不算(先剥掉每行 `//`)。变异证明见模块文档的「抓得住」。
#[test]
fn the_form_stops_at_its_last_group() {
let body = form_body();
let pushes = body.matches("stack = stack.push(").count();
fn the_form_stacks_nothing_but_its_sections() {
let body = form_code();
assert!(
body.contains("for section in section_columns("),
"`form()` 的循环头必须直接迭代 `section_columns(...)`:正文节点的唯一来源就是它"
);
let pushes = body.matches("push(").count();
assert_eq!(
pushes, 1,
"`form()` 里出现了 {pushes} 处 `stack = stack.push(`:分组循环里只该有一处。\
尾部又被叠了一件东西的话,设置页每一组下面都会多出同一件(M196 那张\
「没有逐实例覆盖」的卡就是这么来的)"
"`form()` 里出现了 {pushes} 处 `push(`:分组循环里只该有一处。多出来的那一处就是\
「每组下面多一件」(M196 那张卡)——同一句里折叠成 `.push(a).push(b)` 也算两处"
);
assert!(
body.contains(" frame::scroll(ui, stack)\n}"),
let at = body.find("push(").expect("上面刚数过一次") + "push(".len();
let argument = body[at..].split(')').next().unwrap_or_default().trim();
assert_eq!(
argument, "section",
"被压进正文的必须是 `section_columns` 交出的那一列(循环变量 `section`),实际是 \
`{argument}`:别的来源(演示卡 / 空态 / 任何控件)都不许进正文"
);
for append in ["extend(", "chain(", "once(", "concat(", "append("] {
assert!(
!body.contains(append),
"`form()` 用了 `{append}`:正文只许从 `section_columns` 逐列 push 进来"
);
}
let tail: Vec<&str> = body
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.collect();
assert_eq!(
tail.iter().rev().take(2).copied().collect::<Vec<_>>(),
vec!["}", "frame::scroll(ui, stack)"],
"`form()` 必须以 `frame::scroll(ui, stack)` 收尾(后面紧跟函数右花括号):\
正文到最后一组就结束,别再往后叠东西"
正文到最后一组就结束,别再包一层或往后叠东西"
);
}