mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-24 06:09:33 +00:00
fix(process): clamp execfile timeouts
This commit is contained in:
@@ -2,34 +2,39 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import process from "node:process";
|
||||
import { promisify } from "node:util";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
|
||||
|
||||
const spawnMock = vi.hoisted(() => vi.fn());
|
||||
const execFileMock = vi.hoisted(() => vi.fn());
|
||||
const execFilePromiseMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
let attachChildProcessBridge: typeof import("./child-process-bridge.js").attachChildProcessBridge;
|
||||
let resolveCommandEnv: typeof import("./exec.js").resolveCommandEnv;
|
||||
let resolveProcessExitCode: typeof import("./exec.js").resolveProcessExitCode;
|
||||
let runExec: typeof import("./exec.js").runExec;
|
||||
let runCommandWithTimeout: typeof import("./exec.js").runCommandWithTimeout;
|
||||
let shouldSpawnWithShell: typeof import("./exec.js").shouldSpawnWithShell;
|
||||
|
||||
async function loadExecModules(options?: { mockSpawn?: boolean }) {
|
||||
async function loadExecModules(options?: { mockSpawn?: boolean; mockExecFile?: boolean }) {
|
||||
vi.resetModules();
|
||||
if (options?.mockSpawn) {
|
||||
if (options?.mockSpawn || options?.mockExecFile) {
|
||||
vi.doMock("node:child_process", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("node:child_process")>("node:child_process");
|
||||
return {
|
||||
...actual,
|
||||
spawn: spawnMock,
|
||||
spawn: options?.mockSpawn ? spawnMock : actual.spawn,
|
||||
execFile: options?.mockExecFile ? execFileMock : actual.execFile,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
vi.doUnmock("node:child_process");
|
||||
}
|
||||
({ attachChildProcessBridge } = await import("./child-process-bridge.js"));
|
||||
({ resolveCommandEnv, resolveProcessExitCode, runCommandWithTimeout, shouldSpawnWithShell } =
|
||||
({ resolveCommandEnv, resolveProcessExitCode, runCommandWithTimeout, runExec, shouldSpawnWithShell } =
|
||||
await import("./exec.js"));
|
||||
}
|
||||
|
||||
@@ -85,6 +90,9 @@ describe("runCommandWithTimeout", () => {
|
||||
beforeEach(async () => {
|
||||
vi.useRealTimers();
|
||||
spawnMock.mockReset();
|
||||
execFileMock.mockReset();
|
||||
execFilePromiseMock.mockReset();
|
||||
delete (execFileMock as { [promisify.custom]?: unknown })[promisify.custom];
|
||||
await loadExecModules();
|
||||
});
|
||||
|
||||
@@ -166,6 +174,23 @@ describe("runCommandWithTimeout", () => {
|
||||
expect(resolved.npm_config_fund).toBe("false");
|
||||
});
|
||||
|
||||
it("caps oversized execFile timeouts", async () => {
|
||||
execFilePromiseMock.mockResolvedValue({ stdout: Buffer.from("ok"), stderr: Buffer.from("") });
|
||||
(execFileMock as { [promisify.custom]?: typeof execFilePromiseMock })[promisify.custom] =
|
||||
execFilePromiseMock;
|
||||
await loadExecModules({ mockExecFile: true });
|
||||
|
||||
await expect(
|
||||
runExec("node", ["--version"], { timeoutMs: Number.MAX_SAFE_INTEGER }),
|
||||
).resolves.toEqual({ stdout: "ok", stderr: "" });
|
||||
|
||||
expect(execFilePromiseMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(Array),
|
||||
expect.objectContaining({ timeout: MAX_TIMER_TIMEOUT_MS }),
|
||||
);
|
||||
});
|
||||
|
||||
it("infers success for shimmed Windows commands when exit codes are missing", () => {
|
||||
expect(
|
||||
resolveProcessExitCode({
|
||||
|
||||
@@ -154,9 +154,12 @@ export async function runExec(
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
const options =
|
||||
typeof opts === "number"
|
||||
? { timeout: opts, encoding: "buffer" as const }
|
||||
? { timeout: resolveTimerTimeoutMs(opts, 1), encoding: "buffer" as const }
|
||||
: {
|
||||
timeout: opts.timeoutMs,
|
||||
timeout:
|
||||
typeof opts.timeoutMs === "number"
|
||||
? resolveTimerTimeoutMs(opts.timeoutMs, 1)
|
||||
: undefined,
|
||||
maxBuffer: opts.maxBuffer,
|
||||
cwd: opts.cwd,
|
||||
encoding: "buffer" as const,
|
||||
|
||||
Reference in New Issue
Block a user