mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 04:49:06 +00:00
Even more child process tests passing (#18052)
This commit is contained in:
@@ -178,9 +178,6 @@ function spawn(file, args, options) {
|
||||
abortChildProcess(child, killSignal, signal.reason);
|
||||
}
|
||||
}
|
||||
process.nextTick(() => {
|
||||
child.emit("spawn");
|
||||
});
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -295,11 +292,11 @@ function execFile(file, args, options, callback) {
|
||||
|
||||
if (args?.length) cmd += ` ${ArrayPrototypeJoin.$call(args, " ")}`;
|
||||
if (!ex) {
|
||||
const { getSystemErrorName } = require("node:util");
|
||||
let message = `Command failed: ${cmd}`;
|
||||
if (stderr) message += `\n${stderr}`;
|
||||
ex = genericNodeError(message, {
|
||||
// code: code < 0 ? getSystemErrorName(code) : code, // TODO: Add getSystemErrorName
|
||||
code: code,
|
||||
code: code < 0 ? getSystemErrorName(code) : code,
|
||||
killed: child.killed || killed,
|
||||
signal: signal,
|
||||
});
|
||||
@@ -565,13 +562,21 @@ function spawnSync(file, args, options) {
|
||||
success,
|
||||
exitCode,
|
||||
signalCode,
|
||||
exitedDueToTimeout,
|
||||
pid,
|
||||
} = Bun.spawnSync({
|
||||
cmd: options.args,
|
||||
// normalizeSpawnargs has already prepended argv0 to the spawnargs array
|
||||
// Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command,
|
||||
// so we have to set argv0 to spawnargs[0] and cmd[0] to file
|
||||
cmd: [options.file, ...Array.prototype.slice.$call(options.args, 1)],
|
||||
env: options.env || undefined,
|
||||
cwd: options.cwd || undefined,
|
||||
stdio: bunStdio,
|
||||
windowsVerbatimArguments: options.windowsVerbatimArguments,
|
||||
windowsHide: options.windowsHide,
|
||||
argv0: options.args[0],
|
||||
timeout: options.timeout,
|
||||
killSignal: options.killSignal,
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
@@ -584,6 +589,7 @@ function spawnSync(file, args, options) {
|
||||
status: exitCode,
|
||||
// TODO: Need to expose extra pipes from Bun.spawnSync to child_process
|
||||
output: [null, stdout, stderr],
|
||||
pid,
|
||||
};
|
||||
|
||||
if (error) {
|
||||
@@ -601,16 +607,24 @@ function spawnSync(file, args, options) {
|
||||
result.stdout = result.output[1];
|
||||
result.stderr = result.output[2];
|
||||
|
||||
if (!success && error == null) {
|
||||
result.error = new SystemError(result.output[2], options.file, "spawnSync", -1, result.status);
|
||||
if (exitedDueToTimeout && error == null) {
|
||||
result.error = new SystemError(
|
||||
"spawnSync " + options.file + " ETIMEDOUT",
|
||||
options.file,
|
||||
"spawnSync " + options.file,
|
||||
etimedoutErrorCode(),
|
||||
"ETIMEDOUT",
|
||||
);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
result.error.syscall = "spawnSync " + options.file;
|
||||
result.error.spawnargs = ArrayPrototypeSlice.$call(options.args, 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
const etimedoutErrorCode = $newZigFunction("node_util_binding.zig", "etimedoutErrorCode", 0);
|
||||
|
||||
/**
|
||||
* Spawns a file as a shell synchronously.
|
||||
@@ -770,7 +784,7 @@ function fork(modulePath, args = [], options) {
|
||||
// and stderr from the parent if silent isn't set.
|
||||
options.stdio = stdioStringToArray(options.silent ? "pipe" : "inherit", "ipc");
|
||||
} else if (!ArrayPrototypeIncludes.$call(options.stdio, "ipc")) {
|
||||
throw ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio");
|
||||
throw $ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio");
|
||||
}
|
||||
|
||||
return spawn(options.execPath, args, options);
|
||||
@@ -870,6 +884,7 @@ function normalizeExecArgs(command, options, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
const kBunEnv = Symbol("bunEnv");
|
||||
function normalizeSpawnArguments(file, args, options) {
|
||||
validateString(file, "file");
|
||||
validateArgumentNullCheck(file, "file");
|
||||
@@ -966,7 +981,7 @@ function normalizeSpawnArguments(file, args, options) {
|
||||
}
|
||||
|
||||
const env = options.env || process.env;
|
||||
const envPairs = {};
|
||||
const bunEnv = {};
|
||||
|
||||
// // process.env.NODE_V8_COVERAGE always propagates, making it possible to
|
||||
// // collect coverage for programs that spawn with white-listed environment.
|
||||
@@ -995,7 +1010,7 @@ function normalizeSpawnArguments(file, args, options) {
|
||||
if (value !== undefined) {
|
||||
validateArgumentNullCheck(key, `options.env['${key}']`);
|
||||
validateArgumentNullCheck(value, `options.env['${key}']`);
|
||||
envPairs[key] = value;
|
||||
bunEnv[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,11 +1020,13 @@ function normalizeSpawnArguments(file, args, options) {
|
||||
...options,
|
||||
args,
|
||||
cwd,
|
||||
|
||||
detached: !!options.detached,
|
||||
envPairs,
|
||||
[kBunEnv]: bunEnv,
|
||||
file,
|
||||
windowsHide: !!options.windowsHide,
|
||||
windowsVerbatimArguments: !!windowsVerbatimArguments,
|
||||
argv0: options.argv0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1026,6 +1043,15 @@ function checkExecSyncError(ret, args, cmd?) {
|
||||
}
|
||||
return err;
|
||||
}
|
||||
function parseEnvPairs(envPairs: string[] | undefined): Record<string, string> | undefined {
|
||||
if (!envPairs) return undefined;
|
||||
const resEnv = {};
|
||||
for (const line of envPairs) {
|
||||
const [key, ...value] = line.split("=", 2);
|
||||
resEnv[key] = value.join("=");
|
||||
}
|
||||
return resEnv;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Section 3. ChildProcess class
|
||||
@@ -1241,28 +1267,19 @@ class ChildProcess extends EventEmitter {
|
||||
validateOneOf(options.serialization, "options.serialization", [undefined, "json", "advanced"]);
|
||||
const serialization = options.serialization || "json";
|
||||
|
||||
validateString(options.file, "options.file");
|
||||
// NOTE: This is confusing... So node allows you to pass a file name
|
||||
// But also allows you to pass a command in the args and it should execute
|
||||
// To add another layer of confusion, they also give the option to pass an explicit "argv0"
|
||||
// which overrides the actual command of the spawned process...
|
||||
var file;
|
||||
file = this.spawnfile = options.file;
|
||||
|
||||
var spawnargs;
|
||||
if (options.args == null) {
|
||||
spawnargs = this.spawnargs = [];
|
||||
} else {
|
||||
validateArray(options.args, "options.args");
|
||||
spawnargs = this.spawnargs = options.args;
|
||||
}
|
||||
|
||||
const stdio = options.stdio || ["pipe", "pipe", "pipe"];
|
||||
const bunStdio = getBunStdioFromOptions(stdio);
|
||||
const argv0 = file || options.argv0;
|
||||
|
||||
const has_ipc = $isJSArray(stdio) && stdio[3] === "ipc";
|
||||
var env = options.envPairs || process.env;
|
||||
const has_ipc = $isJSArray(stdio) && stdio.includes("ipc");
|
||||
|
||||
// validate options.envPairs but only if has_ipc. for some reason.
|
||||
if (has_ipc) {
|
||||
if (options.envPairs !== undefined) {
|
||||
validateArray(options.envPairs, "options.envPairs");
|
||||
}
|
||||
}
|
||||
|
||||
var env = options[kBunEnv] || parseEnvPairs(options.envPairs) || process.env;
|
||||
|
||||
const detachedOption = options.detached;
|
||||
this.#encoding = options.encoding || undefined;
|
||||
@@ -1270,55 +1287,84 @@ class ChildProcess extends EventEmitter {
|
||||
const stdioCount = stdio.length;
|
||||
const hasSocketsToEagerlyLoad = stdioCount >= 3;
|
||||
|
||||
this.#handle = Bun.spawn({
|
||||
cmd: spawnargs,
|
||||
stdio: bunStdio,
|
||||
cwd: options.cwd || undefined,
|
||||
env: env,
|
||||
detached: typeof detachedOption !== "undefined" ? !!detachedOption : false,
|
||||
onExit: (handle, exitCode, signalCode, err) => {
|
||||
this.#handle = handle;
|
||||
this.pid = this.#handle.pid;
|
||||
$debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid);
|
||||
validateString(options.file, "options.file");
|
||||
var file;
|
||||
file = this.spawnfile = options.file;
|
||||
|
||||
if (hasSocketsToEagerlyLoad) {
|
||||
process.nextTick(() => {
|
||||
this.stdio;
|
||||
$debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid);
|
||||
});
|
||||
}
|
||||
|
||||
process.nextTick(
|
||||
(exitCode, signalCode, err) => this.#handleOnExit(exitCode, signalCode, err),
|
||||
exitCode,
|
||||
signalCode,
|
||||
err,
|
||||
);
|
||||
},
|
||||
lazy: true,
|
||||
ipc: has_ipc ? this.#emitIpcMessage.bind(this) : undefined,
|
||||
onDisconnect: has_ipc ? ok => this.#disconnect(ok) : undefined,
|
||||
serialization,
|
||||
argv0,
|
||||
windowsHide: !!options.windowsHide,
|
||||
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
|
||||
});
|
||||
this.pid = this.#handle.pid;
|
||||
|
||||
$debug("ChildProcess: spawn", this.pid, spawnargs);
|
||||
|
||||
onSpawnNT(this);
|
||||
|
||||
if (has_ipc) {
|
||||
this.send = this.#send;
|
||||
this.disconnect = this.#disconnect;
|
||||
if (options[kFromNode]) this.#closesNeeded += 1;
|
||||
var spawnargs;
|
||||
if (options.args === undefined) {
|
||||
spawnargs = this.spawnargs = [];
|
||||
// how is this allowed?
|
||||
} else {
|
||||
validateArray(options.args, "options.args");
|
||||
spawnargs = this.spawnargs = options.args;
|
||||
}
|
||||
// normalizeSpawnargs has already prepended argv0 to the spawnargs array
|
||||
// Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command,
|
||||
// so we have to set argv0 to spawnargs[0] and cmd[0] to file
|
||||
|
||||
if (hasSocketsToEagerlyLoad) {
|
||||
for (let item of this.stdio) {
|
||||
item?.ref?.();
|
||||
try {
|
||||
this.#handle = Bun.spawn({
|
||||
cmd: [file, ...Array.prototype.slice.$call(spawnargs, 1)],
|
||||
stdio: bunStdio,
|
||||
cwd: options.cwd || undefined,
|
||||
env: env,
|
||||
detached: typeof detachedOption !== "undefined" ? !!detachedOption : false,
|
||||
onExit: (handle, exitCode, signalCode, err) => {
|
||||
this.#handle = handle;
|
||||
this.pid = this.#handle.pid;
|
||||
$debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid);
|
||||
|
||||
if (hasSocketsToEagerlyLoad) {
|
||||
process.nextTick(() => {
|
||||
this.stdio;
|
||||
$debug("ChildProcess: onExit", exitCode, signalCode, err, this.pid);
|
||||
});
|
||||
}
|
||||
|
||||
process.nextTick(
|
||||
(exitCode, signalCode, err) => this.#handleOnExit(exitCode, signalCode, err),
|
||||
exitCode,
|
||||
signalCode,
|
||||
err,
|
||||
);
|
||||
},
|
||||
lazy: true,
|
||||
ipc: has_ipc ? this.#emitIpcMessage.bind(this) : undefined,
|
||||
onDisconnect: has_ipc ? ok => this.#onDisconnect(ok) : undefined,
|
||||
serialization,
|
||||
argv0: spawnargs[0],
|
||||
windowsHide: !!options.windowsHide,
|
||||
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
|
||||
});
|
||||
this.pid = this.#handle.pid;
|
||||
|
||||
$debug("ChildProcess: spawn", this.pid, spawnargs);
|
||||
|
||||
process.nextTick(() => {
|
||||
this.emit("spawn");
|
||||
});
|
||||
|
||||
if (has_ipc) {
|
||||
this.send = this.#send;
|
||||
this.disconnect = this.#disconnect;
|
||||
if (options[kFromNode]) this.#closesNeeded += 1;
|
||||
}
|
||||
|
||||
if (hasSocketsToEagerlyLoad) {
|
||||
for (let item of this.stdio) {
|
||||
item?.ref?.();
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex == null || typeof ex !== "object" || !Object.hasOwn(ex, "errno")) throw ex;
|
||||
this.#handle = null;
|
||||
ex.syscall = "spawn " + this.spawnfile;
|
||||
ex.spawnargs = Array.prototype.slice.$call(this.spawnargs, 1);
|
||||
process.nextTick(() => {
|
||||
this.emit("error", ex);
|
||||
this.emit("close", (ex as SystemError).errno ?? -1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,7 +1398,7 @@ class ChildProcess extends EventEmitter {
|
||||
// Bun does not handle handles yet
|
||||
try {
|
||||
this.#handle.send(message);
|
||||
if (callback) process.nextTick(callback);
|
||||
if (callback) process.nextTick(callback, null);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (callback) {
|
||||
@@ -1364,18 +1410,21 @@ class ChildProcess extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
#disconnect(ok) {
|
||||
if (ok == null) {
|
||||
$assert(this.connected);
|
||||
this.#handle.disconnect();
|
||||
} else if (!ok) {
|
||||
#onDisconnect(firstTime: boolean) {
|
||||
if (!firstTime) {
|
||||
// strange
|
||||
return;
|
||||
}
|
||||
$assert(!this.connected);
|
||||
this.#maybeClose();
|
||||
process.nextTick(() => this.emit("disconnect"));
|
||||
}
|
||||
#disconnect() {
|
||||
if (!this.connected) {
|
||||
this.emit("error", $ERR_IPC_DISCONNECTED());
|
||||
return;
|
||||
}
|
||||
this.#handle.disconnect();
|
||||
$assert(!this.connected);
|
||||
process.nextTick(() => this.emit("disconnect"));
|
||||
this.#maybeClose();
|
||||
}
|
||||
|
||||
kill(sig?) {
|
||||
@@ -1552,10 +1601,6 @@ function normalizeStdio(stdio): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
function onSpawnNT(self) {
|
||||
self.emit("spawn");
|
||||
}
|
||||
|
||||
function abortChildProcess(child, killSignal, reason) {
|
||||
if (!child) return;
|
||||
try {
|
||||
@@ -1599,6 +1644,10 @@ class ShimmedStdioOutStream extends EventEmitter {
|
||||
destroy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
setEncoding() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1695,11 +1744,10 @@ var Error = globalThis.Error;
|
||||
var TypeError = globalThis.TypeError;
|
||||
var RangeError = globalThis.RangeError;
|
||||
|
||||
function genericNodeError(message, options) {
|
||||
function genericNodeError(message, errorProperties) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const err = new Error(message);
|
||||
err.code = options.code;
|
||||
err.killed = options.killed;
|
||||
err.signal = options.signal;
|
||||
ObjectAssign(err, errorProperties);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1852,12 +1900,6 @@ function ERR_INVALID_OPT_VALUE(name, value) {
|
||||
return err;
|
||||
}
|
||||
|
||||
function ERR_CHILD_PROCESS_IPC_REQUIRED(name) {
|
||||
const err = new TypeError(`Forked processes must have an IPC channel, missing value 'ipc' in ${name}`);
|
||||
err.code = "ERR_CHILD_PROCESS_IPC_REQUIRED";
|
||||
return err;
|
||||
}
|
||||
|
||||
class SystemError extends Error {
|
||||
path;
|
||||
syscall;
|
||||
|
||||
@@ -12,8 +12,6 @@ function initializeClusterIPC() {
|
||||
cluster._setupWorker();
|
||||
// Make sure it's not accidentally inherited by child processes.
|
||||
delete process.env.NODE_UNIQUE_ID;
|
||||
|
||||
process.channel.unref();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user