mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(test): guard tui pty watch cli args
This commit is contained in:
@@ -7,6 +7,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
type Options = {
|
||||
altScreen: boolean;
|
||||
help: boolean;
|
||||
mirrorPath: string;
|
||||
mode: "fake" | "local" | "all";
|
||||
vitestArgs: string[];
|
||||
@@ -26,6 +27,12 @@ const CHILD_SIGTERM_GRACE_MS = 500;
|
||||
const CHILD_SIGKILL_GRACE_MS = 5_000;
|
||||
const MIRROR_READ_CHUNK_BYTES = 1024 * 1024;
|
||||
const CHILD_OUTPUT_TAIL_BYTES = 128 * 1024;
|
||||
const BOOLEAN_OPTIONS = new Set(["--help", "-h", "--no-alt-screen"]);
|
||||
const VALUE_OPTIONS = new Set(["--mode", "--mirror-path"]);
|
||||
|
||||
class CliArgumentError extends Error {
|
||||
override name = "CliArgumentError";
|
||||
}
|
||||
|
||||
type KillableChild = {
|
||||
pid?: number;
|
||||
@@ -48,7 +55,11 @@ function readOption(args: string[], name: string): string | undefined {
|
||||
if (idx < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return args[idx + 1]?.trim() || undefined;
|
||||
const value = args[idx + 1];
|
||||
if (!value || value.startsWith("--")) {
|
||||
throw new CliArgumentError(`${name} requires a value`);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function readMode(args: string[]): Options["mode"] {
|
||||
@@ -56,20 +67,46 @@ function readMode(args: string[]): Options["mode"] {
|
||||
if (mode === "fake" || mode === "local" || mode === "all") {
|
||||
return mode;
|
||||
}
|
||||
throw new Error(`--mode must be fake, local, or all; got ${JSON.stringify(mode)}`);
|
||||
throw new CliArgumentError(`--mode must be fake, local, or all; got ${JSON.stringify(mode)}`);
|
||||
}
|
||||
|
||||
function usage(): string {
|
||||
return [
|
||||
"Usage: node --import tsx scripts/dev/tui-pty-test-watch.ts [options] [-- vitest args...]",
|
||||
"",
|
||||
"Options:",
|
||||
" --mode <fake|local|all> Select TUI PTY test group (default: fake)",
|
||||
" --mirror-path <path> Write/read mirrored ANSI output at this path",
|
||||
" --no-alt-screen Print without switching to the terminal alt screen",
|
||||
" -h, --help Show this help",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function validateOwnArgs(args: string[]): void {
|
||||
for (let idx = 0; idx < args.length; idx += 1) {
|
||||
const arg = args[idx] ?? "";
|
||||
if (BOOLEAN_OPTIONS.has(arg)) {
|
||||
continue;
|
||||
}
|
||||
if (VALUE_OPTIONS.has(arg)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
throw new CliArgumentError(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseOptions(args = process.argv.slice(2)): Options {
|
||||
const separator = args.indexOf("--");
|
||||
const ownArgs = separator >= 0 ? args.slice(0, separator) : args;
|
||||
const vitestArgs = separator >= 0 ? args.slice(separator + 1) : [];
|
||||
const mirrorPath =
|
||||
readOption(ownArgs, "--mirror-path") !== undefined
|
||||
? path.resolve(readOption(ownArgs, "--mirror-path") ?? "")
|
||||
: DEFAULT_MIRROR_PATH;
|
||||
validateOwnArgs(ownArgs);
|
||||
const mirrorPathOption = readOption(ownArgs, "--mirror-path");
|
||||
return {
|
||||
altScreen: !ownArgs.includes("--no-alt-screen"),
|
||||
mirrorPath,
|
||||
help: ownArgs.includes("--help") || ownArgs.includes("-h"),
|
||||
mirrorPath:
|
||||
mirrorPathOption !== undefined ? path.resolve(mirrorPathOption) : DEFAULT_MIRROR_PATH,
|
||||
mode: readMode(ownArgs),
|
||||
vitestArgs,
|
||||
};
|
||||
@@ -209,6 +246,10 @@ async function drainNewMirrorData(
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const options = parseOptions();
|
||||
if (options.help) {
|
||||
process.stdout.write(`${usage()}\n`);
|
||||
return;
|
||||
}
|
||||
const useAltScreen = shouldUseAltScreen(options);
|
||||
await createMirrorFile(options.mirrorPath);
|
||||
|
||||
@@ -422,6 +463,10 @@ async function main(): Promise<void> {
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
main().catch((error: unknown) => {
|
||||
if (error instanceof CliArgumentError) {
|
||||
process.stderr.write(`${error.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stderr.write(
|
||||
`${error instanceof Error ? error.stack || error.message : String(error)}\n`,
|
||||
);
|
||||
@@ -433,6 +478,8 @@ export const testing = {
|
||||
appendBufferTail,
|
||||
createChildStopper,
|
||||
drainNewMirrorData,
|
||||
parseOptions,
|
||||
readNewMirrorData,
|
||||
signalChildProcessTree,
|
||||
usage,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Dev Tooling Safety tests cover dev tooling safety script behavior.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
@@ -205,6 +206,45 @@ describe("script-specific dev tooling hardening", () => {
|
||||
expect(calls).toBe(1);
|
||||
});
|
||||
|
||||
it("prints TUI PTY watch usage without launching the watcher", () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
["--import", "tsx", "scripts/dev/tui-pty-test-watch.ts", "--help"],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("Usage: node --import tsx scripts/dev/tui-pty-test-watch.ts");
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
|
||||
it("rejects unknown TUI PTY watch args before launching the watcher", () => {
|
||||
expect(() => tuiPtyWatchTesting.parseOptions(["--wat"])).toThrow("Unknown argument: --wat");
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
["--import", "tsx", "scripts/dev/tui-pty-test-watch.ts", "--wat"],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr.trim()).toBe("Unknown argument: --wat");
|
||||
expect(result.stdout).toBe("");
|
||||
});
|
||||
|
||||
it("keeps TUI PTY watch vitest args behind the separator", () => {
|
||||
expect(tuiPtyWatchTesting.parseOptions(["--mode", "all", "--", "--help"])).toMatchObject({
|
||||
mode: "all",
|
||||
vitestArgs: ["--help"],
|
||||
});
|
||||
});
|
||||
|
||||
it("escalates stalled TUI PTY watch children after interrupt cleanup", async () => {
|
||||
vi.useFakeTimers();
|
||||
const signals: NodeJS.Signals[] = [];
|
||||
|
||||
Reference in New Issue
Block a user