
























@@ -2,34 +2,39 @@
22import type { ChildProcess } from "node:child_process";
33import { EventEmitter } from "node:events";
44import process from "node:process";
5+import { promisify } from "node:util";
56import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
67import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
78import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
89910const spawnMock = vi.hoisted(() => vi.fn());
11+const execFileMock = vi.hoisted(() => vi.fn());
12+const execFilePromiseMock = vi.hoisted(() => vi.fn());
10131114let attachChildProcessBridge: typeof import("./child-process-bridge.js").attachChildProcessBridge;
1215let resolveCommandEnv: typeof import("./exec.js").resolveCommandEnv;
1316let resolveProcessExitCode: typeof import("./exec.js").resolveProcessExitCode;
17+let runExec: typeof import("./exec.js").runExec;
1418let runCommandWithTimeout: typeof import("./exec.js").runCommandWithTimeout;
1519let shouldSpawnWithShell: typeof import("./exec.js").shouldSpawnWithShell;
162017-async function loadExecModules(options?: { mockSpawn?: boolean }) {
21+async function loadExecModules(options?: { mockSpawn?: boolean; mockExecFile?: boolean }) {
1822vi.resetModules();
19-if (options?.mockSpawn) {
23+if (options?.mockSpawn || options?.mockExecFile) {
2024vi.doMock("node:child_process", async () => {
2125const actual =
2226await vi.importActual<typeof import("node:child_process")>("node:child_process");
2327return {
2428 ...actual,
25-spawn: spawnMock,
29+spawn: options?.mockSpawn ? spawnMock : actual.spawn,
30+execFile: options?.mockExecFile ? execFileMock : actual.execFile,
2631};
2732});
2833} else {
2934vi.doUnmock("node:child_process");
3035}
3136({ attachChildProcessBridge } = await import("./child-process-bridge.js"));
32-({ resolveCommandEnv, resolveProcessExitCode, runCommandWithTimeout, shouldSpawnWithShell } =
37+({ resolveCommandEnv, resolveProcessExitCode, runCommandWithTimeout, runExec, shouldSpawnWithShell } =
3338await import("./exec.js"));
3439}
3540@@ -85,6 +90,9 @@ describe("runCommandWithTimeout", () => {
8590beforeEach(async () => {
8691vi.useRealTimers();
8792spawnMock.mockReset();
93+execFileMock.mockReset();
94+execFilePromiseMock.mockReset();
95+delete (execFileMock as { [promisify.custom]?: unknown })[promisify.custom];
8896await loadExecModules();
8997});
9098@@ -166,6 +174,23 @@ describe("runCommandWithTimeout", () => {
166174expect(resolved.npm_config_fund).toBe("false");
167175});
168176177+it("caps oversized execFile timeouts", async () => {
178+execFilePromiseMock.mockResolvedValue({ stdout: Buffer.from("ok"), stderr: Buffer.from("") });
179+(execFileMock as { [promisify.custom]?: typeof execFilePromiseMock })[promisify.custom] =
180+execFilePromiseMock;
181+await loadExecModules({ mockExecFile: true });
182+183+await expect(
184+runExec("node", ["--version"], { timeoutMs: Number.MAX_SAFE_INTEGER }),
185+).resolves.toEqual({ stdout: "ok", stderr: "" });
186+187+expect(execFilePromiseMock).toHaveBeenCalledWith(
188+expect.any(String),
189+expect.any(Array),
190+expect.objectContaining({ timeout: MAX_TIMER_TIMEOUT_MS }),
191+);
192+});
193+169194it("infers success for shimmed Windows commands when exit codes are missing", () => {
170195expect(
171196resolveProcessExitCode({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。