修: 自检夹具 $T2 一直没被清理(每跑一次漏一个 /tmp 目录)+ 补一条守它的判据

## 病

`mkdtree_fail` 建的是 `$T2`,而清理语句是 `743e397` **之前**写的、只提了当时存在的
`$T` 和 `$B` ⇒ **加了新夹具没加清理**。每跑一次 `--self-check` 就往 `/tmp` 漏一个目录
(实测:一轮里跑 6 次 → 6 个残留;历次累计清出 **40** 个)。

★ 但真正的病是**第二层**:`$T2` 用的是**裸 `mktemp -d`**,于是它叫 `/tmp/tmp.XXXXXXXX`
—— 和任何 `mktemp -d` 的产物**长得一样**。后果:
**认不出来是谁的,就没法清理、也没法写判据。**

## 我第一版判据是假绿(变异测试当场抓住)

我按 `find -name 'am-prune-selftest-*'` 数残留。但 `$T2` 根本不匹配这个前缀
⇒ **把 `$T2` 从清理列表里删掉(复原 bug),判据照样绿 23/0。**
这就是"判据在,但走不到"—— 它守的是另一个前缀。

## 修

1. `mkdtree_fail` 改用 `mktemp -d "$TMPD/am-prune-selftest-XXXXXX"`,与 `mktree` 同前缀
   ⇒ 夹具**可识别**;
2. 清理列表补上 `$T2`;
3. 新增判据「自检夹具清干净(本次新建的残留 = N 个,须为 0)」,
   用**集合差**(跑前快照 vs 跑后快照)而不是"有没有"或"最近 N 分钟"
   ⇒ 上次的残留不会误伤,也不依赖时钟。

## 变异 + 对照(都实跑)

    变异:`rm -rf "$T" "$B"`(去掉 $T2)  ⇒ rc=1,22 通过/1 失败,**点名残留目录** ✓
    基线:                                ⇒ rc=0,23 通过/0 失败,/tmp 残留 0 ✓
    对照:预置一个 STALE 残留             ⇒ rc=0 通过(集合差不误伤)✓

(单次自检耗时 ~39s,不是挂起 —— 我第一次用 2 次循环跑,误撞了 60s 上限。)
This commit is contained in:
2026-09-21 06:27:24 +08:00
parent 3fc503235c
commit 437be52552

View File

@ -190,7 +190,17 @@ if [ "${1:-}" = "--self-check" ]; then
}
# 给"删除失败"那条样本用的最小树:两个构建暂存(窗口 1 份 ⇒ 旧的应被删)
mkdtree_fail() {
local t; t="$(mktemp -d)"
# 前缀必须和 `mktree` 一致(`$TMPD/am-prune-selftest-XXXXXX`)。
#
# ★ 2026-09-21:这里原来是裸 `mktemp -d`,于是 `$T2` 叫 `/tmp/tmp.XXXXXXXX`。
# 后果有两层,第二层才是要命的:
# ① 每跑一次 `--self-check` 就往 `/tmp` 漏一个目录(清理语句里漏了 `$T2`);
# ② 漏出来的目录**认不出来是谁的** —— `/tmp/tmp.*` 是任何 `mktemp -d` 的通用形态,
# 既没法安全清理,也没法写出一条"残留必须为 0"的判据
# (我第一版判据就栽在这:按 `am-prune-selftest-*` 数,永远数不到 `$T2`
# ⇒ 变异测试把 `$T2` 从清理列表里删掉,判据**照样绿**)。
# **夹具认不出来,就没有判据守得住它。**
local t; t="$(mktemp -d "$TMPD/am-prune-selftest-XXXXXX")"
mkdir -p "$t/data" "$t/tmp"
for ts in 20260101-000000 20260105-000000; do
mkdir -p "$t/tmp/agentmail-gateway-build-$ts"
@ -228,6 +238,11 @@ if [ "${1:-}" = "--self-check" ]; then
}
SIG_BEFORE="$(_tree_sig "$ROOT")"
# 自检**之前**已存在的夹具目录(正常情况下是空的,但并发跑或上次崩溃会留下)。
# 后面按**集合差**判"本次是否清干净"—— 比对"有没有新的",
# 而不是"有没有"或"最近 10 分钟内的":前者会被上一次的残留误伤,后者依赖时钟。
_FIX_BEFORE="$(find "${TMPDIR:-/tmp}" -maxdepth 1 -name 'am-prune-selftest-*' 2>/dev/null | sort)"
T="$(mktree)"
out="$(run "$T")"; rc=$? # 干跑:看报告
ck "干净样本:干跑成功(退出码 $rc)" "$([ "$rc" = 0 ] && echo 1 || echo 0)"
@ -297,7 +312,21 @@ FAKERM
ck "自检没有碰生产根($ROOT 清单不变)" "$([ "$(_tree_sig "$ROOT")" = "$SIG_BEFORE" ] && echo 1 || echo 0)"
rm -rf "$T" "$B"
# ⚠️ `$T2` 一度漏在这里(2026-09-21 发现)。夹具是三个(`T` / `B` / `T2`),
# 而清理语句是 `743e397` **之前**写的、只提了当时存在的两个 ⇒ 加了新夹具没加清理。
# 症状:每跑一次 `--self-check`,`/tmp` 就多留一个 `am-prune-selftest-*`。
# **一个自检脚本自己不干净,比它测出来的红更值得先修。**
# 判据:跑完 `--self-check` 后按前缀数目录,必须**一个都不剩**(见下面的自检后置)。
rm -rf "$T" "$B" "$T2"
# 后置:按**集合差**确证本次的夹具真被清掉(不是"我以为清了")。
# 只关心"本次新出现的",所以上次的残留不会把它打成假红;
# 这条判据正好能抓住"新夹具忘了加进清理列表"—— 那正是 `$T2` 的病史。
_FIX_AFTER="$(find "${TMPDIR:-/tmp}" -maxdepth 1 -name 'am-prune-selftest-*' 2>/dev/null | sort)"
_FIX_LEFT="$(comm -13 <(printf '%s\n' "$_FIX_BEFORE") <(printf '%s\n' "$_FIX_AFTER") | grep -c . || true)"
ck "自检夹具清干净(本次新建的残留 = $_FIX_LEFT 个,须为 0)" "$([ "$_FIX_LEFT" = 0 ] && echo 1 || echo 0)"
if [ "$_FIX_LEFT" != 0 ]; then
printf '%s\n' "$_FIX_AFTER" | comm -13 <(printf '%s\n' "$_FIX_BEFORE") - | sed 's/^/ 残留: /' >&2
fi
echo
if [ "$fails" != 0 ]; then
echo " 判据自检失败 $fails/$total 项 —— 清理器本身不可信,不要用它删东西" >&2