test(m166): 钉住生产 write() 的限额绑定(复审 r1 p2-1)

复审 r1 的变异:把 `format.rs` 里 `write()` 传给 `write_with_limits` 的
`WriteLimits::production()` 换成 `WriteLimits::new(u64::MAX, u64::MAX, entries)`,
**606 条全绿**。原因是夹具缩到 KiB 之后,"生产路径 + 真超限内容"那种顺带守住生产
限额的证法没了,而 `the_production_limits_are_the_shared_constants` 只钉了
`production()` 的**返回值**、没钉 `write()` 的**调用点**。

补法(不动生产签名、不进发布构建):
- 新增 `format/probe.rs`(`#[cfg(test)]`):线程局部的限额记录钩子;
  `write_with_limits` 进门时 `#[cfg(test)] probe::record(limits);` 记一笔。
- `tests/limits.rs::the_production_write_path_hands_the_shared_constants_to_the_preflight`
  走**生产**的 `CURSEFORGE.write`(KiB 级 payload)后断言记到的就是
  `WriteLimits::production()` 的逐字段值,并再钉一遍统一常量。
- 反向验:上述变异现在恰好 1 条红(`总量限额:生产路径传错了`,2026-11-08 实测
  18446744073709551615 vs 536870912);只把 entry 换成 u64::MAX 也红(`单条目限额`)。

附带(复审非阻断观察):
- 观察 2 采纳:新增 `zip64::the_gate_runs_before_any_content_is_opened`,把"闸在读之前"
  从结构证明升级成行为证明——源做成**目录**(`metadata()` 有长度、`File::open` 拒绝访问,
  本机实测 C:\Windows 20480 字节 / crates/core/src 4096 字节),闸若挪到读之后会拿到
  `pack_io` 而不是 `pack_limits`。
- 观察 1 采纳:`tests/limits.rs` 里"改坏常量会在 cargo check 就红"的旧注释改成准确说法
  (E0080 只在测试构建出现,裸 `cargo check` 是绿的)。
- `probe.rs` 里 `#![allow(clippy::missing_const_for_thread_local)]`:clippy 1.99 对已经
  写成 `const { Cell::new(None) }` 的 `thread_local!` 仍报错(最小复现同样红),是误报;
  外挂在宏上的 `#[allow]` 会被判 unused attribute,故用模块级 inner attribute。

cargo test -p unlucky-core --lib:608 passed / 0 failed / 0 ignored(+2 条);
clippy --all-targets -D warnings、fmt --check 干净;全仓 0 个 >400 行文件。
This commit is contained in:
AskaEth committed 2026-11-08 09:30:26 +08:00
1 parent 49a2fafe51
commit 9b23221a62
4 files changed
+134 -1

No files matched your search

