test(server): run worker terminal coverage on windows

This commit is contained in:
Mohamed Boudra
2026-05-01 14:27:55 +07:00
parent 67707f2716
commit 84f5818e09
2 changed files with 101 additions and 74 deletions
+1
View File
@@ -126,6 +126,7 @@ jobs:
src/utils/spawn.test.ts src/utils/spawn.test.ts
src/utils/run-git-command.test.ts src/utils/run-git-command.test.ts
src/utils/checkout-git-rev-parse.test.ts src/utils/checkout-git-rev-parse.test.ts
src/terminal/worker-terminal-manager.test.ts
src/server/agent/provider-registry.test.ts src/server/agent/provider-registry.test.ts
src/server/agent/provider-launch-config.test.ts src/server/agent/provider-launch-config.test.ts
src/server/agent/provider-snapshot-manager.test.ts src/server/agent/provider-snapshot-manager.test.ts
@@ -12,6 +12,13 @@ import type {
TerminalWorkerToParentMessage, TerminalWorkerToParentMessage,
} from "./terminal-worker-protocol.js"; } from "./terminal-worker-protocol.js";
function nodeTerminalCommand(script: string): { command: string; args: string[] } {
return {
command: process.execPath,
args: ["-e", script],
};
}
async function waitForCondition( async function waitForCondition(
predicate: () => boolean, predicate: () => boolean,
timeoutMs: number, timeoutMs: number,
@@ -27,20 +34,6 @@ async function waitForCondition(
throw new Error(`Timed out after ${timeoutMs}ms waiting for condition`); throw new Error(`Timed out after ${timeoutMs}ms waiting for condition`);
} }
async function withShell<T>(shell: string, run: () => Promise<T>): Promise<T> {
const originalShell = process.env.SHELL;
process.env.SHELL = shell;
try {
return await run();
} finally {
if (originalShell === undefined) {
delete process.env.SHELL;
} else {
process.env.SHELL = originalShell;
}
}
}
function getVisibleText(session: TerminalSession): string { function getVisibleText(session: TerminalSession): string {
return session return session
.getState() .getState()
@@ -107,36 +100,44 @@ afterEach(() => {
}); });
it("creates a terminal through the worker and streams output", async () => { it("creates a terminal through the worker and streams output", async () => {
await withShell("/bin/sh", async () => { const cwd = mkdtempSync(join(tmpdir(), "worker-terminal-manager-output-"));
manager = createWorkerTerminalManager(); temporaryDirs.push(cwd);
const session = await manager.createTerminal({ cwd: "/tmp", env: { PS1: "$ " } }); manager = createWorkerTerminalManager();
const messages: string[] = []; const session = await manager.createTerminal({
let snapshots = 0; cwd,
const unsubscribe = session.subscribe((message) => { ...nodeTerminalCommand(`
if (message.type === "output") { process.stdin.on("data", (chunk) => {
messages.push(message.data); process.stdout.write("worker-output:" + chunk.toString());
} });
if (message.type === "snapshot") { setInterval(() => {}, 1000);
snapshots += 1; `),
}
});
await new Promise((resolve) => setTimeout(resolve, 100));
const snapshotsBeforeOutput = snapshots;
session.send({ type: "input", data: "printf worker-output\\r" });
await waitForCondition(
() =>
messages.join("").includes("worker-output") ||
getVisibleText(session).includes("worker-output"),
10000,
);
await new Promise((resolve) => setTimeout(resolve, 100));
unsubscribe();
expect(messages.join("") + getVisibleText(session)).toContain("worker-output");
expect(snapshots).toBe(snapshotsBeforeOutput);
}); });
const messages: string[] = [];
let snapshots = 0;
const unsubscribe = session.subscribe((message) => {
if (message.type === "output") {
messages.push(message.data);
}
if (message.type === "snapshot") {
snapshots += 1;
}
});
await new Promise((resolve) => setTimeout(resolve, 100));
const snapshotsBeforeOutput = snapshots;
session.send({ type: "input", data: "hello\n" });
await waitForCondition(
() =>
messages.join("").includes("worker-output:hello") ||
getVisibleText(session).includes("worker-output:hello"),
10000,
);
await new Promise((resolve) => setTimeout(resolve, 100));
unsubscribe();
expect(messages.join("") + getVisibleText(session)).toContain("worker-output:hello");
expect(snapshots).toBe(snapshotsBeforeOutput);
}); });
it("does not surface fire-and-forget send timeouts as unhandled rejections", async () => { it("does not surface fire-and-forget send timeouts as unhandled rejections", async () => {
@@ -171,41 +172,66 @@ it("does not surface fire-and-forget send timeouts as unhandled rejections", asy
}); });
it("keeps registered cwd env inheritance behind the worker manager interface", async () => { it("keeps registered cwd env inheritance behind the worker manager interface", async () => {
await withShell("/bin/sh", async () => { manager = createWorkerTerminalManager();
manager = createWorkerTerminalManager(); const cwd = mkdtempSync(join(tmpdir(), "worker-terminal-manager-env-"));
const cwd = mkdtempSync(join(tmpdir(), "worker-terminal-manager-env-")); temporaryDirs.push(cwd);
temporaryDirs.push(cwd); const markerPath = join(cwd, "env.txt");
const markerPath = join(cwd, "env.txt");
manager.registerCwdEnv({ manager.registerCwdEnv({
cwd, cwd,
env: { PASEO_WORKER_TERMINAL_TEST: "worker-env" }, env: { PASEO_WORKER_TERMINAL_TEST: "worker-env" },
});
const session = await manager.createTerminal({ cwd, env: { PS1: "$ " } });
session.send({
type: "input",
data: `printf '%s' "$PASEO_WORKER_TERMINAL_TEST" > ${JSON.stringify(markerPath)}\r`,
});
await waitForCondition(() => existsSync(markerPath), 10000);
expect(readFileSync(markerPath, "utf8")).toBe("worker-env");
}); });
await manager.createTerminal({
cwd,
...nodeTerminalCommand(`
require("node:fs").writeFileSync(
${JSON.stringify(markerPath)},
process.env.PASEO_WORKER_TERMINAL_TEST ?? "",
);
setInterval(() => {}, 1000);
`),
});
await waitForCondition(() => existsSync(markerPath), 10000);
expect(readFileSync(markerPath, "utf8")).toBe("worker-env");
});
it("starts the default shell through the worker and accepts quoted commands", async () => {
manager = createWorkerTerminalManager();
const cwd = mkdtempSync(join(tmpdir(), "worker-terminal-manager-shell-"));
temporaryDirs.push(cwd);
const markerPath = join(cwd, "shell quoted marker.txt");
const session = await manager.createTerminal({ cwd });
const command = [
"node",
"-e",
`"require('node:fs').writeFileSync('shell quoted marker.txt','shell-ok')"`,
].join(" ");
session.send({ type: "input", data: `${command}\r` });
await waitForCondition(() => existsSync(markerPath), 10000);
expect(readFileSync(markerPath, "utf8")).toBe("shell-ok");
}); });
it("removes worker terminals after killAndWait", async () => { it("removes worker terminals after killAndWait", async () => {
await withShell("/bin/sh", async () => { const cwd = mkdtempSync(join(tmpdir(), "worker-terminal-manager-kill-"));
manager = createWorkerTerminalManager(); temporaryDirs.push(cwd);
const session = await manager.createTerminal({ cwd: "/tmp", env: { PS1: "$ " } }); manager = createWorkerTerminalManager();
const session = await manager.createTerminal({
await manager.killTerminalAndWait(session.id, { cwd,
gracefulTimeoutMs: 1000, ...nodeTerminalCommand("setInterval(() => {}, 1000);"),
forceTimeoutMs: 500,
});
await waitForCondition(() => manager?.getTerminal(session.id) === undefined, 5000);
expect(manager.getTerminal(session.id)).toBeUndefined();
expect(manager.listDirectories()).not.toContain("/tmp");
}); });
await manager.killTerminalAndWait(session.id, {
gracefulTimeoutMs: 1000,
forceTimeoutMs: 500,
});
await waitForCondition(() => manager?.getTerminal(session.id) === undefined, 5000);
expect(manager.getTerminal(session.id)).toBeUndefined();
expect(manager.listDirectories()).not.toContain(cwd);
}); });