Files
scriptc/tests/harness/ts7-client-harness.ts
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

90 lines
2.9 KiB
TypeScript

import { spawn, type ChildProcess } from "node:child_process";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { expect } from "vitest";
import { ts7Executable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js";
import { TS7_FILE_SYSTEM_CALLBACKS } from "../../packages/compiler/src/frontend/ts7/rpc-filesystem.js";
const root = fileURLToPath(new URL("../..", import.meta.url));
function result(
child: ChildProcess,
): Promise<{ status: number | null; signal: string | null; stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
let stdout = "";
let stderr = "";
const timeout = setTimeout(() => {
child.kill("SIGKILL");
reject(new Error(`TypeScript client timed out: ${stderr}`));
}, 45_000);
child.stdout?.setEncoding("utf8").on("data", (chunk: string) => {
stdout += chunk;
});
child.stderr?.setEncoding("utf8").on("data", (chunk: string) => {
stderr += chunk;
});
child.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
child.once("close", (status, signal) => {
clearTimeout(timeout);
resolve({ status, signal, stdout, stderr });
});
});
}
export async function runClient(
command: string,
args: string[],
directory: string,
report: string,
): Promise<string> {
const server = spawn(
ts7Executable(),
["--api", "--timing", "--cwd", directory, `--callbacks=${TS7_FILE_SYSTEM_CALLBACKS}`],
{
stdio: ["pipe", "pipe", "pipe"],
},
);
let serverStderr = "";
server.stderr.setEncoding("utf8").on("data", (chunk: string) => {
serverStderr += chunk;
});
const serverDone = new Promise<void>((resolve) => {
server.once("close", () => resolve());
});
let serverError: Error | undefined;
server.on("error", (error) => {
serverError = error;
});
try {
// Node creates POSIX pipes nonblocking. Both consumers below use sync
// reads; change that property before inheriting the underlying handles.
if (process.platform !== "win32") {
for (const stream of [server.stdin, server.stdout]) {
(
stream as unknown as { _handle: { setBlocking: (value: boolean) => void } }
)._handle.setBlocking(true);
}
}
const child = spawn(command, [...args, directory, report], {
cwd: root,
stdio: ["ignore", "pipe", "pipe", server.stdout, server.stdin],
});
const actual = await result(child);
expect(serverError).toBeUndefined();
expect(actual.signal, actual.stderr + serverStderr).toBeNull();
expect(actual.status, actual.stderr + serverStderr).toBe(0);
expect(actual.stdout).toBe("");
expect(actual.stderr).toBe("");
expect(serverStderr).toBe("");
return readFileSync(report, "utf8");
} finally {
server.stdin.destroy();
server.stdout.destroy();
server.kill();
await serverDone;
}
}