mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-05 06:02:56 +00:00
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import { mkdtempSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { describe, expect, it } from "vitest";
|
|
import { appendBoundedProcessOutput, runProcess } from "../../scripts/control-ui-i18n.ts";
|
|
|
|
function processIsAlive(pid: number): boolean {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch (error) {
|
|
return (error as NodeJS.ErrnoException).code === "EPERM";
|
|
}
|
|
}
|
|
|
|
async function waitForProcessExit(pid: number, timeoutMs = 1_000): Promise<void> {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
if (!processIsAlive(pid)) {
|
|
return;
|
|
}
|
|
await new Promise((resolve) => {
|
|
setTimeout(resolve, 10);
|
|
});
|
|
}
|
|
throw new Error(`process ${pid} was still alive after ${timeoutMs}ms`);
|
|
}
|
|
|
|
describe("control-ui-i18n process runner", () => {
|
|
it("keeps a bounded process output tail", () => {
|
|
const first = appendBoundedProcessOutput({ text: "", truncatedChars: 0 }, "abcdef", 5);
|
|
const second = appendBoundedProcessOutput(first, "ghij", 5);
|
|
|
|
expect(first).toEqual({ text: "bcdef", truncatedChars: 1 });
|
|
expect(second).toEqual({ text: "fghij", truncatedChars: 5 });
|
|
});
|
|
|
|
it("bounds failure diagnostics to the newest output", async () => {
|
|
await expect(
|
|
runProcess(
|
|
process.execPath,
|
|
[
|
|
"-e",
|
|
[
|
|
"process.stderr.write('stderr-begin-' + 'x'.repeat(128) + '-stderr-end', () => process.exit(2));",
|
|
].join(" "),
|
|
],
|
|
{ maxOutputChars: 64, rejectOnFailure: true },
|
|
),
|
|
).rejects.toThrow(/output truncated[\s\S]*stderr-end/u);
|
|
});
|
|
|
|
it("rejects successful commands before returning truncated stdout", async () => {
|
|
await expect(
|
|
runProcess(
|
|
process.execPath,
|
|
["-e", "process.stdout.write('x'.repeat(128), () => process.exit(0));"],
|
|
{
|
|
maxOutputChars: 12,
|
|
},
|
|
),
|
|
).rejects.toThrow("produced more than 12 stdout chars");
|
|
});
|
|
|
|
it.runIf(process.platform !== "win32")(
|
|
"kills descendant processes after the process timeout",
|
|
async () => {
|
|
const tempDir = mkdtempSync(path.join(tmpdir(), "openclaw-control-ui-i18n-timeout-"));
|
|
const markerPath = path.join(tempDir, "grandchild.pid");
|
|
const grandchildScript = [
|
|
"process.on('SIGTERM', () => {});",
|
|
"setInterval(() => {}, 1000);",
|
|
].join("\n");
|
|
const parentScript = [
|
|
"const { spawn } = require('node:child_process');",
|
|
"const { writeFileSync } = require('node:fs');",
|
|
`const grandchild = spawn(process.execPath, ["-e", ${JSON.stringify(grandchildScript)}], { stdio: "ignore" });`,
|
|
`writeFileSync(${JSON.stringify(markerPath)}, String(grandchild.pid));`,
|
|
"process.on('SIGTERM', () => {});",
|
|
"setInterval(() => {}, 1000);",
|
|
].join("\n");
|
|
|
|
await expect(
|
|
runProcess(process.execPath, ["-e", parentScript], {
|
|
cwd: tempDir,
|
|
killGraceMs: 25,
|
|
timeoutMs: 500,
|
|
}),
|
|
).rejects.toThrow(`timed out after 500ms`);
|
|
|
|
const grandchildPid = Number(readFileSync(markerPath, "utf8"));
|
|
await waitForProcessExit(grandchildPid);
|
|
},
|
|
);
|
|
});
|