113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
// Test Helpers — Shared utilities for running CLI commands, managing the
|
|
// isolated test daemon, and skipping tests when server binaries are missing.
|
|
import { execFile, execSync } from "node:child_process";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
// Project Root — resolved relative to this file.
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
export const projectRoot = path.resolve(__dirname, "..");
|
|
|
|
// CLI Path — absolute path to cli.ts for child_process calls.
|
|
export const cliPath = path.join(projectRoot, "cli.ts");
|
|
|
|
// Tsx CLI — resolve the tsx binary for running .ts files via child_process.
|
|
export const tsx = path.resolve(
|
|
projectRoot,
|
|
"node_modules",
|
|
"tsx",
|
|
"dist",
|
|
"cli.mjs",
|
|
);
|
|
|
|
// Unique Test Socket — each suite gets its own Unix socket so parallel
|
|
// integration tests don't race through the same daemon.
|
|
export function testSocket(): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lsp-test-"));
|
|
return path.join(dir, "daemon.sock");
|
|
}
|
|
|
|
// Set Test Socket — sets PI_LSP_SOCKET_PATH for the current process and
|
|
// returns a cleanup function that deletes the env var and removes the socket.
|
|
export function setTestSocket(env: Record<string, string | undefined>): () => void {
|
|
const sock = testSocket();
|
|
env.PI_LSP_SOCKET_PATH = sock;
|
|
return () => {
|
|
delete env.PI_LSP_SOCKET_PATH;
|
|
try {
|
|
fs.rmSync(path.dirname(sock), { recursive: true, force: true });
|
|
} catch {
|
|
// Socket may not exist — that's fine.
|
|
}
|
|
};
|
|
}
|
|
|
|
// Stop Test Daemon — best-effort shutdown of whatever daemon is on the test
|
|
// socket. Ignores errors (daemon may not be running).
|
|
export async function stopTestDaemon(env: Record<string, string | undefined>): Promise<void> {
|
|
try {
|
|
await execFileAsync("node", [tsx, cliPath, "daemon", "stop"], { env });
|
|
} catch {
|
|
// Already stopped or never started — ignore.
|
|
}
|
|
}
|
|
|
|
// Run CLI — spawns `node tsx cli.ts <...args>` and returns stdout as a string.
|
|
// Uses the provided env (should include PI_LSP_SOCKET_PATH for daemon tests).
|
|
export async function runCli(
|
|
args: string[],
|
|
env: Record<string, string | undefined>,
|
|
): Promise<{ stdout: string; stderr: string }> {
|
|
try {
|
|
const { stdout, stderr } = await execFileAsync(
|
|
"node",
|
|
[tsx, cliPath, ...args],
|
|
{ env, timeout: 30_000, maxBuffer: 1024 * 1024 },
|
|
);
|
|
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
} catch (err: unknown) {
|
|
const child = err as { stdout?: string; stderr?: string; status?: number };
|
|
return {
|
|
stdout: child.stdout?.trim() ?? "",
|
|
stderr: child.stderr?.trim() ?? String(err),
|
|
};
|
|
}
|
|
}
|
|
|
|
// Run CLI Expecting JSON — runs the CLI and parses stdout as JSON. Throws
|
|
// if parsing fails or the process exits with non-zero status.
|
|
export async function runCliJson(
|
|
args: string[],
|
|
env: Record<string, string | undefined>,
|
|
): Promise<unknown> {
|
|
const { stdout, stderr } = await runCli(args, env);
|
|
if (!stdout) throw new Error(`CLI produced no stdout: ${stderr}`);
|
|
try {
|
|
return JSON.parse(stdout);
|
|
} catch (err) {
|
|
throw new Error(
|
|
`Failed to parse CLI output as JSON: ${(err as Error).message}\nOutput: ${stdout}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Require Server — checks that the given server binary is on PATH. Returns
|
|
// a skip message string if not found (caller should use `test.skip(msg)`),
|
|
// or undefined if the server is available.
|
|
export function requireServer(command: string): string | undefined {
|
|
try {
|
|
execSync(`which ${command}`, { stdio: "pipe" });
|
|
return undefined;
|
|
} catch {
|
|
return `Server "${command}" not found on PATH`;
|
|
}
|
|
}
|
|
|
|
// Fixtures Directory — path to the test fixture files.
|
|
export const fixturesDir = path.join(__dirname, "fixtures");
|