| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import { describe, expect, test } from "bun:test"
- import fs from "fs/promises"
- import path from "path"
- import { Process } from "../../src/util/process"
- import { tmpdir } from "../fixture/fixture"
- function node(script: string) {
- return [process.execPath, "-e", script]
- }
- describe("util.process", () => {
- test("captures stdout and stderr", async () => {
- const out = await Process.run(node('process.stdout.write("out");process.stderr.write("err")'))
- expect(out.code).toBe(0)
- expect(out.stdout.toString()).toBe("out")
- expect(out.stderr.toString()).toBe("err")
- })
- test("returns code when nothrow is enabled", async () => {
- const out = await Process.run(node("process.exit(7)"), { nothrow: true })
- expect(out.code).toBe(7)
- })
- test("throws RunFailedError on non-zero exit", async () => {
- const err = await Process.run(node('process.stderr.write("bad");process.exit(3)')).catch((error) => error)
- expect(err).toBeInstanceOf(Process.RunFailedError)
- if (!(err instanceof Process.RunFailedError)) throw err
- expect(err.code).toBe(3)
- expect(err.stderr.toString()).toBe("bad")
- })
- test("aborts a running process", async () => {
- const abort = new AbortController()
- const started = Date.now()
- setTimeout(() => abort.abort(), 25)
- const out = await Process.run(node("setInterval(() => {}, 1000)"), {
- abort: abort.signal,
- nothrow: true,
- })
- expect(out.code).not.toBe(0)
- expect(Date.now() - started).toBeLessThan(1000)
- }, 3000)
- test("kills after timeout when process ignores terminate signal", async () => {
- if (process.platform === "win32") return
- const abort = new AbortController()
- const started = Date.now()
- setTimeout(() => abort.abort(), 25)
- const out = await Process.run(node('process.on("SIGTERM", () => {}); setInterval(() => {}, 1000)'), {
- abort: abort.signal,
- nothrow: true,
- timeout: 25,
- })
- expect(out.code).not.toBe(0)
- expect(Date.now() - started).toBeLessThan(1000)
- }, 3000)
- test("uses cwd when spawning commands", async () => {
- await using tmp = await tmpdir()
- const out = await Process.run(node("process.stdout.write(process.cwd())"), {
- cwd: tmp.path,
- })
- expect(out.stdout.toString()).toBe(tmp.path)
- })
- test("merges environment overrides", async () => {
- const out = await Process.run(node('process.stdout.write(process.env.KILO_TEST ?? "")'), {
- env: {
- KILO_TEST: "set",
- },
- })
- expect(out.stdout.toString()).toBe("set")
- })
- test("uses shell in run on Windows", async () => {
- if (process.platform !== "win32") return
- const out = await Process.run(["set", "KILO_TEST_SHELL"], {
- shell: true,
- env: {
- KILO_TEST_SHELL: "ok",
- },
- })
- expect(out.code).toBe(0)
- expect(out.stdout.toString()).toContain("KILO_TEST_SHELL=ok")
- })
- test("runs cmd scripts with spaces on Windows without shell", async () => {
- if (process.platform !== "win32") return
- await using tmp = await tmpdir()
- const dir = path.join(tmp.path, "with space")
- const file = path.join(dir, "echo cmd.cmd")
- await fs.mkdir(dir, { recursive: true })
- await Bun.write(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n")
- const proc = Process.spawn([file, "--stdio"], {
- stdin: "pipe",
- stdout: "pipe",
- stderr: "pipe",
- })
- expect(await proc.exited).toBe(0)
- })
- test("rejects missing commands without leaking unhandled errors", async () => {
- await using tmp = await tmpdir()
- const cmd = path.join(tmp.path, "missing" + (process.platform === "win32" ? ".cmd" : ""))
- const err = await Process.spawn([cmd], {
- stdin: "pipe",
- stdout: "pipe",
- stderr: "pipe",
- }).exited.catch((err) => err)
- expect(err).toBeInstanceOf(Error)
- if (!(err instanceof Error)) throw err
- expect(err).toMatchObject({
- code: "ENOENT",
- })
- })
- })
|