import { describe, expect, test } from "bun:test" import { readFile } from "node:fs/promises" import { runFile, cleanupTmpDir } from "./run.ts" import type { ShoutFile } from "./parse.ts" function makeFile(commands: { command: string; expected?: string[] }[]): ShoutFile { return { path: "test.shout", commands: commands.map((c, i) => ({ line: i + 1, raw: `$ ${c.command}`, command: c.command, expected: c.expected ?? [], exitCode: null, })), } } const defaultOpts = { cleanEnv: false, timeout: 5000, verbose: false, } async function isProcessRunning(pid: number): Promise { try { const stat = await readFile(`/proc/${pid}/stat`, "utf-8") // Find state after the comm field (which is wrapped in parens and may contain spaces) const state = stat.charAt(stat.lastIndexOf(")") + 2) return state !== "Z" && state !== "X" } catch { return false } } describe("runFile", () => { test("basic command", async () => { const file = makeFile([{ command: "echo hello" }]) const result = await runFile(file, defaultOpts) try { expect(result.results[0]?.actual).toEqual(["hello"]) expect(result.results[0]?.exitCode).toBe(0) } finally { await cleanupTmpDir(result.tmpDir) } }) test("cleans up backgrounded processes after exit", async () => { const file = makeFile([ { command: "sleep 300 >/dev/null 2>&1 & echo $!" }, ]) const result = await runFile(file, defaultOpts) try { const pid = parseInt(result.results[0]?.actual[0] ?? "", 10) expect(pid).toBeGreaterThan(0) await new Promise(r => setTimeout(r, 100)) expect(await isProcessRunning(pid)).toBe(false) } finally { await cleanupTmpDir(result.tmpDir) } }) test("cleans up multiple backgrounded processes", async () => { const file = makeFile([ { command: "sleep 300 >/dev/null 2>&1 & P1=$!; sleep 300 >/dev/null 2>&1 & P2=$!; echo $P1 $P2" }, ]) const result = await runFile(file, defaultOpts) try { const pids = (result.results[0]?.actual[0] ?? "").split(" ").map(Number) expect(pids).toHaveLength(2) expect(pids[0]).toBeGreaterThan(0) expect(pids[1]).toBeGreaterThan(0) await new Promise(r => setTimeout(r, 100)) for (const pid of pids) { expect(await isProcessRunning(pid)).toBe(false) } } finally { await cleanupTmpDir(result.tmpDir) } }) })