diff options
Diffstat (limited to 'test/bun.js/child_process.test.ts')
-rw-r--r-- | test/bun.js/child_process.test.ts | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/test/bun.js/child_process.test.ts b/test/bun.js/child_process.test.ts new file mode 100644 index 000000000..fd1c27ae7 --- /dev/null +++ b/test/bun.js/child_process.test.ts @@ -0,0 +1,279 @@ +import { describe, it, expect } from "bun:test"; +import { + ChildProcess, + spawn, + execFile, + exec, + fork, + spawnSync, + execFileSync, + execSync, +} from "node:child_process"; + +// Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39?permalink_comment_id=2896416#gistcomment-2896416 +// Not 100% accurate, but good enough for this test +const SEMVER_REGEX = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/; + +describe("ChildProcess.spawn()", () => { + it("should emit `spawn` on spawn", async () => { + const proc = new ChildProcess(); + const result = await new Promise((resolve) => { + proc.on("spawn", () => { + resolve(true); + }); + proc.spawn({ file: "bun", args: ["bun", "-v"] }); + }); + expect(result).toBe(true); + }); + + it("should emit `exit` when killed", async () => { + const proc = new ChildProcess(); + const result = await new Promise((resolve) => { + proc.on("exit", () => { + resolve(true); + }); + + proc.spawn({ file: "bun", args: ["bun", "-v"] }); + proc.kill(); + }); + expect(result).toBe(true); + }); +}); + +describe("spawn()", () => { + it("should spawn a process", () => { + const child = spawn("echo", ["hello"]); + expect(!!child).toBe(true); + }); + + it("should disallow invalid filename", () => { + let child; + let child2; + try { + child = spawn(123); + child2 = spawn(["echo", "hello"]); + } catch (e) { + console.error(e); + } + expect(!!child).toBe(false); + expect(!!child2).toBe(false); + }); + + it("should allow stdout to be read via Node stream.Readable `data` events", async () => { + const child = spawn("bun", ["-v"]); + const result: string = await new Promise((resolve) => { + child.stdout.on("error", (e) => { + console.error(e); + }); + child.stdout.on("data", (data) => { + console.log(`stdout: ${data}`); + resolve(data.toString()); + }); + child.stderr.on("data", (data) => { + console.log(`stderr: ${data}`); + }); + }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); + + it("should allow stdout to be read via .read() API", async () => { + const child = spawn("bun", ["-v"]); + const result: string = await new Promise((resolve) => { + let finalData = ""; + child.stdout.on("error", (e) => { + console.error(e); + }); + child.stdout.on("readable", () => { + let data; + + while ((data = child.stdout.read()) !== null) { + finalData += data.toString(); + } + resolve(finalData); + }); + }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); + + it("should accept stdio option with 'ignore' for no stdio fds", async () => { + const child1 = spawn("bun", ["-v"], { + stdio: "ignore", + }); + const child2 = spawn("bun", ["-v"], { + stdio: ["ignore", "ignore", "ignore"], + }); + + expect(!!child1).toBe(true); + expect(child1.stdin).toBe(null); + expect(child1.stdout).toBe(null); + expect(child1.stderr).toBe(null); + + expect(!!child2).toBe(true); + expect(child2.stdin).toBe(null); + expect(child2.stdout).toBe(null); + expect(child2.stderr).toBe(null); + }); + + it("should allow us to set cwd", async () => { + const child = spawn("pwd", { cwd: process.env.TMPDIR }); + const result: string = await new Promise((resolve) => { + child.stdout.on("data", (data) => { + resolve(data.toString()); + }); + }); + const platformTmpDir = `${process.platform === "darwin" ? "/private" : ""}${ + process.env.TMPDIR + }`; + expect(`${result.trim()}/`).toBe(platformTmpDir); + }); + + it("should allow us to write to stdin", async () => { + const child = spawn("tee"); + const result: string = await new Promise((resolve) => { + child.stdin.write("hello"); + child.stdout.on("data", (data) => { + resolve(data.toString()); + }); + }); + expect(result.trim()).toBe("hello"); + }); + + it("should allow us to timeout hanging processes", async () => { + const child = spawn("sleep", ["2"], { timeout: 400 }); + const start = performance.now(); + let end; + await new Promise((resolve) => { + child.on("exit", () => { + end = performance.now(); + resolve(true); + }); + }); + expect(end - start < 2000).toBe(true); + }); + + it("should allow us to set env", async () => { + const child = spawn("env", { env: { TEST: "test" } }); + const result: string = await new Promise((resolve) => { + child.stdout.on("data", (data) => { + resolve(data.toString()); + }); + }); + expect(/TEST\=test/.test(result)).toBe(true); + }); + + it("should allow explicit setting of argv0", async () => { + const child = spawn("node", ["--help"], { argv0: "bun" }); + const result: string = await new Promise((resolve) => { + let msg; + child.stdout.on("data", (data) => { + msg += data.toString(); + }); + + child.stdout.on("close", () => { + resolve(msg); + }); + }); + expect(/bun:/.test(result)).toBe(true); + }); + + it("should allow us to spawn in a shell", async () => { + const result1: string = await new Promise((resolve) => { + const child1 = spawn("echo", ["$0"], { shell: true }); + child1.stdout.on("data", (data) => { + resolve(data.toString()); + }); + }); + const result2: string = await new Promise((resolve) => { + const child2 = spawn("echo", ["$0"], { shell: "bash" }); + child2.stdout.on("data", (data) => { + resolve(data.toString()); + }); + }); + expect(result1.trim()).toBe("/bin/sh"); + expect(result2.trim()).toBe("/bin/bash"); + }); + it("should spawn a process synchronously", () => { + const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); + expect(stdout.trim()).toBe("hello"); + }); +}); + +describe("execFile()", () => { + it("should execute a file", async () => { + const result: Buffer = await new Promise((resolve, reject) => { + execFile("bun", ["-v"], (error, stdout, stderr) => { + if (error) { + reject(error); + } + resolve(stdout); + }); + }); + expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); + }); +}); + +describe("exec()", () => { + it("should execute a command in a shell", async () => { + const result: Buffer = await new Promise((resolve, reject) => { + exec("bun -v", (error, stdout, stderr) => { + if (error) { + reject(error); + } + resolve(stdout); + }); + }); + expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); + }); +}); + +describe("fork()", () => { + it("should throw an error when used", () => { + let err; + try { + fork("index.js"); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + }); +}); + +describe("spawnSync()", () => { + it("should spawn a process synchronously", () => { + const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); + expect(stdout.trim()).toBe("hello"); + }); +}); + +describe("execFileSync()", () => { + it("should execute a file synchronously", () => { + const result = execFileSync("bun", ["-v"], { encoding: "utf8" }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); +}); + +describe("execSync()", () => { + it("should execute a command in the shell synchronously", () => { + const result = execSync("bun -v", { encoding: "utf8" }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); +}); + +// describe("Bun.spawn()", () => { +// it("should return exit code 0 on successful execution", async () => { +// const result = await new Promise((resolve) => { +// Bun.spawn({ +// cmd: ["echo", "hello"], +// encoding: "utf8", +// onExit: (code) => resolve(code), +// stdout: "inherit", +// }); +// }); +// expect(result).toBe(0); +// }); +// it("should fail when given an invalid cwd", () => { +// const child = Bun.spawn({ cmd: ["echo", "hello"], cwd: "/invalid" }); +// expect(child.pid).toBe(undefined); +// }); +// }); |