test(m262): 三条平台/时序依赖的测试改成确定性判据
① prefs.rs:custom 落点不再写死 D:/JDKs(Linux 上它是相对路径,会被拼到数据根后, 同一条断言在两个平台上验的是两件事)。改由临时目录给绝对路径;另加一条相对路径 必须按数据根解析的断言,把上一版抓不住的那一半补上。 ② fonts/tests.rs:install::status() 读的是进程级静态槽,同 binary 的 page_messages 用例切 zh_cn 会真的发起下载并把它写成 Downloading ⇒ 前后快照比较与谁并行谁赢。 改成用 current_exe() 在**只有本用例的进程**里量,并断言前后逐值相等(任何写槽都算越界)。 ③ fixture_cases.rs:目录 mtime 不再赌「写文件恰好落在另一个 tick」,改为显式钉 mtime (t0/t1 整秒,对任何 filetime 粒度无余数)并逐 ns 比对。 三条都补了「抓得住什么 / 抓不住什么」。
This commit is contained in:
1 parent
98eb4c14c1
commit
d4959fd3d3
3 files changed
+259
-13
No files matched your search
@@ -179,12 +179,59 @@ fn type_roles_pick_the_display_stack_for_big_text() {
|
||||
/// 用 `resolve_with_faces` 而不是 `resolve_with`:`system_faces()` 不可注入,拿不到「一台没有
|
||||
/// 中文字体的机器」。这里给一张只有拉丁族的脸表 ⇒ 中文环境判出 `NeedsDownload`。
|
||||
///
|
||||
/// 抓得住:把 `install::after_probe(cjk_report.strategy)` 加回求值路径(那正是 r2 判为 bug 的写法)
|
||||
/// ——状态槽会从 `NotNeeded`/`Unknown` 变成 `Downloading`,下面那条断言当场红。
|
||||
/// 复审 r3 实测:加回去整套 `unlucky-app-iced --lib` **687 passed / 0 failed**(当时没有这条)。
|
||||
/// 抓不住:`App` 构造之后 `log_cjk` 那一拍会不会触发(那是 `cjk_for` 的职责,且它本来就该触发)。
|
||||
/// ## 为什么这条现在要在**自己的进程**里量(M262,ubuntu CI 真红)
|
||||
///
|
||||
/// `install::status()` 读的是**进程级**的静态槽:`install.rs:146` 的
|
||||
/// `static STATE: OnceLock<Mutex<CjkInstall>>`。而同一个 test binary(`unlucky-app-iced --lib`)
|
||||
/// 里还有别的用例会**写**它,路径如下:
|
||||
///
|
||||
/// ```text
|
||||
/// app/update/page_messages.rs:206 the_language_message_switches_the_catalog_and_rebuilds_the_view
|
||||
/// → App::update(Message::Language(Fixed(ZhCn)))
|
||||
/// → app/update/page_messages.rs:146 set_language → ui/lang.rs:135 log_cjk
|
||||
/// → fonts.rs:266 cjk_for(Some("zh_cn")) → install.rs:311 after_probe
|
||||
/// → install.rs:317-318 Step::Start → install/worker.rs:74 update(Downloading{bytes})
|
||||
/// ```
|
||||
///
|
||||
/// 触发条件:那个用例用 `Options::default()`(`screenshot: None` ⇒ `construct.rs:106`
|
||||
/// `set_offline(false)`)构造 App,而机器上**没有**中文字体时 `zh_cn` 判出 `NeedsDownload`。
|
||||
/// ubuntu runner 正是这一档(Windows 开发机装了中文字体,策略停在 `System`,所以本地看不出来)。
|
||||
/// 之后那条后台线程会真去拉 16.9 MiB(`install/worker.rs:88`),槽在下载期间一直是
|
||||
/// `Downloading`。它和本用例**并行**跑(默认多线程 harness),于是本用例读到的 `after`
|
||||
/// 完全可能是**别人**在飞的那条下载。
|
||||
///
|
||||
/// 上一版直接比 `install::status()` 的前后快照 = 拿一把全进程共享的尺子量,谁并行谁赢。
|
||||
/// 修法不是放宽断言,而是**把尺子放进只有本用例的进程**:`current_exe()` + `--exact`
|
||||
/// 重新起一次本 test binary、只跑这一条(`ULL_FONT_PURITY_CHILD=1` 防递归)。子进程里槽的初值
|
||||
/// 就是 `Unknown`,没有谁跟它抢,判据因此是确定性的。
|
||||
///
|
||||
/// 抓得住:
|
||||
/// - 把 `install::after_probe(cjk_report.strategy)` 加回求值路径(r2 判为 bug 的写法)——
|
||||
/// 子进程里槽从 `Unknown` 变成 `Downloading { bytes: 17772300 }`,`!after.is_downloading()`
|
||||
/// 当场红(实测,见报告「变异证明」);
|
||||
/// - 更宽的回归:求值路径**任何**写槽的动作(连 `Unknown → NotNeeded` 这种「无害改写」也算),
|
||||
/// 由「前后必须逐值相等」那条守;
|
||||
/// - 判据本身失效(这张脸表不再判出 `NeedsDownload`)—— 单独一条断言,免得上面两条变成假绿。
|
||||
///
|
||||
/// 抓不住:
|
||||
/// - 别的用例会不会写槽(本用例主动换进程,**不再**替全进程背书);
|
||||
/// - `App` 构造之后 `log_cjk` 那一拍会不会触发(那是 `cjk_for` 的职责,且它本来就该触发);
|
||||
/// - 起进程本身失败(`current_exe` 拿不到、禁 fork 的环境)——那时是**红**,不是软过;
|
||||
/// - 子进程里跑了「几条」这件事只由 stdout 的 `1 passed` 兜着(改名会让它红,而不是变 0 条假绿)。
|
||||
#[test]
|
||||
fn resolving_fonts_never_starts_a_download() {
|
||||
if std::env::var_os(CHILD_ENV).is_some() {
|
||||
evaluation_starts_no_download();
|
||||
return;
|
||||
}
|
||||
rerun_in_own_process();
|
||||
}
|
||||
|
||||
/// 子进程标记(防递归:父进程起子进程,子进程直接跑判据)。
|
||||
const CHILD_ENV: &str = "ULL_FONT_PURITY_CHILD";
|
||||
|
||||
/// 判据本体(只在**没有别的用例抢全局槽**的进程里跑)。
|
||||
fn evaluation_starts_no_download() {
|
||||
use unlucky_i18n::font::Strategy;
|
||||
|
||||
use crate::fonts::install;
|
||||
@@ -202,13 +249,48 @@ fn resolving_fonts_never_starts_a_download() {
|
||||
let resolved = resolve_with_faces(Some("zh_cn"), Some(&latin));
|
||||
let after = install::status();
|
||||
|
||||
assert_eq!(
|
||||
before,
|
||||
install::CjkInstall::Unknown,
|
||||
"隔离失效:这个进程里槽的初值已被谁改过({before:?}),下面的前后比较就没有意义"
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.cjk.strategy,
|
||||
Strategy::NeedsDownload(&unlucky_i18n::font::NOTO_SANS_SC),
|
||||
"这张脸表必须判出「需要下载」,否则下面的断言证明不了什么"
|
||||
"这张脸表必须判出「需要下载」,否则后面的断言证明不了什么"
|
||||
);
|
||||
assert!(
|
||||
!after.is_downloading(),
|
||||
"求值字体**不许**发起下载(状态从 {before:?} 变成了 {after:?})"
|
||||
);
|
||||
assert_eq!(
|
||||
after, before,
|
||||
"求值字体一次都不许写状态槽(比「不再是下载中」更强:任何改写都算越界)"
|
||||
);
|
||||
}
|
||||
|
||||
/// 用 `current_exe()` 把本 test binary 再起一次,只跑 [`resolving_fonts_never_starts_a_download`]。
|
||||
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)
|
||||
.args(["--exact", "--nocapture", "--test-threads=1", name])
|
||||
.env(CHILD_ENV, "1")
|
||||
// 截图通道的钉子会直接替换 `status()` 的返回值(`install.rs:157` 先看它),
|
||||
// 留着就等于把尺子换成别人的常数。
|
||||
.env_remove(crate::fonts::install::CAPTURE_ENV)
|
||||
.output()
|
||||
.unwrap_or_else(|error| panic!("起不了隔离子进程 {}:{error}", exe.display()));
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"隔离子进程里的判据红了(退出码 {:?})\n--- stdout ---\n{stdout}\n--- stderr ---\n{}",
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("1 passed"),
|
||||
"子进程里必须**真**跑了一条用例(`--exact` 名字对不上会 0 passed 退出 0,那是假绿):\n{stdout}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user