test(core): mtime 探针改用显式 set_modified,update-e2e 失败升注解(M275)
tree_snapshot_sees_new_nodes_and_mtime_changes 的「同内容重写」时间维度断言在 Windows/NTFS 上不可靠:同一系统时钟 tick 内的两次写、或时间戳只在句柄关闭时按 粗粒度落盘,都会让 mtime 与写之前逐字节相同 —— main 上 test (windows-latest) 偶发红在这行 assert_ne!(check-run 130965734990)。改成显式时间戳: File::options().write(true).open + set_modified(基线 + 5s),值由测试指定、 与 tick/落盘粒度无关;不用 sleep、不用重试。用例原本的目的(快照必须看得见 mtime 变化)保持不变,另附 4s 下界断言把「我们推的那一下」与 stat 抖动分开。 ci.yml 的 update-e2e 步骤原本是裸 cargo test,失败只留一句 exit code 1、 check-run 上零注解,匿名侧等于信息为零。改成与 test 作业同款:pwsh + Tee-Object 存 $RUNNER_TEMP 日志、失败把 panicked at / error / test result: FAILED / failures: 行以 ::error:: 输出(% → %25)、打印并 exit $LASTEXITCODE (cargo 真码,不是 Tee 的码)。步骤名 update-e2e 与 --test update_e2e_test 逐字保留(check_test_source_off.py 的 ALLOWED_STEP 钉死这两点)。 行数不变(ci.yml 397 → 398,≤400):注释块等价压缩腾出的行给运行时逻辑。
This commit is contained in:
1 parent
a0a35596c8
commit
686dd9335a
2 files changed
+69
-36
No files matched your search
@@ -5,6 +5,7 @@
|
||||
//! 也抓不住「目录本来就在、但被重写/清空」的情形。
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use unlucky_core::launch::natives::{self, NativeJar, NativesPlan};
|
||||
|
||||
@@ -19,8 +20,9 @@ use crate::prelude::*;
|
||||
/// 瞬时写入只留父目录 mtime 一条线索,而 NTFS 会把该更新合并掉 —— 本仓实测:全量并行跑
|
||||
/// 测试时这条偶发看不见(单跑又看得见),所以瞬时建删只剩「`natives` 目录不存在」兜底。
|
||||
///
|
||||
/// 时间戳口径:NTFS / ext4 的 mtime 精度是 100ns 级,快照取纳秒整数比较,同一秒内的
|
||||
/// 两次操作(写文件 → 再写一次)分辨得出来,不需要 `sleep` 把操作推到不同秒。
|
||||
/// 时间戳口径:**精度 ≠ 可靠性**。NTFS / ext4 的 mtime 字段精度是 100ns 级,但「两个操作能不能
|
||||
/// 分辨」取决于时间戳**何时被采样、以多大粒度更新**(同内容立即重写在 NTFS 上可能落在同一系统
|
||||
/// 时钟 tick 内、句柄关闭时才按粗粒度落盘),所以快照只在「有人显式改过时间戳」时才是判据。
|
||||
/// 判据自检见 `tree_snapshot_sees_new_nodes_and_mtime_changes`(防假绿)。
|
||||
fn tree_snapshot(root: &Path) -> Vec<String> {
|
||||
fn line(meta: &std::fs::Metadata, relative: &str) -> String {
|
||||
@@ -211,14 +213,22 @@ fn dry_run_after_real_extract_reports_ready_without_writing() {
|
||||
///
|
||||
/// 这里**不**用「建了立刻删」来探父目录 mtime:NTFS 会把瞬时建删对父目录 mtime 的影响
|
||||
/// 合并掉(本仓实测:全量并行跑时偶发看不见、单跑看得见),拿它当门禁就是引入 flaky。
|
||||
/// 改成两种确定性变化:新节点(结构维度)+ 同内容重写(时间维度,只有文件 mtime 变,
|
||||
/// 不依赖父目录时间戳的合并行为)。
|
||||
///
|
||||
/// 时间维度也**不**靠「同内容立刻重写」:NTFS 上 mtime 不保证「每次写都变」——同一个系统
|
||||
/// 时钟 tick 内的两次写、或时间戳只在句柄关闭时按粗粒度落盘,都会让重写后的 mtime 与写之前
|
||||
/// **逐字节相同**。M275 的实际失败正是这一条:main 上 `test (windows-latest)` 偶发红在这行
|
||||
/// `assert_ne!`(`crates\core\tests\launch\natives_dry_run_cases.rs:241`),本机永远复现不出来。
|
||||
/// `sleep` + 重试轮询只是把概率压低(把「不可靠观察」换成「不可靠观察 + 更长的墙钟」),
|
||||
/// 不是消除。确定性判据是**显式时间戳**:`File::set_modified` 把 mtime 直接写成
|
||||
/// 「基线 + 5s」——值由我们指定,与时钟 tick / 落盘粒度无关;快照若看不见它,说明快照真的
|
||||
/// 丢了时间维度,而不是运气不好。
|
||||
#[test]
|
||||
fn tree_snapshot_sees_new_nodes_and_mtime_changes() {
|
||||
let root = temp_dir("natives-snapshot-probe");
|
||||
let version_dir = root.join("versions/1.16.5");
|
||||
std::fs::create_dir_all(&version_dir).expect("建版本目录");
|
||||
std::fs::write(version_dir.join("1.16.5.json"), b"{}").expect("放清单占位");
|
||||
let manifest = version_dir.join("1.16.5.json");
|
||||
std::fs::write(&manifest, b"{}").expect("放清单占位");
|
||||
let baseline = tree_snapshot(&version_dir);
|
||||
|
||||
// 1) 结构维度:多一个目录节点(正是 `create_dir_all(natives)` 留下的痕迹)。
|
||||
@@ -230,8 +240,20 @@ fn tree_snapshot_sees_new_nodes_and_mtime_changes() {
|
||||
);
|
||||
assert_ne!(baseline, with_dir);
|
||||
|
||||
// 2) 时间维度:同一份内容重写一遍,节点集合不变、文件 mtime 必须变。
|
||||
std::fs::write(version_dir.join("1.16.5.json"), b"{}").expect("重写清单占位");
|
||||
// 2) 时间维度:显式把清单文件的 mtime 推到基线之后(节点集合不变 ⇒ 差异只可能来自时间戳)。
|
||||
// 不改内容长度(那动的是 size,不是 mtime)、不 `sleep`、不重试。
|
||||
let before = std::fs::metadata(&manifest)
|
||||
.expect("清单元数据")
|
||||
.modified()
|
||||
.expect("基线 mtime");
|
||||
let pushed = before + Duration::from_secs(5);
|
||||
let file = std::fs::File::options()
|
||||
.write(true)
|
||||
.open(&manifest)
|
||||
.expect("打开清单占位");
|
||||
file.set_modified(pushed).expect("把 mtime 推到基线之后");
|
||||
drop(file);
|
||||
|
||||
let rewritten = tree_snapshot(&version_dir);
|
||||
assert_eq!(
|
||||
with_dir.len(),
|
||||
@@ -242,4 +264,14 @@ fn tree_snapshot_sees_new_nodes_and_mtime_changes() {
|
||||
with_dir, rewritten,
|
||||
"快照必须看得见已有文件的 mtime 变化,否则零落盘断言是假绿"
|
||||
);
|
||||
// 差异必须是我们推的那一下,而不是两次 `stat` 的偶然抖动(粗粒度文件系统会把 5s 取整到
|
||||
// 4s 或 6s,所以判据是「至少前进 4s」而不是「恰好 5s」)。
|
||||
let after = std::fs::metadata(&manifest)
|
||||
.expect("清单元数据")
|
||||
.modified()
|
||||
.expect("推进后的 mtime");
|
||||
assert!(
|
||||
after.duration_since(before).expect("mtime 应当前进了") >= Duration::from_secs(4),
|
||||
"mtime 应当被推到基线之后:{before:?} → {after:?}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user