Files
openclaw/test/scripts/control-ui-i18n.test.ts
2026-06-01 11:10:57 +02:00

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);
},
);
});