Files
Unlucky-Launcher/crates/core/src/addon/temp.rs
T
AskaEth 49a2fafe51 test(m166): zip64 夹具改用小 limits,去掉 8.5 GiB 真落盘
`set_len` 造的所谓稀疏文件在 NTFS 上并不稀疏:实测 `SetLength(1 GiB)` 之后
C 盘可用空间正好掉 1024 MiB。于是 `zip64.rs` 那两条用例的声明尺寸
(`u32::MAX + 1` ≈ 4 GiB 加 `3 × 1.5 GiB` = 4.5 GiB)就是 8.5 GiB **真占用**,
再加上 `write/tests.rs` 的 3 GiB 与 `limits.rs` 的三条 64 MiB 级用例,
单次跑的落盘峰值到过 ~12 GiB。

判据本身跟夹具大小无关(吃的是 `metadata().len()`),所以把 limits 做成参数:
`format::write_with_limits` 是 `PackFormat::write` 的实体,生产路径仍只传
`WriteLimits::production()`,测试传 KiB 级小值走**同一条** `preflight` 分支。
`WriteLimits` 的可见性顺势开到 `pub(in crate::addon::pack)`。

GiB 级的**数值判定**搬进 `format/write/tests.rs` 的纯算术用例:>4 GiB(撞 ZIP64
阈值即拒、差一字节放行)与 >2 GiB(M152 那个 2.5 GB 样本现在按统一上限拒),
以及第二道闸 `Written` 在同一批数值上的行为——两道闸吃的都是 u64,不需要落盘。

断言一条没删、用例一条没新加 `#[ignore]`(反而把旧的那条 `#[ignore]` 的
2.5 GB 用例换成会跑的 KiB 级版本,补上 `guard_entry` 分支的端到端覆盖)。

扫描:`grep -rn 稀疏 crates/ packaging/ docs/` 里凡写着"稀疏文件不占磁盘"的
注释一并改成实测口径(含 `addon/temp.rs`),避免下一个人照着再踩一次。
2026-11-08 09:30:26 +08:00

109 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 测试用的**一次性临时根**(`Drop` 即删;panic 展开时同样删)。
//!
//! # 为什么它必须在 `addon` 里而不是某个测试目录里
//!
//! `addon::pack` 与 `addon::tests` 两族用例都建临时目录,而 `pack/tests/fixtures.rs` 被**两边
//! 同时**通过 `#[path]` 编译(同一份手造包只维护一处)。守卫因此要放在两边的公共祖先上,否则
//! `fixtures.rs` 里的 `super::TempRoot` 在其中一侧解析不到。
//!
//! # 为什么不能靠"系统会清 `%TEMP%`"
//!
//! 那种清理要等时间或外部工具,而 `zip64` 那族用例历史上每个要造 4 GiB 的"稀疏"文件验阈值
//! (**实测在 NTFS 上 `set_len` 真占空间**,见 `pack/tests/zip64.rs` 的模块文档):
//! M160 r2 实测 `%TEMP%/ull-pack-*` 堆到 **1651 个目录 / 110 GB**,把整台机器(含别的 agent
//! 的构建)一起顶死。所以测试自己负责收尾,并且有自证用例
//! (`pack/tests/mod.rs` 的 `the_temp_root_is_gone_once_dropped` /
//! `a_panicking_test_still_removes_its_temp_root`)。
//!
//! # 形状来自仓库既有写法
//!
//! 与 [`crate::update::tests::support::TempDir`] 同一套 `new` / `join` / `Drop` 三件套
//! (不引 `tempfile`、不新增范式),只多了两件事:`Deref<Target = Path>` 让调用点能直接当
//! `&Path` 用;[`TempRoot::under`] 让"子树落点、删父目录"这件事不留下空壳。
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
/// 一次性临时根目录(**`Drop` 即删**)。
pub(crate) struct TempRoot {
/// 真正要删的根([`Self::under`] 造的子树也删它,不留空壳父目录)。
root: PathBuf,
/// 本夹具的落点(通常等于 `root`;`under` 时是 `root/<sub>`)。
path: PathBuf,
}
impl TempRoot {
/// 建一个新的临时根(同名残留先清掉:`COUNTER` 在同一进程内唯一,跨进程靠 pid)。
pub(crate) fn new(tag: &str) -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
let root =
std::env::temp_dir().join(format!("ull-pack-{}-{tag}-{unique}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("建临时根");
Self {
path: root.clone(),
root,
}
}
/// 在临时根下开一个子目录当落点(`Drop` 删的是**根**,父目录不会剩空壳)。
pub(crate) fn under(tag: &str, sub: &str) -> Self {
let base = Self::new(tag);
let path = base.path.join(sub);
std::fs::create_dir_all(&path).expect("建临时子目录");
Self {
root: base.root.clone(),
path,
}
}
/// 建一个临时根,并让落点直接是根内的某个文件(`Drop` 删的仍是**根**)。
///
/// 给"夹具函数要返回一个包路径、而包路径必须活到测试用完"的场景用:返回值 `Deref` 到那个
/// 文件,根由它持有。`root.parent()` 拿得到根目录。
pub(crate) fn at(tag: &str, file: &str) -> Self {
let mut base = Self::new(tag);
base.path = base.path.join(file);
base
}
/// 本夹具的落点。
pub(crate) fn path(&self) -> &Path {
&self.path
}
/// 落点下的一条路径(`&str` 与 `Path` 都收,免得把调用点写成两套)。
pub(crate) fn join(&self, name: impl AsRef<Path>) -> PathBuf {
self.path.join(name)
}
}
impl Deref for TempRoot {
type Target = Path;
fn deref(&self) -> &Path {
&self.path
}
}
impl AsRef<Path> for TempRoot {
/// 让 `&TempRoot` 能直接喂给 `impl AsRef<Path>` 形态的 API(`fs::read_dir` / `remove_dir_all`
/// / `PackArchive::open_default` …)。`Deref` 只解决"要 `&Path`"的场景,两者都要。
fn as_ref(&self) -> &Path {
&self.path
}
}
impl Drop for TempRoot {
fn drop(&mut self) {
// 尽力删:删不掉留一行日志,别在析构里 panic(二次 panic 会 abort)。
if let Err(error) = std::fs::remove_dir_all(&self.root) {
if error.kind() != std::io::ErrorKind::NotFound {
eprintln!("[temp] 删 {} 失败:{error}", self.root.display());
}
}
}
}