mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Document & cover some missing spawn/spawnSync options (#24417)
This commit is contained in:
@@ -187,3 +187,45 @@ tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: ["ignore", "inherit"
|
||||
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: [null, null, null] }));
|
||||
|
||||
tsd.expectAssignable<SyncSubprocess<Bun.SpawnOptions.Readable, Bun.SpawnOptions.Readable>>(Bun.spawnSync([], {}));
|
||||
|
||||
// Lazy option types (async only)
|
||||
{
|
||||
// valid: lazy usable with async spawn
|
||||
const p1 = Bun.spawn(["echo", "hello"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
lazy: true,
|
||||
});
|
||||
tsd.expectType(p1.stdout).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
|
||||
}
|
||||
|
||||
{
|
||||
// valid: lazy false is also allowed
|
||||
const p2 = Bun.spawn(["echo", "hello"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
lazy: false,
|
||||
});
|
||||
tsd.expectType(p2.stderr).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
|
||||
}
|
||||
|
||||
{
|
||||
// invalid: lazy is not supported in spawnSync
|
||||
Bun.spawnSync(["echo", "hello"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
// @ts-expect-error lazy applies only to async spawn
|
||||
lazy: true,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// invalid: lazy is not supported in spawnSync (object overload)
|
||||
// @ts-expect-error lazy applies only to async spawn
|
||||
Bun.spawnSync({
|
||||
cmd: ["echo", "hello"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
lazy: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ describe("bun:test", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.each([1, 2, 3])("test.each", a => {
|
||||
expectType<1 | 2 | 3>(a);
|
||||
});
|
||||
|
||||
// inference should work when data is passed directly in
|
||||
test.each([
|
||||
["a", true, 5],
|
||||
|
||||
@@ -480,6 +480,7 @@ for (let [gcTick, label] of [
|
||||
}
|
||||
|
||||
resolve && resolve();
|
||||
// @ts-expect-error
|
||||
resolve = undefined;
|
||||
})();
|
||||
await promise;
|
||||
@@ -679,9 +680,10 @@ describe("should not hang", () => {
|
||||
it(
|
||||
"sleep " + sleep,
|
||||
async () => {
|
||||
const runs = [];
|
||||
const runs: Promise<void>[] = [];
|
||||
|
||||
let initialMaxFD = -1;
|
||||
for (let order of [
|
||||
for (const order of [
|
||||
["sleep", "kill", "unref", "exited"],
|
||||
["sleep", "unref", "kill", "exited"],
|
||||
["kill", "sleep", "unref", "exited"],
|
||||
@@ -789,6 +791,7 @@ describe("close handling", () => {
|
||||
|
||||
await exitPromise;
|
||||
})();
|
||||
|
||||
Bun.gc(false);
|
||||
await Bun.sleep(0);
|
||||
|
||||
@@ -837,3 +840,182 @@ it("error does not UAF", async () => {
|
||||
}
|
||||
expect(emsg).toInclude(" ");
|
||||
});
|
||||
|
||||
describe("onDisconnect", () => {
|
||||
it.todoIf(isWindows)("ipc delivers message", async () => {
|
||||
const msg = Promise.withResolvers<void>();
|
||||
|
||||
let ipcMessage: unknown;
|
||||
|
||||
await using proc = spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
process.send("hello");
|
||||
Promise.resolve().then(() => process.exit(0));
|
||||
`,
|
||||
],
|
||||
ipc: message => {
|
||||
ipcMessage = message;
|
||||
msg.resolve();
|
||||
},
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
await msg.promise;
|
||||
expect(ipcMessage).toBe("hello");
|
||||
expect(await proc.exited).toBe(0);
|
||||
});
|
||||
|
||||
it.todoIf(isWindows)("onDisconnect callback is called when IPC disconnects", async () => {
|
||||
const disc = Promise.withResolvers<void>();
|
||||
|
||||
let disconnectCalled = false;
|
||||
|
||||
await using proc = spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
Promise.resolve().then(() => {
|
||||
process.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
`,
|
||||
],
|
||||
// Ensure IPC channel is opened without relying on a message
|
||||
ipc: () => {},
|
||||
onDisconnect: () => {
|
||||
disconnectCalled = true;
|
||||
disc.resolve();
|
||||
},
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
await disc.promise;
|
||||
expect(disconnectCalled).toBe(true);
|
||||
expect(await proc.exited).toBe(0);
|
||||
});
|
||||
|
||||
it("onDisconnect is not called when IPC is not used", async () => {
|
||||
await using proc = spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('hello')"],
|
||||
onDisconnect: () => {
|
||||
expect().fail("onDisconnect was called()");
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
stdin: "ignore",
|
||||
});
|
||||
expect(await proc.exited).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("argv0", () => {
|
||||
it("argv0 option changes process.argv0 but not executable", async () => {
|
||||
await using proc = spawn({
|
||||
cmd: [bunExe(), "-e", "console.log(process.argv0); console.log(process.execPath)"],
|
||||
argv0: "custom-argv0",
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = await proc.stdout.text();
|
||||
const lines = output.trim().split(/\r?\n/);
|
||||
expect(lines[0]).toBe("custom-argv0");
|
||||
expect(path.normalize(lines[1])).toBe(path.normalize(bunExe()));
|
||||
await proc.exited;
|
||||
});
|
||||
|
||||
it("argv0 option works with spawnSync", () => {
|
||||
const argv0 = "custom-argv0-sync";
|
||||
|
||||
const proc = spawnSync({
|
||||
cmd: [bunExe(), "-e", "console.log(JSON.stringify({ argv0: process.argv0, execPath: process.execPath }))"],
|
||||
argv0,
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = JSON.parse(proc.stdout.toString().trim());
|
||||
expect(output).toEqual({ argv0, execPath: path.normalize(bunExe()) });
|
||||
});
|
||||
|
||||
it("argv0 defaults to cmd[0] when not specified", async () => {
|
||||
await using proc = spawn({
|
||||
cmd: [bunExe(), "-e", "console.log(process.argv0)"],
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = await proc.stdout.text();
|
||||
expect(output.trim()).toBe(bunExe());
|
||||
await proc.exited;
|
||||
});
|
||||
});
|
||||
|
||||
describe("option combinations", () => {
|
||||
it("detached + argv0 works together", async () => {
|
||||
await using proc = spawn({
|
||||
cmd: [bunExe(), "-e", "console.log(process.argv0)"],
|
||||
detached: true,
|
||||
argv0: "custom-name",
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = await proc.stdout.text();
|
||||
expect(output.trim()).toBe("custom-name");
|
||||
await proc.exited;
|
||||
});
|
||||
|
||||
it.todoIf(isWindows)("onDisconnect + ipc + serialization works together", async () => {
|
||||
let messageReceived = false;
|
||||
let disconnectCalled = false;
|
||||
|
||||
const msg = Promise.withResolvers<void>();
|
||||
const disc = Promise.withResolvers<void>();
|
||||
|
||||
await using proc = spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
process.send({type: "hello", data: "world"});
|
||||
Promise.resolve().then(() => {
|
||||
process.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
`,
|
||||
],
|
||||
ipc: message => {
|
||||
expect(message).toEqual({ type: "hello", data: "world" });
|
||||
messageReceived = true;
|
||||
msg.resolve();
|
||||
},
|
||||
onDisconnect: () => {
|
||||
disconnectCalled = true;
|
||||
disc.resolve();
|
||||
},
|
||||
serialization: "advanced",
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
await Promise.all([msg.promise, disc.promise]);
|
||||
expect(messageReceived).toBe(true);
|
||||
expect(disconnectCalled).toBe(true);
|
||||
expect(await proc.exited).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user