Files
bun.sh/test/js/node/process/process-stdin.test.ts
pfg 408fda7ad2 Continue emitting 'readable' events after pausing stdin (#17690)
Fixes #21189

`.pause()` should unref but it should still continue to emit `readable`
events (although it should not send `data` events)

also stdin.unref() should not pause input, it should only prevent stdin
from keeping the process alive.

DRAFT:

- [x] ~~this causes a bug where `process.stdin.on("readable", () => {});
process.stdin.pause()` will allow the process to exit when it
shouldn't.~~ fixed

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-04 21:04:08 -07:00

156 lines
3.7 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("pipe does the right thing", async () => {
// Note: Bun.spawnSync uses memfd_create on Linux for pipe, which means we see
// it as a file instead of a tty
const result = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(typeof process.stdin.ref)"],
stdin: "pipe",
stdout: "pipe",
stderr: "inherit",
env: bunEnv,
});
expect((await new Response(result.stdout).text()).trim()).toBe("function");
expect(await result.exited).toBe(0);
});
test("file does the right thing", async () => {
const result = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(typeof process.stdin.ref)"],
stdin: Bun.file(import.meta.path),
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
expect(await result.stdout.text()).toMatchInlineSnapshot(`
"undefined
"
`);
expect(await result.stderr.text()).toMatchInlineSnapshot(`""`);
expect(await result.exited).toBe(0);
});
test("stdin with 'readable' event handler should receive data when paused", async () => {
const proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const handleReadable = () => {
let chunk;
while ((chunk = process.stdin.read())) {
console.log("got chunk", JSON.stringify(chunk));
}
};
process.stdin.on("readable", handleReadable);
process.stdin.pause();
setTimeout(() => {
process.exit(1);
}, 1000);
`,
],
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
proc.stdin.write("abc\n");
proc.stdin.write("def\n");
proc.stdin.end();
await proc.exited;
expect(await proc.stdout.text()).toMatchInlineSnapshot(`
"got chunk {"type":"Buffer","data":[97,98,99,10,100,101,102,10]}
"
`);
expect(await proc.stderr.text()).toMatchInlineSnapshot(`""`);
expect(proc.exitCode).toBe(1);
});
test("stdin with 'data' event handler should NOT receive data when paused", async () => {
const proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const handleData = chunk => {
console.log("got chunk");
};
process.stdin.on("data", handleData);
process.stdin.pause();
setTimeout(() => {
process.exit(1);
}, 1000);
`,
],
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
proc.stdin.write("abc\n");
proc.stdin.write("def\n");
proc.stdin.end();
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(await proc.stdout.text()).toMatchInlineSnapshot(`""`);
expect(await proc.stderr.text()).toMatchInlineSnapshot(`""`);
expect(proc.exitCode).toBe(1);
});
test("stdin should allow process to exit when paused", async () => {
const proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
process.stdin.on("data", () => {});
process.stdin.pause();
`,
],
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
await proc.exited;
expect(await proc.stdout.text()).toMatchInlineSnapshot(`""`);
expect(await proc.stderr.text()).toMatchInlineSnapshot(`""`);
expect(proc.exitCode).toBe(0);
});
test("stdin should not allow process to exit when not paused", async () => {
const proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
process.stdin.on("data", () => {});
`,
],
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
await Bun.sleep(1000);
expect(proc.exitCode).toBe(null);
proc.kill();
await proc.exited;
expect(await proc.stdout.text()).toMatchInlineSnapshot(`""`);
expect(await proc.stderr.text()).toMatchInlineSnapshot(`""`);
});