fix(pty): harden cross-platform smoke checks

This commit is contained in:
marius-kilocode
2026-08-24 15:44:01 +02:00
parent 8d0750f563
commit 05cbbdd84a
3 changed files with 48 additions and 7 deletions
+2 -1
View File
@@ -17,7 +17,8 @@ export async function smoke() {
const exited = Promise.withResolvers<number>()
const data = proc.onData((chunk) => {
state.output += chunk
if (state.output.includes("KILO_PTY_READY")) output.resolve()
const lines = state.output.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/)
if (lines.some((line) => line.trim() === "KILO_PTY_READY")) output.resolve()
})
const exit = proc.onExit((event) => {
state.exited = true
@@ -1,4 +1,5 @@
import { spawn } from "child_process"
import { readdir, readFile } from "node:fs/promises"
import { setTimeout as sleep } from "node:timers/promises"
import type { Proc } from "../../pty/pty"
import { Log } from "../../util/log"
@@ -87,6 +88,15 @@ function signal(proc: Process, pids: number[], value: "SIGTERM" | "SIGKILL", inp
}
async function tree(file: string = "ps", args: string[] = ["-axo", "pid=,ppid="]) {
if (process.platform === "linux") {
try {
const rows = await procTree()
if (rows.length > 0) return rows
} catch (err) {
log.debug("failed to read Linux process tree", { err })
}
}
return await new Promise<Array<{ pid: number; parent: number }>>((resolve, reject) => {
try {
const child = spawn(file, args, {
@@ -116,6 +126,21 @@ async function tree(file: string = "ps", args: string[] = ["-axo", "pid=,ppid="]
})
}
async function procTree() {
const entries = await readdir("/proc", { withFileTypes: true })
const rows = await Promise.all(
entries
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
.map(async (entry) => {
const stat = await readFile(`/proc/${entry.name}/stat`, "utf8")
const match = stat.match(/^\d+ \(.*\) [A-Z] (\d+)/)
if (!match) return
return { pid: Number(entry.name), parent: Number(match[1]) }
}),
)
return rows.filter((row): row is { pid: number; parent: number } => row !== undefined)
}
async function taskkill(file: string, args: string[], opts: { stdio: "ignore"; windowsHide: true; timeout: number }) {
return await new Promise<boolean>((resolve) => {
try {
@@ -21,6 +21,26 @@ const layer = AppNodeBuilder.build(LayerNode.group([Pty.node, EventV2.node]), [
])
const it = testEffect(layer)
async function alive(pid: number) {
if (process.platform !== "win32") {
try {
process.kill(pid, 0)
return true
} catch {
return false
}
}
const proc = Bun.spawn(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
stdout: "pipe",
stderr: "ignore",
windowsHide: true,
})
const output = await new Response(proc.stdout).text()
await proc.exited
return output.includes(`"${pid}"`)
}
const attach = Effect.fn("PtyPlatformTest.attach")(function* (id: Pty.Info["id"]) {
const pty = yield* Pty.Service
const output = yield* Queue.unbounded<string>()
@@ -70,12 +90,7 @@ describe("cross-platform PTY", () => {
expect(pid).toBeGreaterThan(0)
yield* pty.remove(info.id)
if (process.platform === "win32") {
const result = yield* Effect.promise(() => Bun.$`taskkill /pid ${pid} /f /t`.quiet().nothrow())
expect(result.exitCode).toBe(128)
} else {
expect(() => process.kill(pid, 0)).toThrow()
}
expect(yield* Effect.promise(() => alive(pid))).toBe(false)
}),
)
})