fix(e2e): reject loose lifecycle metric limits

This commit is contained in:
Vincent Koc
2026-05-29 01:01:12 +02:00
parent ac8c56cc70
commit e32a59bc79
2 changed files with 61 additions and 12 deletions

View File

@@ -8,16 +8,25 @@ if (!summaryPath || !phase || separator !== "--" || !command) {
process.exit(2);
}
const pageSize = Number.parseInt(process.env.OPENCLAW_PROC_PAGE_SIZE || "4096", 10);
const clockTicks = Number.parseInt(process.env.OPENCLAW_PROC_CLK_TCK || "100", 10);
const pollMs = Number.parseInt(process.env.OPENCLAW_PLUGIN_LIFECYCLE_METRIC_POLL_MS || "100", 10);
const timeoutMs = Number.parseInt(
process.env.OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS || "300000",
10,
);
const timeoutKillGraceMs = Number.parseInt(
process.env.OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS || "2000",
10,
function readPositiveIntEnv(name, fallback) {
const text = String(process.env[name] ?? fallback).trim();
if (!/^\d+$/u.test(text)) {
throw new Error(`${name} must be a positive integer; got: ${text}`);
}
const value = Number(text);
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer; got: ${text}`);
}
return value;
}
const pageSize = readPositiveIntEnv("OPENCLAW_PROC_PAGE_SIZE", 4096);
const clockTicks = readPositiveIntEnv("OPENCLAW_PROC_CLK_TCK", 100);
const pollMs = readPositiveIntEnv("OPENCLAW_PLUGIN_LIFECYCLE_METRIC_POLL_MS", 100);
const timeoutMs = readPositiveIntEnv("OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS", 300000);
const timeoutKillGraceMs = readPositiveIntEnv(
"OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS",
2000,
);
if (!fs.existsSync("/proc")) {

View File

@@ -45,13 +45,51 @@ afterEach(() => {
});
describe("plugin lifecycle resource sampler", () => {
it("rejects loose numeric env values instead of parsing prefixes", () => {
const dir = makeTempDir();
const summary = path.join(dir, "summary.tsv");
const result = spawnSync("node", [scriptPath, summary, "invalid-env", "--", "node", "-e", ""], {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "150ms",
},
timeout: 5000,
});
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS must be a positive integer; got: 150ms",
);
});
it("rejects zero lifecycle timeouts instead of disabling the guard", () => {
const dir = makeTempDir();
const summary = path.join(dir, "summary.tsv");
const result = spawnSync("node", [scriptPath, summary, "invalid-env", "--", "node", "-e", ""], {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS: "0",
},
timeout: 5000,
});
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS must be a positive integer; got: 0",
);
});
it("configures a phase timeout with process-group cleanup", () => {
const script = readFileSync(scriptPath, "utf8");
expect(script).toContain("OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS");
expect(script).toContain("OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS");
expect(script).toContain("detached: true");
expect(script).toContain('process.kill(-child.pid, signal)');
expect(script).toContain("process.kill(-child.pid, signal)");
expect(script).toContain('const summarySignal = timedOut ? "timeout"');
expect(script).toContain("process.exit(124)");
});
@@ -78,7 +116,9 @@ describe("plugin lifecycle resource sampler", () => {
expect(result.status).toBe(124);
expect(result.stdout).toContain("signal=timeout");
expect(readFileSync(summary, "utf8")).toMatch(/^wedged\t\d+\t[\d.]+\t\d+\t[\d.]+\ttimeout$/mu);
expect(readFileSync(summary, "utf8")).toMatch(
/^wedged\t\d+\t[\d.]+\t\d+\t[\d.]+\ttimeout$/mu,
);
},
);