fix(fs): S-10 hang test hook requires UMT_FS_TEST_HOOKS=1; preflight warns when set

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 00:48:45 +08:00
1 parent b4560be48b
commit 2ddaf7bfa6
4 files changed
+32 -7

No files matched your search

+14
View File
@@ -9,6 +9,7 @@ import {
type FsProgress,
FsProxy,
fsErrorClass,
fsTestHookFromEnv,
sweepStalePartials,
toFsError,
} from "./index.ts";
@@ -147,10 +148,22 @@ describe("FsProxy 启动方式", () => {
expect(px.stats()).toMatchObject({ mode: "inprocess", fallbackReason: expect.stringMatching(/^spawn_failed/) });
});
it("验收开关只在 UMT_FS_TEST_HOOKS=1 时生效(单独误设 HANG_AFTER_BYTES 无效)", () => {
const env = (m: Record<string, string>) => (k: string) => m[k];
expect(fsTestHookFromEnv(env({ UMT_FS_TEST_HANG_AFTER_BYTES: "1024" }))).toBeUndefined();
expect(fsTestHookFromEnv(env({ UMT_FS_TEST_HANG_AFTER_BYTES: "1024", UMT_FS_TEST_HOOKS: "0" }))).toBeUndefined();
expect(fsTestHookFromEnv(env({ UMT_FS_TEST_HANG_AFTER_BYTES: "x", UMT_FS_TEST_HOOKS: "1" }))).toBeUndefined();
expect(fsTestHookFromEnv(env({ UMT_FS_TEST_HANG_AFTER_BYTES: "1024", UMT_FS_TEST_HOOKS: "1" }))).toEqual({
hangAfterBytes: 1024,
ignoreTerm: true,
});
});
it("UMT_FS_TEST_HANG_AFTER_BYTES(容器验收开关)→ 数据类操作卡死 → SIGKILL → TIMEOUT;快操作不受影响", async () => {
const t = await tree({ "src/a.mkv": 3 * MiB, "dst/": null });
const px = new FsProxy({ mode: "subprocess", ...fast });
Deno.env.set("UMT_FS_TEST_HANG_AFTER_BYTES", String(MiB));
Deno.env.set("UMT_FS_TEST_HOOKS", "1");
try {
expect(await px.statSafe(t.path("src/a.mkv"))).toMatchObject({ kind: "ok" });
const e = await px.copy(t.path("src/a.mkv"), t.path("dst/a.mkv")).catch((x) => x);
@@ -158,6 +171,7 @@ describe("FsProxy 启动方式", () => {
expect(px.stats()).toMatchObject({ timeouts: 1, killed: 1 });
} finally {
Deno.env.delete("UMT_FS_TEST_HANG_AFTER_BYTES");
Deno.env.delete("UMT_FS_TEST_HOOKS");
}
}, 30_000);
});
+10 -5
View File
@@ -110,14 +110,19 @@ export function defaultWorkerCommand(): { cmd: string; args: string[]; kind: "sc
/**
* 仅容器/手工验收用:UMT_FS_TEST_HANG_AFTER_BYTES=<n> 让每个数据类操作的 worker 在读到 n 字节后卡死 JS 线程
* (不响应 SIGTERM),用于在生产二进制里验证 停滞超时 → SIGKILL → TIMEOUT。只在子进程模式生效。
* 必须同时设置 UMT_FS_TEST_HOOKS=1 才生效(验收 07 S-10):单独误设这一个变量不会让所有文件操作卡死。
*/
export function fsTestHookFromEnv(get: (k: string) => string | undefined): FsTestHook | undefined {
const v = get("UMT_FS_TEST_HANG_AFTER_BYTES");
if (v === undefined || v === "" || !/^\d+$/.test(v)) return undefined;
if (get("UMT_FS_TEST_HOOKS") !== "1") return undefined;
return { hangAfterBytes: Number(v), ignoreTerm: true };
}
function envTestHook(): FsTestHook | undefined {
try {
const v = (globalThis as { Deno?: { env?: { get(k: string): string | undefined } } }).Deno?.env?.get(
"UMT_FS_TEST_HANG_AFTER_BYTES",
);
if (v === undefined || v === "" || !/^\d+$/.test(v)) return undefined;
return { hangAfterBytes: Number(v), ignoreTerm: true };
const env = (globalThis as { Deno?: { env?: { get(k: string): string | undefined } } }).Deno?.env;
return env ? fsTestHookFromEnv((k) => env.get(k)) : undefined;
} catch {
return undefined;
}