test(server): run worker terminal coverage on windows
This commit is contained in:
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user