+10
View File
@@ -63,6 +63,11 @@ use super::multimc;
mod payload; mod payload;
mod write; mod write;
/// 测试用的限额记录钩子(`probe.rs`,**只在 `cfg(test)` 里编译**):把"生产 `write()`
/// 到底传了哪份限额"变成可观测的,见该模块文档。
#[cfg(test)]
pub(in crate::addon::pack) mod probe;
/// 导出时的覆盖内容来源在 [`payload`] 子模块里(`payload.rs`,与本文件拆开是为了守 400 行): /// 导出时的覆盖内容来源在 [`payload`] 子模块里(`payload.rs`,与本文件拆开是为了守 400 行):
/// [`PackPayload::metadata_only`] 只写清单、[`PackPayload::from_root`] 按 /// [`PackPayload::metadata_only`] 只写清单、[`PackPayload::from_root`] 按
/// `<root>/<dir>/<path>` 取文件、[`PackPayload::with_sources`] 按**包内条目名**查表 /// `<root>/<dir>/<path>` 取文件、[`PackPayload::with_sources`] 按**包内条目名**查表
@@ -160,6 +165,11 @@ pub(in crate::addon::pack) fn write_with_limits<F: PackFormat + ?Sized>(
target: &Path, target: &Path,
limits: write::WriteLimits, limits: write::WriteLimits,
) -> PackResult<()> { ) -> PackResult<()> {
// 测试构建里记一笔"这次传的是哪份限额":夹具缩小之后,只有它能把
// 「生产 `write()` 传的就是 `WriteLimits::production()`」这件事变成可观测的(见 `probe`)。
#[cfg(test)]
probe::record(limits);
let Some(entry) = format.metadata_entry() else { let Some(entry) = format.metadata_entry() else {
return Err(PackError::unsupported(format!( return Err(PackError::unsupported(format!(
"格式 {}({})只支持读取,不支持导出", "格式 {}({})只支持读取,不支持导出",
@@ -0,0 +1,44 @@
//! **只在测试构建里存在**的限额记录钩子(p2-1,复审 r1)。
//!
//! # 为什么需要它
//!
//! M166 把夹具从 GiB 缩到 KiB 之后,「生产路径 + 真超限内容」那种顺带守住生产限额的证法没了。
//! 于是「[`super::PackFormat::write`] 到底把哪份限额交给 [`super::write_with_limits`]」
//! **没有任何测试能观测**:把它换成 `WriteLimits::new(u64::MAX, u64::MAX, …)` 会全绿
//! (复审 r1 实测 606 passed)。`the_production_limits_are_the_shared_constants` 只钉了
//! `production()` 的**返回值**,没钉 `write()` 的**调用点**。
//!
//! 所以这里加一条捕获缝:`write_with_limits` 进门时记一笔,测试走**生产**的
//! `CURSEFORGE.write(…)` 之后断言记到的就是 [`super::write::WriteLimits::production`]。
//!
//! # 三条纪律
//!
//! 1. **只在 `cfg(test)` 里编译**:发布构建里这个模块不存在,`write_with_limits` 的签名也没动;
//! 2. 记录是**线程局部**的:`cargo test` 每个用例一个线程,并行跑也不会串味;
//! 3. 这是**测试设施**,不是给生产留的"放宽限额"开关——生产路径只有
//! [`super::write::WriteLimits::production`] 一个来源。
// clippy 1.99 的 `missing_const_for_thread_local` 在**已经是** `const { … }` 的写法上仍然报:
// 最小复现 `thread_local! { static A: Cell<Option<u64>> = const { Cell::new(None) }; }` 一样红
// (`-D warnings` 下直接失败)。是这条 lint 的误报,不是这里漏了 const,所以整模块压掉
// (外挂在 `thread_local!` 上的 `#[allow]` 对展开里的诊断无效,会被报成 unused attribute)。
#![allow(clippy::missing_const_for_thread_local)]
use std::cell::Cell;
use super::write::WriteLimits;
thread_local! {
/// 本线程最近一次 [`super::write_with_limits`] 收到的限额。
static LAST_LIMITS: Cell<Option<WriteLimits>> = const { Cell::new(None) };
}
/// 记一笔(由 [`super::write_with_limits`] 进门时调用)。
pub(super) fn record(limits: WriteLimits) {
LAST_LIMITS.with(|slot| slot.set(Some(limits)));
}
/// 本线程最近一次记到的限额(`None` = 还没走过 `write_with_limits`)。
pub(in crate::addon::pack) fn last_limits() -> Option<WriteLimits> {
LAST_LIMITS.with(Cell::get)
}
+41 -1
View File
@@ -120,7 +120,10 @@ fn the_entry_count_gate_is_the_same_number_as_the_read_side() {
fn the_three_limits_form_one_chain_and_the_read_side_uses_them() { fn the_three_limits_form_one_chain_and_the_read_side_uses_them() {
// ① 关系:单条 ≤ 总量 < ZIP64 ≤ u32。前一半保证"能写出来的包读得回来", // ① 关系:单条 ≤ 总量 < ZIP64 ≤ u32。前一半保证"能写出来的包读得回来",
// 后一半保证我们永远不需要写 ZIP64,也永远撞不到 mrpack 的 fileSize(u32) 上限。 // 后一半保证我们永远不需要写 ZIP64,也永远撞不到 mrpack 的 fileSize(u32) 上限。
// 这三条是**编译期**断言(`const` 块):改坏常量会在 `cargo check` 就红,不用等跑测试。 // 这三条是**编译期**断言(`const` 块,而且写在 `#[test] fn` 体内 ⇒ **只在测试构建里存在**):
// 改坏常量会在 `cargo test` / `cargo clippy --all-targets` 的编译期就红(E0080),
// 不用等跑到测试函数;但**裸 `cargo check -p unlucky-core`** 是 lib 非测试构建,
// 看不见这些断言(复审 r1 实测:E0080 只在 `--no-run`/`--all-targets` 下出现)。
const _: () = assert!( const _: () = assert!(
PACK_MAX_ENTRY_BYTES <= PACK_MAX_UNCOMPRESSED_BYTES, PACK_MAX_ENTRY_BYTES <= PACK_MAX_UNCOMPRESSED_BYTES,
"单条上限必须 ≤ 总量上限(否则单条规则永远够不着)" "单条上限必须 ≤ 总量上限(否则单条规则永远够不着)"
@@ -251,6 +254,43 @@ fn the_write_side_refuses_a_total_over_the_unified_total_limit() {
assert!(!target.exists()); assert!(!target.exists());
} }
#[test]
fn the_production_write_path_hands_the_shared_constants_to_the_preflight() {
// p2-1(复审 r1):夹具缩到 KiB 之后,"生产路径 + 真超限内容"那种顺带守住生产限额的
// 证法没了,于是「`PackFormat::write` 到底把哪份限额交给 `write_with_limits`」
// 没有任何测试能观测——把它换成 `WriteLimits::new(u64::MAX, u64::MAX, …)` 会全绿。
// `the_production_limits_are_the_shared_constants` 只钉了 `production()` 的**返回值**,
// 这条钉的是**调用点**(走生产的 `CURSEFORGE.write`,靠 `format::probe` 记录)。
let root = TempRoot::new("limits-binding");
write_file(&root.join("overrides/mods/jei.jar"), b"PK\x03\x04 jei");
let target = root.join("out.zip");
CURSEFORGE
.write(
&manifest_of(vec![override_file("mods/jei.jar")]),
PackPayload::from_root(&root),
&target,
)
.expect("正常尺寸必须写得出来");
let recorded = crate::addon::pack::format::probe::last_limits()
.expect("生产 write() 必须经过 write_with_limits");
let production = WriteLimits::production();
assert_eq!(recorded.total, production.total, "总量限额:生产路径传错了");
assert_eq!(
recorded.entry, production.entry,
"单条目限额:生产路径传错了"
);
assert_eq!(
recorded.entries, production.entries,
"条目数限额:生产路径传错了"
);
// 再把生产值本身钉在统一常量上:两条合起来才等于"生产路径用的是读侧那三个数"。
assert_eq!(recorded.total, PACK_MAX_UNCOMPRESSED_BYTES);
assert_eq!(recorded.entry, PACK_MAX_ENTRY_BYTES);
assert_eq!(recorded.entries, PACK_MAX_ENTRIES);
}
#[test] #[test]
fn a_real_pack_written_under_the_limit_reopens_under_the_read_limits() { fn a_real_pack_written_under_the_limit_reopens_under_the_read_limits() {
// 端到端的那句话:"我们能写出的包一定能被自己读回来"。用一份正常尺寸的覆盖内容跑一遍。 // 端到端的那句话:"我们能写出的包一定能被自己读回来"。用一份正常尺寸的覆盖内容跑一遍。
+39
View File
@@ -183,6 +183,45 @@ fn a_single_entry_over_the_entry_limit_is_refused_before_a_single_byte_is_read()
assert_eq!(leftovers(&root, &["overrides"]), Vec::<String>::new()); assert_eq!(leftovers(&root, &["overrides"]), Vec::<String>::new());
} }
#[test]
fn the_gate_runs_before_any_content_is_opened() {
// **行为**证明(复审 r1 的观察 2):源做成**目录**——`metadata()` 拿得到长度,
// `File::open` 会失败(Windows NTFS: "拒绝访问",实测 `C:\Windows` = 20480 字节、
// 仓库里的 `crates/core/src` = 4096 字节)。所以闸若被挪到"读完内容之后",这里拿到的
// 会是 `pack_io` 而不是 `pack_limits` —— 比只看"临时文件没被创建"更硬。
let root = TempRoot::new("zip64-before-read");
let directory = root.join("overrides/mods/not-a-file");
std::fs::create_dir_all(&directory).expect("建目录");
let declared = std::fs::metadata(&directory)
.expect("stat 目录(这一步必须成功,否则区分不了两道闸)")
.len();
if declared == 0 {
// 少数文件系统把目录长度报成 0:这条行为断言在那上面无从构造。此时覆盖仍在
// (`leftovers` / `!target.exists()` 的结构证明不受影响)。
return;
}
// 上限比目录的**声明长度**小一字节:总量闸必然先命中,且内容一个字节都不可能读成功。
let limit = declared - 1;
let target = root.join("out.zip");
let error = write_with_limits(
&CURSEFORGE,
&manifest_of(vec![override_file("mods/not-a-file")]),
PackPayload::from_root(&root),
&target,
WriteLimits::new(limit, limit, SMALL_ENTRIES),
)
.expect_err("声明尺寸超限必须拒");
assert_eq!(
error.kind(),
PackErrorKind::Limits,
"闸必须在打开内容**之前**:拿到 pack_io 说明它被挪到读之后了:{error}"
);
assert!(!target.exists(), "预检失败时目标路径不许出现");
assert_eq!(leftovers(&root, &["overrides"]), Vec::<String>::new());
}
#[test] #[test]
fn an_export_under_the_threshold_still_writes_a_normal_pack() { fn an_export_under_the_threshold_still_writes_a_normal_pack() {
// 对照:不超阈值时预检不许误伤(否则这条护栏就变成"导出全挂")。 // 对照:不超阈值时预检不许误伤(否则这条护栏就变成"导出全挂")。