Repository navigation
test: 修掉 3 个对主机负载和文件锁敏感的测试断言(Windows CI 偶发假红) - #3152
Conversation
A: fork-nudge-deadzone lane proof read bili.log once via readFileSync right after the response returned, racing the logger's async append stream (src/logger.ts createWriteStream) — under a loaded Windows runner the nudge line had not flushed yet and the assertion false-red'd. Bounded poll (5s, 50ms ticks) until the session's nudge line lands; write-order keeps the negative assertion sound. Teeth unchanged (timeout fails the same way). B: golden-storage driveWire scratch moved out of os.tmpdir() into the gitignored workspace-local tmp/golden-drive/ — on Windows hosted runners %TEMP% is an AV/indexer hot path shared by all c=4 legs, and external locks there repeatedly outlasted the ~9.8s rmrf ladder (#2908 family). Teardown cleanup is now best-effort (warn + inert leftover, swept >6h old on next import) since every assertion precedes it. Root-cause part: the tee logger (configured unconditionally by startServer) held a file stream into the deleted tree — any later log line made getStream() reopen the orphaned path and resurrect the tree mid-rmrf (#1646/#1910 race class); closeLogger()+configureLogger("off")+store.cancelAll() now run before rmrf so no in-process writer can recreate it. C: preflight-hold asserted an absolute wall-clock bound (tHeaderMs < SLOW_MS = 1500ms) — a loaded runner stalls the event loop and eats the fixed margin while the behavior stays correct (observed 1724ms). Both stream and non-stream legs now assert the relative invariant the message always claimed: headers arrived BEFORE the mock's actual summarization completion (timestamped in-mock, same clock). A #568 regression (no early commit) lands headers at stream end, i.e. after summarization finished — still caught. Lower bound tEndMs >= SLOW_MS untouched. Targeted local: fork-nudge-deadzone 3/3, preflight-hold 4/4, storage-engine-proofs 12/12, golden-storage-render 5/5, tmp-rm 6/6, typecheck clean. Full-suite verdict: remote CI.
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.3595-pr.3152.d0a8d5fThis exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr3152.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 本 [PR] 是另一会话已完成的工作(#3147 floors 2–3),不重做。我独立拉取 head 独立核对结论(对照源码验证,非复读 PR 描述):
结构化八问:
判定:审核通过,可合并——方向正确、机制经独立代码级验证成立、牙口不降、无回归;等 CI 全绿即可合。Windows EPERM 的最终确认留给后续若干 win-latest 腿观察(本地 Linux 无法复现,属预期)。 另发现一处非阻塞的同族残留,已按粒度规则记入 #3147 作后续清单项(同症状同机制域,不单独立项): |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.411-master.51aa9f1 # this exact build
npm install -g billion-context@master # always the newest merged build
|
Replace the absolute 'tHeaderMs < SLOW_MS' (SLOW_MS=1500) wall-clock bound in the wire-exit early-commit tests with a relative invariant ('tHeaderMs < summaryDoneMs - t0', both timestamps on the same process clock). The absolute bound flakes under CI runner load whenever the event loop stalls >1500ms before the mock's summarization timer fires; the relative form is load-invariant and still catches a true regression (headers committed after summarization completes/fails). Mirrors the ranxianglei#3147-C fix already merged in preflight-hold.test.ts (ranxianglei#3152) and covers the residual instances in wire-exit-matrix.test.ts and wire-exit-gap-cells.test.ts surfaced by class scan.
Fixes ranxianglei#3228
Closes #3147
修什么
三个测试的断言对 Windows CI runner 的「主机负载 / 文件锁」过于敏感:runner 高负载或 AV/索引器活跃时表现为假红(按 #3029 设计,具体断言失败不自动重试,需人工 rerun)。三者都是既有脆弱点被当天 runner 抖动打穿,与 #3145 内容无关(已核对其 diff:仅 CI 分片脚本)。
三处修法(全部测试层,零服务端语义改动)
A. fork-nudge-deadzone — 日志读竞态
原实现在响应返回后立即一次性
readFileSync(bili.log),与 logger 的异步追加流(src/logger.tscreateWriteStream)竞态——负载高时响应先于日志落盘返回。改为有界轮询(5s 上限、50ms 间隔)等子会话 nudge 行出现;写流保序,nudge 行可见则同请求更早的行(含任何 preflight warn)必可见,负向断言(doesNotMatch preflight compressing)保持 sound。牙口不变:行为真回归时轮询超时照样 fail。B. storage-engine-proofs / golden-storage-drive — teardown EPERM(#2908 族复发)
os.tmpdir()(Windows 即%TEMP%\golden-<wire>-*,AV/索引器热路径,且被 c=4 全部 leg 共享互相抬升外部锁概率)挪到<repo>/tmp/golden-drive/(gitignored 工作区本地路径)。机制层面:预算放宽(test(windows): driveWire golden-harness 清理期 index.db EBUSY 复发(#2799 族,#2808 后残留) #2908)已被两次复发证明治标,离开热路径才是源头。startServer无条件配置 tee logger,其文件流在整个进程生命周期指向 tmp 树内——删除后任何后续 log 行都会触发getStream()的孤儿 inode 检测并 REOPEN 被删路径,在 rmrf 中途复活目录树(test: teardown rmSync ENOTEMPTY race — most test cleanups lack retry options (CI ubuntu-latest Node 22 flake) #1646/release v0.1.180 #1910 ENOTEMPTY/EBUSY 竞态族;本地观察到的残留billion-context/bili.log即此产物)。现 rmrf 前执行store.cancelAll()+closeLogger()+configureLogger("off")(同步调用、无交错窗口),切断全部进程内复活路径。残留观察:极端情况下 persist 管线内部异步 writer 仍可能复活单个 session 文件(~14KB,inert)——由 best-effort+sweep 设计吸收;不为此动 src/persist.ts 生产路径。C. preflight-hold — 绝对墙钟余量
原断言
tHeaderMs < SLOW_MS(1500ms)是绝对上界——负载下事件循环 stall 几百 ms 就吃光固定余量(本次实测 1724ms),但行为本身正确(#568 early-commit 成立)。stream 与 non-stream JSON 两腿均改为相对不变量(断言消息本来就在说的):头必须早于 mock 汇总调用的实际完成时刻到达——mock 在回调内记录真实完成时间戳(同进程时钟,与 timedPost 的 t0 直接可比)。牙口核对:#568 若回归(不再 early-commit),头在流结束才到、必然晚于汇总完成 → 照样抓得住。下界tEndMs >= SLOW_MS不动。行为变更披露(旧 → 新)
os.tmpdir()/golden-<wire>-*<repo>/tmp/golden-drive/<wire>-*(gitignored)验证
rebased head
096931d9c(master 42881c6,含 #3145):typecheck clean;定向本地 fork-nudge-deadzone 3/3、preflight-hold 4/4、tmp-rm 6/6(ladder pin 未动)、storage-engine-proofs 12/12、golden-storage-render 5/5(golden 摘要稳定,tests/golden/* 零变更;后两者在 rebase 前运行,rebase 窗口的 master 新提交只新增 tests/storage-save-path.test.ts,不触碰本 PR 文件及其依赖)。全量判定交远程 CI。Windows EPERM 本地无法复现(Linux 无 AV 争用),最终确认看后续若干 win-latest leg 是否还复发。