fix: harden command timeout defaults
This commit is contained in:
parent
96b4409f03
commit
fb5b84b410
@ -2,6 +2,8 @@ import { spawn, spawnSync } from "node:child_process";
|
||||
import { startResourceSampler } from "./collectors/resources.mjs";
|
||||
import { repoRoot } from "./paths.mjs";
|
||||
|
||||
const defaultCommandTimeoutMs = 120000;
|
||||
|
||||
export function checkCommand(command, args) {
|
||||
const result = spawnSync(command, args, {
|
||||
encoding: "utf8",
|
||||
@ -17,6 +19,7 @@ export function checkCommand(command, args) {
|
||||
}
|
||||
|
||||
export function runCommand(command, options = {}) {
|
||||
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
||||
const startedAtEpochMs = Date.now();
|
||||
const startedAt = new Date(startedAtEpochMs).toISOString();
|
||||
return new Promise((resolve) => {
|
||||
@ -41,7 +44,7 @@ export function runCommand(command, options = {}) {
|
||||
timedOut = true;
|
||||
child.kill("SIGTERM");
|
||||
setTimeout(() => child.kill("SIGKILL"), 3000).unref();
|
||||
}, options.timeoutMs);
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk.toString();
|
||||
@ -101,6 +104,14 @@ export function quoteShell(value) {
|
||||
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
||||
}
|
||||
|
||||
function normalizeTimeoutMs(value) {
|
||||
const timeoutMs = value === undefined ? defaultCommandTimeoutMs : Number(value);
|
||||
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
|
||||
throw new Error(`runCommand timeoutMs must be a positive integer, got ${JSON.stringify(value)}`);
|
||||
}
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
function truncate(value, limit = 20000) {
|
||||
if (value.length <= limit) {
|
||||
return value;
|
||||
|
||||
@ -103,6 +103,7 @@ export async function runSelfCheck(flags = {}) {
|
||||
"external-cli codex is not usable"
|
||||
));
|
||||
checks.push(await externalCliRunAuthVerificationCheck(tmp));
|
||||
checks.push(await commandTimeoutContractCheck());
|
||||
checks.push(ocmCommandBuildersCheck());
|
||||
checks.push(evaluationViolationHelpersCheck());
|
||||
checks.push(await jsonCommandCheck("plan-json", "node bin/kova.mjs plan --json", (data) => {
|
||||
@ -4616,6 +4617,37 @@ async function externalCliRunAuthVerificationCheck(tmp) {
|
||||
};
|
||||
}
|
||||
|
||||
async function commandTimeoutContractCheck() {
|
||||
const command = "node -e 'setTimeout(() => console.log(\"default-timeout-ok\"), 20)'";
|
||||
try {
|
||||
const result = await runCommand(command, { maxOutputChars: 100000 });
|
||||
assertEqual(result.status, 0, "default timeout command status");
|
||||
assertEqual(result.timedOut, false, "default timeout should not expire immediately");
|
||||
assertEqual(result.stdout.trim(), "default-timeout-ok", "default timeout command output");
|
||||
let invalidRejected = false;
|
||||
try {
|
||||
await runCommand("node -e 'process.exit(0)'", { timeoutMs: 0 });
|
||||
} catch (error) {
|
||||
invalidRejected = /timeoutMs must be a positive integer/.test(error.message);
|
||||
}
|
||||
assertEqual(invalidRejected, true, "invalid timeout rejected");
|
||||
return {
|
||||
id: "command-timeout-contract",
|
||||
status: "PASS",
|
||||
command: "evaluate runCommand timeout defaults",
|
||||
durationMs: result.durationMs
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
id: "command-timeout-contract",
|
||||
status: "FAIL",
|
||||
command,
|
||||
durationMs: 0,
|
||||
message: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function failingCommandCheck(id, command, expectedMessage) {
|
||||
const result = await runCommand(command, { timeoutMs: 30000, maxOutputChars: 1000000 });
|
||||
const output = `${result.stdout}\n${result.stderr}`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user