test(core): 「哪些键真的交给日志」补自动化证据(M244 r2 P2-1)
复审 P2-1:旧用例只断言 `collect_fresh`(纯函数)与 `unknown_keys()`(表级清单), 没人断言 `report` **真的**只把「新键」交给 sink;把 `for key in fresh` 改成 `for key in unknown`(每次渲染重复写日志)当时 6 条用例全绿,而「同键只报一次」正是判据 ③。 - `report_with(&self, unknown, sink: impl FnMut(&str))`:`report` 变成 `report_with(unknown, emit)`,`emit` 仍是 `cfg(test)` 空实现(不碰进程级单例,flake 不回潮); - 新用例 `report_emits_only_fresh_keys`:假 sink 收集,断言一次渲染内重复只交一次、 跨渲染只有新键才交、顺序与 `collect_fresh` 一致; - 顺手收掉自己列的边角:`unknown_log_line` 先把键名里的 `\r`/`\n` 换成空格 (`launcher.log` 是行导向的,恶意清单塞换行能把一个键变成两行假日志), 新用例 `log_lines_never_break_into_two_lines`。 变异自证(各自 exit 101,均已还原、文件 sha256 复原): - `for key in &fresh` → `for key in unknown` ⇒ `report_emits_only_fresh_keys` 红, `left: ["a", "a"] right: ["a"]`; - 删掉 `key.replace(['\r','\n'], " ")` 那一行 ⇒ `log_lines_never_break_into_two_lines` 红, `left: 2 right: 1`。 门禁:fmt / check -p unlucky-core --all-targets / test -p unlucky-core -p unlucky-cli (1678 passed / 0 failed)/ clippy --workspace --all-targets -- -D warnings 全绿。
This commit is contained in:
1 parent
60607ae7b3
commit
c97ec293c3
2 files changed
+69
-8
No files matched your search
@@ -8,7 +8,8 @@
|
||||
//! flake——实测过一次:本模块的几行把 `launcher_log` 的 `缓冲没有上限:205 行` 顶红了。
|
||||
//! 所以 [`emit`](super::emit) 在测试构建里是空实现,落盘那一环用一次性探针
|
||||
//! (`--test-threads=1`,跑完即删)手工验过。这里断言的是它的上游——行文本
|
||||
//! ([`unknown_log_line`])与「哪些键该记」([`collect_fresh`])——两段合起来就是「记什么」。
|
||||
//! ([`unknown_log_line`])、「哪些键算新」([`collect_fresh`])、「新键怎么交给 sink」
|
||||
//! ([`PlaceholderMap::report_with`],用假 sink)——三段合起来就是「记什么、记几次」。
|
||||
|
||||
use super::{collect_fresh, near_miss, unknown_log_line, PlaceholderMap};
|
||||
use crate::launch::command_builder::CommandBuilder;
|
||||
@@ -135,3 +136,50 @@ fn rendering_semantics_are_unchanged() {
|
||||
"空键也是未登记键(原样保留)"
|
||||
);
|
||||
}
|
||||
|
||||
/// ③ 的「真的交给了日志」这一层:`report_with` 只把**本次新**见到的键递给你的 sink。
|
||||
///
|
||||
/// 变异证明:把 `report_with` 里的 `for key in &fresh` 换成 `for key in unknown`(每次渲染都
|
||||
/// 重复写日志)⇒ 本条红(`left: ["a", "a", …]`)——「一个键只报一次、不刷屏」被守住的地方
|
||||
/// 就在这里,而不是在 `collect_fresh` 的用例里。
|
||||
#[test]
|
||||
fn report_emits_only_fresh_keys() {
|
||||
let map = PlaceholderMap::new();
|
||||
let mut delivered: Vec<String> = Vec::new();
|
||||
map.report_with(&strings(&["a", "a"]), |key| delivered.push(key.to_string()));
|
||||
assert_eq!(delivered, strings(&["a"]), "一次渲染内的重复只交一次");
|
||||
map.report_with(&strings(&["a", "b"]), |key| delivered.push(key.to_string()));
|
||||
assert_eq!(
|
||||
delivered,
|
||||
strings(&["a", "b"]),
|
||||
"跨渲染只有新键才交,且保序"
|
||||
);
|
||||
assert_eq!(
|
||||
map.unknown_keys(),
|
||||
strings(&["a", "b"]),
|
||||
"表级清单与 sink 同一份"
|
||||
);
|
||||
|
||||
// 顺序口径与 `collect_fresh` 一致(同一份规则的两处实现不能漂)。
|
||||
let mut fresh_seen = Vec::new();
|
||||
assert_eq!(
|
||||
collect_fresh(&mut fresh_seen, &strings(&["a", "a"])),
|
||||
strings(&["a"])
|
||||
);
|
||||
assert_eq!(
|
||||
collect_fresh(&mut fresh_seen, &strings(&["a", "b"])),
|
||||
strings(&["b"])
|
||||
);
|
||||
}
|
||||
|
||||
/// 日志是**行导向**的:恶意/畸形清单可以在 JSON 里塞换行,一个键不能变成两行假日志。
|
||||
#[test]
|
||||
fn log_lines_never_break_into_two_lines() {
|
||||
let line = unknown_log_line("evil\n2026-01-01T00:00:00Z [launch] 伪造行\r");
|
||||
assert_eq!(line.lines().count(), 1, "{line}");
|
||||
assert!(!line.contains('\n') && !line.contains('\r'), "{line}");
|
||||
assert!(
|
||||
line.contains("evil 2026-01-01T00:00:00Z [launch] 伪造行"),
|
||||
"{line}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user