mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 07:28:53 +00:00
Compare commits
9 Commits
ciro/fix-a
...
pfg/child-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45b1582620 | ||
|
|
4da8ce1c1c | ||
|
|
58dbcb0e91 | ||
|
|
57376f2a2c | ||
|
|
2d0829b5bb | ||
|
|
887ee539ba | ||
|
|
d828f25837 | ||
|
|
f1dc926d68 | ||
|
|
be1fdbe810 |
@@ -2236,9 +2236,21 @@ pub fn spawnMaybeSync(
|
||||
&spawn_options,
|
||||
@ptrCast(argv.items.ptr),
|
||||
@ptrCast(env_array.items.ptr),
|
||||
) catch |err| {
|
||||
spawn_options.deinit();
|
||||
return globalThis.throwError(err, ": failed to spawn process") catch return .zero;
|
||||
) catch |err| switch (err) {
|
||||
error.EMFILE, error.ENFILE => {
|
||||
spawn_options.deinit();
|
||||
const display_path: [:0]const u8 = if (argv.items.len > 0 and argv.items[0] != null)
|
||||
std.mem.sliceTo(argv.items[0].?, 0)
|
||||
else
|
||||
"";
|
||||
var systemerror = bun.sys.Error.fromCode(if (err == error.EMFILE) .MFILE else .NFILE, .posix_spawn).withPath(display_path).toSystemError();
|
||||
systemerror.errno = if (err == error.EMFILE) -bun.C.UV_EMFILE else -bun.C.UV_ENFILE;
|
||||
return globalThis.throwValue(systemerror.toErrorInstance(globalThis));
|
||||
},
|
||||
else => {
|
||||
spawn_options.deinit();
|
||||
return globalThis.throwError(err, ": failed to spawn process") catch return .zero;
|
||||
},
|
||||
}) {
|
||||
.err => |err| {
|
||||
spawn_options.deinit();
|
||||
|
||||
@@ -1163,6 +1163,8 @@ class ChildProcess extends EventEmitter {
|
||||
return null;
|
||||
case "destroyed":
|
||||
return new ShimmedStdin();
|
||||
case "undefined":
|
||||
return undefined;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -1183,6 +1185,8 @@ class ChildProcess extends EventEmitter {
|
||||
}
|
||||
case "destroyed":
|
||||
return new ShimmedStdioOutStream();
|
||||
case "undefined":
|
||||
return undefined;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -1213,6 +1217,9 @@ class ChildProcess extends EventEmitter {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const element = opts[i];
|
||||
|
||||
if (element === "undefined") {
|
||||
return undefined;
|
||||
}
|
||||
if (element !== "pipe") {
|
||||
result[i] = null;
|
||||
continue;
|
||||
@@ -1357,14 +1364,33 @@ class ChildProcess extends EventEmitter {
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
if (
|
||||
ex != null &&
|
||||
typeof ex === "object" &&
|
||||
Object.hasOwn(ex, "code") &&
|
||||
// node sends these errors on the next tick rather than throwing
|
||||
(ex.code === "EACCES" ||
|
||||
ex.code === "EAGAIN" ||
|
||||
ex.code === "EMFILE" ||
|
||||
ex.code === "ENFILE" ||
|
||||
ex.code === "ENOENT")
|
||||
) {
|
||||
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);
|
||||
});
|
||||
if (ex.code === "EMFILE" || ex.code === "ENFILE") {
|
||||
// emfile/enfile error; in this case node does not initialize stdio streams.
|
||||
this.#stdioOptions[0] = "undefined";
|
||||
this.#stdioOptions[1] = "undefined";
|
||||
this.#stdioOptions[2] = "undefined";
|
||||
}
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3872,8 +3872,7 @@ pub fn wtf8Sequence(code_point: u32) [4]u8 {
|
||||
|
||||
pub inline fn wtf8ByteSequenceLength(first_byte: u8) u3 {
|
||||
return switch (first_byte) {
|
||||
0 => 0,
|
||||
1...0x80 - 1 => 1,
|
||||
0...0x80 - 1 => 1,
|
||||
else => if ((first_byte & 0xE0) == 0xC0)
|
||||
@as(u3, 2)
|
||||
else if ((first_byte & 0xF0) == 0xE0)
|
||||
|
||||
43
test/js/node/test/parallel/test-child-process-detached.js
Normal file
43
test/js/node/test/parallel/test-child-process-detached.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const spawn = require('child_process').spawn;
|
||||
const childPath = fixtures.path('parent-process-nonpersistent.js');
|
||||
let persistentPid = -1;
|
||||
|
||||
const child = spawn(process.execPath, [ childPath ]);
|
||||
|
||||
child.stdout.on('data', function(data) {
|
||||
persistentPid = parseInt(data, 10);
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.notStrictEqual(persistentPid, -1);
|
||||
assert.throws(function() {
|
||||
process.kill(child.pid);
|
||||
}, /^Error: kill ESRCH$|^SystemError: kill\(\) failed: ESRCH: No such process$/);
|
||||
process.kill(persistentPid);
|
||||
});
|
||||
78
test/js/node/test/parallel/test-child-process-emfile.js
Normal file
78
test/js/node/test/parallel/test-child-process-emfile.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows)
|
||||
common.skip('no RLIMIT_NOFILE on Windows');
|
||||
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
const ulimit = Number(child_process.execSync('ulimit -Hn'));
|
||||
if (ulimit > 64 || Number.isNaN(ulimit)) {
|
||||
const [cmd, opts] = common.escapePOSIXShell`ulimit -n 64 && "${process.execPath}" "${__filename}"`;
|
||||
// Sorry about this nonsense. It can be replaced if
|
||||
// https://github.com/nodejs/node-v0.x-archive/pull/2143#issuecomment-2847886
|
||||
// ever happens.
|
||||
const result = child_process.spawnSync(
|
||||
'/bin/sh',
|
||||
['-c', cmd],
|
||||
opts,
|
||||
);
|
||||
assert.strictEqual(result.stdout.toString(), '');
|
||||
assert.strictEqual(result.stderr.toString(), '');
|
||||
assert.strictEqual(result.status, 0);
|
||||
assert.strictEqual(result.error, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const openFds = [];
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
openFds.push(fs.openSync(__filename, 'r'));
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.code, 'EMFILE');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should emit an error, not throw.
|
||||
const proc = child_process.spawn(process.execPath, ['-e', '0']);
|
||||
|
||||
// Verify that stdio is not setup on EMFILE or ENFILE.
|
||||
assert.strictEqual(proc.stdin, undefined);
|
||||
assert.strictEqual(proc.stdout, undefined);
|
||||
assert.strictEqual(proc.stderr, undefined);
|
||||
assert.strictEqual(proc.stdio, undefined);
|
||||
|
||||
proc.on('error', common.mustCall(function(err) {
|
||||
assert.strictEqual(err.code, 'EMFILE');
|
||||
}));
|
||||
|
||||
proc.on('exit', common.mustNotCall('"exit" event should not be emitted'));
|
||||
|
||||
// Close one fd for LSan
|
||||
if (openFds.length >= 1) {
|
||||
fs.closeSync(openFds.pop());
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const exec = require('child_process').exec;
|
||||
const { promisify } = require('util');
|
||||
|
||||
const execPromisifed = promisify(exec);
|
||||
const invalidArgTypeError = {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError'
|
||||
};
|
||||
|
||||
const waitCommand = common.isWindows ?
|
||||
// `"` is forbidden for Windows paths, no need for escaping.
|
||||
`"${process.execPath}" -e "setInterval(()=>{}, 99)"` :
|
||||
'sleep 2m';
|
||||
|
||||
{
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
assert.rejects(promise, {
|
||||
name: 'AbortError',
|
||||
cause: new DOMException('This operation was aborted', 'AbortError'),
|
||||
}).then(common.mustCall());
|
||||
ac.abort();
|
||||
}
|
||||
|
||||
{
|
||||
const err = new Error('boom');
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
assert.rejects(promise, {
|
||||
name: 'AbortError',
|
||||
cause: err
|
||||
}).then(common.mustCall());
|
||||
ac.abort(err);
|
||||
}
|
||||
|
||||
{
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
assert.rejects(promise, {
|
||||
name: 'AbortError',
|
||||
cause: 'boom'
|
||||
}).then(common.mustCall());
|
||||
ac.abort('boom');
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
execPromisifed(waitCommand, { signal: {} });
|
||||
}, invalidArgTypeError);
|
||||
}
|
||||
|
||||
{
|
||||
function signal() {}
|
||||
assert.throws(() => {
|
||||
execPromisifed(waitCommand, { signal });
|
||||
}, invalidArgTypeError);
|
||||
}
|
||||
|
||||
{
|
||||
const signal = AbortSignal.abort(); // Abort in advance
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
|
||||
assert.rejects(promise, { name: 'AbortError' })
|
||||
.then(common.mustCall());
|
||||
}
|
||||
|
||||
{
|
||||
const err = new Error('boom');
|
||||
const signal = AbortSignal.abort(err); // Abort in advance
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
|
||||
assert.rejects(promise, { name: 'AbortError', cause: err })
|
||||
.then(common.mustCall());
|
||||
}
|
||||
|
||||
{
|
||||
const signal = AbortSignal.abort('boom'); // Abort in advance
|
||||
const promise = execPromisifed(waitCommand, { signal });
|
||||
|
||||
assert.rejects(promise, { name: 'AbortError', cause: 'boom' })
|
||||
.then(common.mustCall());
|
||||
}
|
||||
146
test/js/node/test/parallel/test-child-process-exec-maxbuf.js
Normal file
146
test/js/node/test/parallel/test-child-process-exec-maxbuf.js
Normal file
@@ -0,0 +1,146 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
|
||||
function runChecks(err, stdio, streamName, expected) {
|
||||
assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`);
|
||||
assert(err instanceof RangeError);
|
||||
assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER');
|
||||
assert.deepStrictEqual(stdio[streamName], expected);
|
||||
}
|
||||
|
||||
// The execPath might contain chars that should be escaped in a shell context.
|
||||
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
|
||||
// Windows, so we can simply pass the path.
|
||||
const execNode = (args, optionsOrCallback, callback) => {
|
||||
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" `;
|
||||
let options = optionsOrCallback;
|
||||
if (typeof optionsOrCallback === 'function') {
|
||||
options = undefined;
|
||||
callback = optionsOrCallback;
|
||||
}
|
||||
return cp.exec(
|
||||
cmd + args,
|
||||
{ ...opts, ...options },
|
||||
callback,
|
||||
);
|
||||
};
|
||||
|
||||
// default value
|
||||
{
|
||||
execNode(`-e "console.log('a'.repeat(1024 * 1024))"`, common.mustCall((err) => {
|
||||
assert(err instanceof RangeError);
|
||||
assert.strictEqual(err.message, 'stdout maxBuffer length exceeded');
|
||||
assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER');
|
||||
}));
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
execNode(`-e "console.log('a'.repeat(1024 * 1024 - 1))"`, common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1));
|
||||
assert.strictEqual(stderr, '');
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const options = { maxBuffer: Infinity };
|
||||
|
||||
execNode(`-e "console.log('hello world');"`, options, common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), 'hello world');
|
||||
assert.strictEqual(stderr, '');
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const cmd = 'echo hello world';
|
||||
|
||||
cp.exec(
|
||||
cmd,
|
||||
{ maxBuffer: 5 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stdout', 'hello');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
execNode(
|
||||
`-e "console.log('a'.repeat(1024 * 1024))"`,
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(
|
||||
err,
|
||||
{ stdout, stderr },
|
||||
'stdout',
|
||||
'a'.repeat(1024 * 1024)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
execNode(`-e "console.log('a'.repeat(1024 * 1024 - 1))"`, common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1));
|
||||
assert.strictEqual(stderr, '');
|
||||
}));
|
||||
}
|
||||
|
||||
const unicode = '中文测试'; // length = 4, byte length = 12
|
||||
|
||||
{
|
||||
execNode(
|
||||
`-e "console.log('${unicode}');"`,
|
||||
{ maxBuffer: 10 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
execNode(
|
||||
`-e "console.error('${unicode}');"`,
|
||||
{ maxBuffer: 3 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stderr', '中文测');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const child = execNode(
|
||||
`-e "console.log('${unicode}');"`,
|
||||
{ encoding: null, maxBuffer: 10 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
|
||||
})
|
||||
);
|
||||
|
||||
child.stdout.setEncoding('utf-8');
|
||||
}
|
||||
|
||||
{
|
||||
const child = execNode(
|
||||
`-e "console.error('${unicode}');"`,
|
||||
{ encoding: null, maxBuffer: 3 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
runChecks(err, { stdout, stderr }, 'stderr', '中文测');
|
||||
})
|
||||
);
|
||||
|
||||
child.stderr.setEncoding('utf-8');
|
||||
}
|
||||
|
||||
{
|
||||
execNode(
|
||||
`-e "console.error('${unicode}');"`,
|
||||
{ encoding: null, maxBuffer: 5 },
|
||||
common.mustCall((err, stdout, stderr) => {
|
||||
const buf = Buffer.from(unicode).slice(0, 5);
|
||||
runChecks(err, { stdout, stderr }, 'stderr', buf);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { execFile } = require('child_process');
|
||||
|
||||
function checkFactory(streamName) {
|
||||
return common.mustCall((err) => {
|
||||
assert(err instanceof RangeError);
|
||||
assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`);
|
||||
assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER');
|
||||
});
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
execFile(
|
||||
process.execPath,
|
||||
['-e', 'console.log("a".repeat(1024 * 1024))'],
|
||||
checkFactory('stdout')
|
||||
);
|
||||
}
|
||||
|
||||
// default value
|
||||
{
|
||||
execFile(
|
||||
process.execPath,
|
||||
['-e', 'console.log("a".repeat(1024 * 1024 - 1))'],
|
||||
common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1));
|
||||
assert.strictEqual(stderr, '');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const options = { maxBuffer: Infinity };
|
||||
|
||||
execFile(
|
||||
process.execPath,
|
||||
['-e', 'console.log("hello world");'],
|
||||
options,
|
||||
common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), 'hello world');
|
||||
assert.strictEqual(stderr, '');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
execFile('echo', ['hello world'], { maxBuffer: 5 }, checkFactory('stdout'));
|
||||
}
|
||||
|
||||
const unicode = '中文测试'; // length = 4, byte length = 12
|
||||
|
||||
{
|
||||
execFile(
|
||||
process.execPath,
|
||||
['-e', `console.log('${unicode}');`],
|
||||
{ maxBuffer: 10 },
|
||||
checkFactory('stdout'));
|
||||
}
|
||||
|
||||
{
|
||||
execFile(
|
||||
process.execPath,
|
||||
['-e', `console.error('${unicode}');`],
|
||||
{ maxBuffer: 10 },
|
||||
checkFactory('stderr')
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const child = execFile(
|
||||
process.execPath,
|
||||
['-e', `console.log('${unicode}');`],
|
||||
{ encoding: null, maxBuffer: 10 },
|
||||
checkFactory('stdout')
|
||||
);
|
||||
|
||||
child.stdout.setEncoding('utf-8');
|
||||
}
|
||||
|
||||
{
|
||||
const child = execFile(
|
||||
process.execPath,
|
||||
['-e', `console.error('${unicode}');`],
|
||||
{ encoding: null, maxBuffer: 10 },
|
||||
checkFactory('stderr')
|
||||
);
|
||||
|
||||
child.stderr.setEncoding('utf-8');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// This test checks that the maxBuffer option for child_process.execFileSync()
|
||||
// works as expected.
|
||||
|
||||
const assert = require('assert');
|
||||
const { getSystemErrorName } = require('util');
|
||||
const { execFileSync } = require('child_process');
|
||||
const msgOut = 'this is stdout';
|
||||
const msgOutBuf = Buffer.from(`${msgOut}\n`);
|
||||
|
||||
const args = [
|
||||
'-e',
|
||||
`console.log("${msgOut}");`,
|
||||
];
|
||||
|
||||
// Verify that an error is returned if maxBuffer is surpassed.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execFileSync(process.execPath, args, { maxBuffer: 1 });
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS');
|
||||
// We can have buffers larger than maxBuffer because underneath we alloc 64k
|
||||
// that matches our read sizes.
|
||||
assert.deepStrictEqual(e.stdout, msgOutBuf);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that a maxBuffer size of Infinity works.
|
||||
{
|
||||
const ret = execFileSync(process.execPath, args, { maxBuffer: Infinity });
|
||||
|
||||
assert.deepStrictEqual(ret, msgOutBuf);
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 1024 * 1024.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
['-e', "console.log('a'.repeat(1024 * 1024))"]
|
||||
);
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
const { escapePOSIXShell } = require('../common');
|
||||
|
||||
// This test checks that the maxBuffer option for child_process.spawnSync()
|
||||
// works as expected.
|
||||
|
||||
const assert = require('assert');
|
||||
const { getSystemErrorName } = require('util');
|
||||
const { execSync } = require('child_process');
|
||||
const msgOut = 'this is stdout';
|
||||
const msgOutBuf = Buffer.from(`${msgOut}\n`);
|
||||
|
||||
const [cmd, opts] = escapePOSIXShell`"${process.execPath}" -e "${`console.log('${msgOut}')`}"`;
|
||||
|
||||
// Verify that an error is returned if maxBuffer is surpassed.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execSync(cmd, { ...opts, maxBuffer: 1 });
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS');
|
||||
// We can have buffers larger than maxBuffer because underneath we alloc 64k
|
||||
// that matches our read sizes.
|
||||
assert.deepStrictEqual(e.stdout, msgOutBuf);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that a maxBuffer size of Infinity works.
|
||||
{
|
||||
const ret = execSync(
|
||||
cmd,
|
||||
{ ...opts, maxBuffer: Infinity },
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(ret, msgOutBuf);
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 1024 * 1024.
|
||||
{
|
||||
assert.throws(() => {
|
||||
execSync(...escapePOSIXShell`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`);
|
||||
}, (e) => {
|
||||
assert.ok(e, 'maxBuffer should error');
|
||||
assert.strictEqual(e.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 1024 * 1024.
|
||||
{
|
||||
const ret = execSync(...escapePOSIXShell`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024 - 1))"`);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
ret.toString().trim(),
|
||||
'a'.repeat(1024 * 1024 - 1)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// Before https://github.com/nodejs/node/pull/2847 a child process trying
|
||||
// (asynchronously) to use the closed channel to it's creator caused a segfault.
|
||||
|
||||
const assert = require('assert');
|
||||
const cluster = require('cluster');
|
||||
const net = require('net');
|
||||
|
||||
if (!cluster.isPrimary) {
|
||||
// Exit on first received handle to leave the queue non-empty in primary
|
||||
process.on('message', function() {
|
||||
process.exit(1);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const server = net
|
||||
.createServer(function(s) {
|
||||
if (common.isWindows) {
|
||||
s.on('error', function(err) {
|
||||
// Prevent possible ECONNRESET errors from popping up
|
||||
if (err.code !== 'ECONNRESET') throw err;
|
||||
});
|
||||
}
|
||||
setTimeout(function() {
|
||||
s.destroy();
|
||||
}, 100);
|
||||
})
|
||||
.listen(0, function() {
|
||||
const worker = cluster.fork();
|
||||
|
||||
worker.on('error', function(err) {
|
||||
if (
|
||||
err.code !== 'ECONNRESET' &&
|
||||
err.code !== 'ECONNREFUSED' &&
|
||||
err.code !== 'EMFILE'
|
||||
) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
function send(callback) {
|
||||
const s = net.connect(server.address().port, function() {
|
||||
worker.send({}, s, callback);
|
||||
});
|
||||
|
||||
// https://github.com/nodejs/node/issues/3635#issuecomment-157714683
|
||||
// ECONNREFUSED or ECONNRESET errors can happen if this connection is
|
||||
// still establishing while the server has already closed.
|
||||
// EMFILE can happen if the worker __and__ the server had already closed.
|
||||
s.on('error', function(err) {
|
||||
if (
|
||||
err.code !== 'ECONNRESET' &&
|
||||
err.code !== 'ECONNREFUSED' &&
|
||||
err.code !== 'EMFILE'
|
||||
) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
worker.process.once(
|
||||
'close',
|
||||
common.mustCall(function() {
|
||||
// Otherwise the crash on `channel.fd` access may happen
|
||||
assert.strictEqual(worker.process.channel, null);
|
||||
server.close();
|
||||
})
|
||||
);
|
||||
|
||||
worker.on('online', function() {
|
||||
send(function(err) {
|
||||
assert.ifError(err);
|
||||
send(function(err) {
|
||||
// Ignore errors when sending the second handle because the worker
|
||||
// may already have exited.
|
||||
if (err && err.code !== 'ERR_IPC_CHANNEL_CLOSED' &&
|
||||
err.code !== 'ECONNRESET' &&
|
||||
err.code !== 'ECONNREFUSED' &&
|
||||
err.code !== 'EMFILE') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
109
test/js/node/test/parallel/test-child-process-fork-dgram.js
Normal file
109
test/js/node/test/parallel/test-child-process-fork-dgram.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
|
||||
// The purpose of this test is to make sure that when forking a process,
|
||||
// sending a fd representing a UDP socket to the child and sending messages
|
||||
// to this endpoint, these messages are distributed to the parent and the
|
||||
// child process.
|
||||
|
||||
|
||||
const common = require('../common');
|
||||
if (common.isWindows)
|
||||
common.skip('Sending dgram sockets to child processes is not supported');
|
||||
|
||||
const dgram = require('dgram');
|
||||
const fork = require('child_process').fork;
|
||||
const assert = require('assert');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
let childServer;
|
||||
|
||||
process.once('message', (msg, clusterServer) => {
|
||||
childServer = clusterServer;
|
||||
|
||||
childServer.once('message', () => {
|
||||
process.send('gotMessage');
|
||||
childServer.close();
|
||||
});
|
||||
|
||||
process.send('handleReceived');
|
||||
});
|
||||
|
||||
} else {
|
||||
const parentServer = dgram.createSocket('udp4');
|
||||
const client = dgram.createSocket('udp4');
|
||||
const child = fork(__filename, ['child']);
|
||||
|
||||
const msg = Buffer.from('Some bytes');
|
||||
|
||||
let childGotMessage = false;
|
||||
let parentGotMessage = false;
|
||||
|
||||
parentServer.once('message', (msg, rinfo) => {
|
||||
parentGotMessage = true;
|
||||
parentServer.close();
|
||||
});
|
||||
|
||||
parentServer.on('listening', () => {
|
||||
child.send('server', parentServer);
|
||||
|
||||
child.on('message', (msg) => {
|
||||
if (msg === 'gotMessage') {
|
||||
childGotMessage = true;
|
||||
} else if (msg === 'handleReceived') {
|
||||
sendMessages();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function sendMessages() {
|
||||
const serverPort = parentServer.address().port;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
// Both the parent and the child got at least one message,
|
||||
// test passed, clean up everything.
|
||||
if (parentGotMessage && childGotMessage) {
|
||||
clearInterval(timer);
|
||||
client.close();
|
||||
} else {
|
||||
client.send(
|
||||
msg,
|
||||
0,
|
||||
msg.length,
|
||||
serverPort,
|
||||
'127.0.0.1',
|
||||
(err) => {
|
||||
assert.ifError(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
parentServer.bind(0, '127.0.0.1');
|
||||
|
||||
process.once('exit', () => {
|
||||
assert(parentGotMessage);
|
||||
assert(childGotMessage);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const fork = require('child_process').fork;
|
||||
const net = require('net');
|
||||
const count = 12;
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const sockets = [];
|
||||
|
||||
process.on('message', common.mustCall((m, socket) => {
|
||||
function sendClosed(id) {
|
||||
process.send({ id: id, status: 'closed' });
|
||||
}
|
||||
|
||||
if (m.cmd === 'new') {
|
||||
assert(socket);
|
||||
assert(socket instanceof net.Socket, 'should be a net.Socket');
|
||||
sockets.push(socket);
|
||||
}
|
||||
|
||||
if (m.cmd === 'close') {
|
||||
assert.strictEqual(socket, undefined);
|
||||
if (sockets[m.id].destroyed) {
|
||||
// Workaround for https://github.com/nodejs/node/issues/2610
|
||||
sendClosed(m.id);
|
||||
// End of workaround. When bug is fixed, this code can be used instead:
|
||||
// throw new Error('socket destroyed unexpectedly!');
|
||||
} else {
|
||||
sockets[m.id].once('close', sendClosed.bind(null, m.id));
|
||||
sockets[m.id].destroy();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
} else {
|
||||
const child = fork(process.argv[1], ['child']);
|
||||
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
if (!subprocessKilled) {
|
||||
assert.fail('subprocess died unexpectedly! ' +
|
||||
`code: ${code} signal: ${signal}`);
|
||||
}
|
||||
}));
|
||||
|
||||
const server = net.createServer();
|
||||
const sockets = [];
|
||||
|
||||
server.on('connection', common.mustCall((socket) => {
|
||||
child.send({ cmd: 'new' }, socket);
|
||||
sockets.push(socket);
|
||||
|
||||
if (sockets.length === count) {
|
||||
closeSockets(0);
|
||||
}
|
||||
}, count));
|
||||
|
||||
const onClose = common.mustCall(count);
|
||||
|
||||
server.on('listening', common.mustCall(() => {
|
||||
let j = count;
|
||||
while (j--) {
|
||||
const client = net.connect(server.address().port, '127.0.0.1');
|
||||
client.on('close', onClose);
|
||||
}
|
||||
}));
|
||||
|
||||
let subprocessKilled = false;
|
||||
function closeSockets(i) {
|
||||
if (i === count) {
|
||||
subprocessKilled = true;
|
||||
server.close();
|
||||
child.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
child.once('message', common.mustCall((m) => {
|
||||
assert.strictEqual(m.status, 'closed');
|
||||
server.getConnections(common.mustSucceed((num) => {
|
||||
assert.strictEqual(num, count - (i + 1));
|
||||
closeSockets(i + 1);
|
||||
}));
|
||||
}));
|
||||
child.send({ id: i, cmd: 'close' });
|
||||
}
|
||||
|
||||
server.on('close', common.mustCall());
|
||||
|
||||
server.listen(0, '127.0.0.1');
|
||||
}
|
||||
159
test/js/node/test/parallel/test-child-process-fork-net-server.js
Normal file
159
test/js/node/test/parallel/test-child-process-fork-net-server.js
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const fork = require('child_process').fork;
|
||||
const net = require('net');
|
||||
const debug = require('util').debuglog('test');
|
||||
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
|
||||
let serverScope;
|
||||
|
||||
// TODO(@jasnell): The message event is not called consistently
|
||||
// across platforms. Need to investigate if it can be made
|
||||
// more consistent.
|
||||
const onServer = (msg, server) => {
|
||||
if (msg.what !== 'server') return;
|
||||
process.removeListener('message', onServer);
|
||||
|
||||
serverScope = server;
|
||||
|
||||
// TODO(@jasnell): This is apparently not called consistently
|
||||
// across platforms. Need to investigate if it can be made
|
||||
// more consistent.
|
||||
server.on('connection', (socket) => {
|
||||
debug('CHILD: got connection');
|
||||
process.send({ what: 'connection' });
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
// Start making connection from parent.
|
||||
debug('CHILD: server listening');
|
||||
process.send({ what: 'listening' });
|
||||
};
|
||||
|
||||
process.on('message', onServer);
|
||||
|
||||
// TODO(@jasnell): The close event is not called consistently
|
||||
// across platforms. Need to investigate if it can be made
|
||||
// more consistent.
|
||||
const onClose = (msg) => {
|
||||
if (msg.what !== 'close') return;
|
||||
process.removeListener('message', onClose);
|
||||
|
||||
serverScope.on('close', common.mustCall(() => {
|
||||
process.send({ what: 'close' });
|
||||
}));
|
||||
serverScope.close();
|
||||
};
|
||||
|
||||
process.on('message', onClose);
|
||||
|
||||
process.send({ what: 'ready' });
|
||||
} else {
|
||||
|
||||
const child = fork(process.argv[1], ['child']);
|
||||
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
const message = `CHILD: died with ${code}, ${signal}`;
|
||||
assert.strictEqual(code, 0, message);
|
||||
}));
|
||||
|
||||
// Send net.Server to child and test by connecting.
|
||||
function testServer(callback) {
|
||||
|
||||
// Destroy server execute callback when done.
|
||||
const countdown = new Countdown(2, () => {
|
||||
server.on('close', common.mustCall(() => {
|
||||
debug('PARENT: server closed');
|
||||
child.send({ what: 'close' });
|
||||
}));
|
||||
server.close();
|
||||
});
|
||||
|
||||
// We expect 4 connections and close events.
|
||||
const connections = new Countdown(4, () => countdown.dec());
|
||||
const closed = new Countdown(4, () => countdown.dec());
|
||||
|
||||
// Create server and send it to child.
|
||||
const server = net.createServer();
|
||||
|
||||
// TODO(@jasnell): The specific number of times the connection
|
||||
// event is emitted appears to be variable across platforms.
|
||||
// Need to investigate why and whether it can be made
|
||||
// more consistent.
|
||||
server.on('connection', (socket) => {
|
||||
debug('PARENT: got connection');
|
||||
socket.destroy();
|
||||
connections.dec();
|
||||
});
|
||||
|
||||
server.on('listening', common.mustCall(() => {
|
||||
debug('PARENT: server listening');
|
||||
child.send({ what: 'server' }, server);
|
||||
}));
|
||||
server.listen(0);
|
||||
|
||||
// Handle client messages.
|
||||
// TODO(@jasnell): The specific number of times the message
|
||||
// event is emitted appears to be variable across platforms.
|
||||
// Need to investigate why and whether it can be made
|
||||
// more consistent.
|
||||
const messageHandlers = (msg) => {
|
||||
if (msg.what === 'listening') {
|
||||
// Make connections.
|
||||
let socket;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
socket = net.connect(server.address().port, common.mustCall(() => {
|
||||
debug('CLIENT: connected');
|
||||
}));
|
||||
socket.on('close', common.mustCall(() => {
|
||||
closed.dec();
|
||||
debug('CLIENT: closed');
|
||||
}));
|
||||
}
|
||||
|
||||
} else if (msg.what === 'connection') {
|
||||
// Child got connection
|
||||
connections.dec();
|
||||
} else if (msg.what === 'close') {
|
||||
child.removeListener('message', messageHandlers);
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
child.on('message', messageHandlers);
|
||||
}
|
||||
|
||||
const onReady = common.mustCall((msg) => {
|
||||
if (msg.what !== 'ready') return;
|
||||
child.removeListener('message', onReady);
|
||||
testServer(common.mustCall());
|
||||
});
|
||||
|
||||
// Create server and send it to child.
|
||||
child.on('message', onReady);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const {
|
||||
mustCall,
|
||||
mustCallAtLeast,
|
||||
} = require('../common');
|
||||
const assert = require('assert');
|
||||
const fork = require('child_process').fork;
|
||||
const net = require('net');
|
||||
const debug = require('util').debuglog('test');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
|
||||
const onSocket = mustCall((msg, socket) => {
|
||||
if (msg.what !== 'socket') return;
|
||||
process.removeListener('message', onSocket);
|
||||
socket.end('echo');
|
||||
debug('CHILD: got socket');
|
||||
});
|
||||
|
||||
process.on('message', onSocket);
|
||||
|
||||
process.send({ what: 'ready' });
|
||||
} else {
|
||||
|
||||
const child = fork(process.argv[1], ['child']);
|
||||
|
||||
child.on('exit', mustCall((code, signal) => {
|
||||
const message = `CHILD: died with ${code}, ${signal}`;
|
||||
assert.strictEqual(code, 0, message);
|
||||
}));
|
||||
|
||||
// Send net.Socket to child.
|
||||
function testSocket() {
|
||||
|
||||
// Create a new server and connect to it,
|
||||
// but the socket will be handled by the child.
|
||||
const server = net.createServer();
|
||||
server.on('connection', mustCall((socket) => {
|
||||
// TODO(@jasnell): Close does not seem to actually be called.
|
||||
// It is not clear if it is needed.
|
||||
socket.on('close', () => {
|
||||
debug('CLIENT: socket closed');
|
||||
});
|
||||
child.send({ what: 'socket' }, socket);
|
||||
}));
|
||||
server.on('close', mustCall(() => {
|
||||
debug('PARENT: server closed');
|
||||
}));
|
||||
|
||||
server.listen(0, mustCall(() => {
|
||||
debug('testSocket, listening');
|
||||
const connect = net.connect(server.address().port);
|
||||
let store = '';
|
||||
connect.on('data', mustCallAtLeast((chunk) => {
|
||||
store += chunk;
|
||||
debug('CLIENT: got data');
|
||||
}));
|
||||
connect.on('close', mustCall(() => {
|
||||
debug('CLIENT: closed');
|
||||
assert.strictEqual(store, 'echo');
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
const onReady = mustCall((msg) => {
|
||||
if (msg.what !== 'ready') return;
|
||||
child.removeListener('message', onReady);
|
||||
|
||||
testSocket();
|
||||
});
|
||||
|
||||
// Create socket and send it to child.
|
||||
child.on('message', onReady);
|
||||
}
|
||||
188
test/js/node/test/parallel/test-child-process-fork-net.js
Normal file
188
test/js/node/test/parallel/test-child-process-fork-net.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// This tests that a socket sent to the forked process works.
|
||||
// See https://github.com/nodejs/node/commit/dceebbfa
|
||||
|
||||
'use strict';
|
||||
const {
|
||||
mustCall,
|
||||
mustCallAtLeast,
|
||||
platformTimeout,
|
||||
} = require('../common');
|
||||
const assert = require('assert');
|
||||
const fork = require('child_process').fork;
|
||||
const net = require('net');
|
||||
const debug = require('util').debuglog('test');
|
||||
const count = 12;
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const needEnd = [];
|
||||
const id = process.argv[3];
|
||||
|
||||
process.on('message', mustCall((m, socket) => {
|
||||
if (!socket) return;
|
||||
|
||||
debug(`[${id}] got socket ${m}`);
|
||||
|
||||
// Will call .end('end') or .write('write');
|
||||
socket[m](m);
|
||||
|
||||
socket.resume();
|
||||
|
||||
socket.on('data', mustCallAtLeast(() => {
|
||||
debug(`[${id}] socket.data ${m}`);
|
||||
}));
|
||||
|
||||
socket.on('end', mustCall(() => {
|
||||
debug(`[${id}] socket.end ${m}`);
|
||||
}));
|
||||
|
||||
// Store the unfinished socket
|
||||
if (m === 'write') {
|
||||
needEnd.push(socket);
|
||||
}
|
||||
|
||||
socket.on('close', mustCall((had_error) => {
|
||||
debug(`[${id}] socket.close ${had_error} ${m}`);
|
||||
}));
|
||||
|
||||
socket.on('finish', mustCall(() => {
|
||||
debug(`[${id}] socket finished ${m}`);
|
||||
}));
|
||||
}, 4));
|
||||
|
||||
process.on('message', mustCall((m) => {
|
||||
if (m !== 'close') return;
|
||||
debug(`[${id}] got close message`);
|
||||
needEnd.forEach((endMe, i) => {
|
||||
debug(`[${id}] ending ${i}/${needEnd.length}`);
|
||||
endMe.end('end');
|
||||
});
|
||||
}, 4));
|
||||
|
||||
process.on('disconnect', mustCall(() => {
|
||||
debug(`[${id}] process disconnect, ending`);
|
||||
needEnd.forEach((endMe, i) => {
|
||||
debug(`[${id}] ending ${i}/${needEnd.length}`);
|
||||
endMe.end('end');
|
||||
});
|
||||
}));
|
||||
|
||||
} else {
|
||||
|
||||
const child1 = fork(process.argv[1], ['child', '1']);
|
||||
const child2 = fork(process.argv[1], ['child', '2']);
|
||||
const child3 = fork(process.argv[1], ['child', '3']);
|
||||
|
||||
const server = net.createServer();
|
||||
|
||||
let connected = 0;
|
||||
let closed = 0;
|
||||
server.on('connection', function(socket) {
|
||||
switch (connected % 6) {
|
||||
case 0:
|
||||
child1.send('end', socket); break;
|
||||
case 1:
|
||||
child1.send('write', socket); break;
|
||||
case 2:
|
||||
child2.send('end', socket); break;
|
||||
case 3:
|
||||
child2.send('write', socket); break;
|
||||
case 4:
|
||||
child3.send('end', socket); break;
|
||||
case 5:
|
||||
child3.send('write', socket); break;
|
||||
}
|
||||
connected += 1;
|
||||
|
||||
// TODO(@jasnell): This is not actually being called.
|
||||
// It is not clear if it is needed.
|
||||
socket.once('close', () => {
|
||||
debug(`[m] socket closed, total ${++closed}`);
|
||||
});
|
||||
|
||||
if (connected === count) {
|
||||
closeServer();
|
||||
}
|
||||
});
|
||||
|
||||
let disconnected = 0;
|
||||
server.on('listening', mustCall(() => {
|
||||
|
||||
let j = count;
|
||||
while (j--) {
|
||||
const client = net.connect(server.address().port, '127.0.0.1');
|
||||
client.on('error', () => {
|
||||
// This can happen if we kill the subprocess too early.
|
||||
// The client should still get a close event afterwards.
|
||||
// It likely won't so don't wrap in a mustCall.
|
||||
debug('[m] CLIENT: error event');
|
||||
});
|
||||
client.on('close', mustCall(() => {
|
||||
debug('[m] CLIENT: close event');
|
||||
disconnected += 1;
|
||||
}));
|
||||
client.resume();
|
||||
}
|
||||
}));
|
||||
|
||||
let closeEmitted = false;
|
||||
server.on('close', mustCall(function() {
|
||||
closeEmitted = true;
|
||||
|
||||
// Clean up child processes.
|
||||
try {
|
||||
child1.kill();
|
||||
} catch {
|
||||
debug('child process already terminated');
|
||||
}
|
||||
try {
|
||||
child2.kill();
|
||||
} catch {
|
||||
debug('child process already terminated');
|
||||
}
|
||||
try {
|
||||
child3.kill();
|
||||
} catch {
|
||||
debug('child process already terminated');
|
||||
}
|
||||
}));
|
||||
|
||||
server.listen(0, '127.0.0.1');
|
||||
|
||||
function closeServer() {
|
||||
server.close();
|
||||
|
||||
setTimeout(() => {
|
||||
assert(!closeEmitted);
|
||||
child1.send('close');
|
||||
child2.send('close');
|
||||
child3.disconnect();
|
||||
}, platformTimeout(200));
|
||||
}
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.strictEqual(server._workers.length, 0);
|
||||
assert.strictEqual(disconnected, count);
|
||||
assert.strictEqual(connected, count);
|
||||
});
|
||||
}
|
||||
54
test/js/node/test/parallel/test-child-process-fork-stdio.js
Normal file
54
test/js/node/test/parallel/test-child-process-fork-stdio.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
const net = require('net');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
process.stdout.write('this should be ignored');
|
||||
process.stderr.write('this should not be ignored');
|
||||
|
||||
const pipe = new net.Socket({ fd: 4 });
|
||||
|
||||
process.on('disconnect', () => {
|
||||
pipe.unref();
|
||||
});
|
||||
|
||||
pipe.setEncoding('utf8');
|
||||
pipe.on('data', (data) => {
|
||||
process.send(data);
|
||||
});
|
||||
} else {
|
||||
assert.throws(
|
||||
() => cp.fork(__filename, { stdio: ['pipe', 'pipe', 'pipe', 'pipe'] }),
|
||||
{ code: 'ERR_CHILD_PROCESS_IPC_REQUIRED', name: 'Error' });
|
||||
|
||||
let ipc = '';
|
||||
let stderr = '';
|
||||
const buf = Buffer.from('data to send via pipe');
|
||||
const child = cp.fork(__filename, ['child'], {
|
||||
stdio: [0, 'ignore', 'pipe', 'ipc', 'pipe']
|
||||
});
|
||||
|
||||
assert.strictEqual(child.stdout, null);
|
||||
|
||||
child.on('message', (msg) => {
|
||||
ipc += msg;
|
||||
|
||||
if (ipc === buf.toString()) {
|
||||
child.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
assert.strictEqual(stderr, 'this should not be ignored');
|
||||
}));
|
||||
|
||||
child.stdio[4].write(buf);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Flags: --expose-internals
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { fork } = require('child_process');
|
||||
const http = require('http');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
process.once('message', (req, socket) => {
|
||||
const res = new http.ServerResponse(req);
|
||||
res.assignSocket(socket);
|
||||
res.end();
|
||||
});
|
||||
|
||||
process.send('ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const { kTimeout } = require('internal/timers');
|
||||
|
||||
let child;
|
||||
let socket;
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
const r = {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
path: req.path,
|
||||
httpVersionMajor: req.httpVersionMajor,
|
||||
query: req.query,
|
||||
};
|
||||
|
||||
socket = res.socket;
|
||||
child.send(r, socket);
|
||||
server.close();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
child = fork(__filename, [ 'child' ]);
|
||||
child.once('message', (msg) => {
|
||||
assert.strictEqual(msg, 'ready');
|
||||
const req = http.request({
|
||||
port: server.address().port,
|
||||
}, common.mustCall((res) => {
|
||||
res.on('data', () => {});
|
||||
res.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(socket[kTimeout], null);
|
||||
assert.strictEqual(socket.parser, null);
|
||||
assert.strictEqual(socket._httpMessage, null);
|
||||
}));
|
||||
}));
|
||||
|
||||
req.end();
|
||||
});
|
||||
}));
|
||||
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
if (common.isWindows) {
|
||||
// https://github.com/nodejs/node/issues/48300
|
||||
common.skip('Does not work with cygwin quirks on Windows');
|
||||
}
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const spawn = require('child_process').spawn;
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
let cat, grep, wc;
|
||||
|
||||
const KB = 1024;
|
||||
const MB = KB * KB;
|
||||
|
||||
|
||||
// Make sure process chaining allows desired data flow:
|
||||
// check cat <file> | grep 'x' | wc -c === 1MB
|
||||
// This helps to make sure no data is lost between pipes.
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const file = tmpdir.resolve('data.txt');
|
||||
const buf = Buffer.alloc(MB).fill('x');
|
||||
|
||||
// Most OS commands that deal with data, attach special meanings to new line -
|
||||
// for example, line buffering. So cut the buffer into lines at some points,
|
||||
// forcing data flow to be split in the stream. Do not use os.EOL for \n as
|
||||
// that is 2 characters on Windows and is sometimes converted to 1 character
|
||||
// which causes the test to fail.
|
||||
for (let i = 1; i < KB; i++)
|
||||
buf.write('\n', i * KB);
|
||||
fs.writeFileSync(file, buf.toString());
|
||||
|
||||
cat = spawn('cat', [file]);
|
||||
grep = spawn('grep', ['x'], { stdio: [cat.stdout, 'pipe', 'pipe'] });
|
||||
wc = spawn('wc', ['-c'], { stdio: [grep.stdout, 'pipe', 'pipe'] });
|
||||
|
||||
// Extra checks: We never try to start reading data ourselves.
|
||||
cat.stdout._handle.readStart = common.mustNotCall();
|
||||
grep.stdout._handle.readStart = common.mustNotCall();
|
||||
|
||||
// Keep an array of error codes and assert on them during process exit. This
|
||||
// is because stdio can still be open when a child process exits, and we don't
|
||||
// want to lose information about what caused the error.
|
||||
const errors = [];
|
||||
process.on('exit', () => {
|
||||
assert.deepStrictEqual(errors, []);
|
||||
});
|
||||
|
||||
[cat, grep, wc].forEach((child, index) => {
|
||||
const errorHandler = (thing, type) => {
|
||||
// Don't want to assert here, as we might miss error code info.
|
||||
console.error(`unexpected ${type} from child #${index}:\n${thing}`);
|
||||
};
|
||||
|
||||
child.stderr.on('data', (d) => { errorHandler(d, 'data'); });
|
||||
child.on('error', (err) => { errorHandler(err, 'error'); });
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
if (code !== 0) {
|
||||
errors.push(`child ${index} exited with code ${code}`);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
let wcBuf = '';
|
||||
wc.stdout.on('data', common.mustCall((data) => {
|
||||
wcBuf += data;
|
||||
}));
|
||||
|
||||
process.on('exit', () => {
|
||||
// Grep always adds one extra byte at the end.
|
||||
assert.strictEqual(wcBuf.trim(), (MB + 1).toString());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as common from '../common/index.mjs';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import { EOL } from 'node:os';
|
||||
import { strictEqual, notStrictEqual, throws } from 'node:assert';
|
||||
import cp from 'node:child_process';
|
||||
|
||||
// TODO(LiviaMedeiros): test on different platforms
|
||||
if (!common.isLinux)
|
||||
common.skip();
|
||||
|
||||
const expectedCWD = process.cwd();
|
||||
const expectedUID = process.getuid();
|
||||
|
||||
for (const tamperedCwd of ['', '/tmp', '/not/existing/malicious/path', 42n]) {
|
||||
Object.prototype.cwd = tamperedCwd;
|
||||
|
||||
cp.exec('pwd', common.mustSucceed((out) => {
|
||||
strictEqual(`${out}`, `${expectedCWD}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.execSync('pwd')}`, `${expectedCWD}${EOL}`);
|
||||
cp.execFile('pwd', common.mustSucceed((out) => {
|
||||
strictEqual(`${out}`, `${expectedCWD}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.execFileSync('pwd')}`, `${expectedCWD}${EOL}`);
|
||||
cp.spawn('pwd').stdout.on('data', common.mustCall((out) => {
|
||||
strictEqual(`${out}`, `${expectedCWD}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.spawnSync('pwd').stdout}`, `${expectedCWD}${EOL}`);
|
||||
|
||||
delete Object.prototype.cwd;
|
||||
}
|
||||
|
||||
for (const tamperedUID of [0, 1, 999, 1000, 0n, 'gwak']) {
|
||||
Object.prototype.uid = tamperedUID;
|
||||
|
||||
cp.exec('id -u', common.mustSucceed((out) => {
|
||||
strictEqual(`${out}`, `${expectedUID}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.execSync('id -u')}`, `${expectedUID}${EOL}`);
|
||||
cp.execFile('id', ['-u'], common.mustSucceed((out) => {
|
||||
strictEqual(`${out}`, `${expectedUID}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.execFileSync('id', ['-u'])}`, `${expectedUID}${EOL}`);
|
||||
cp.spawn('id', ['-u']).stdout.on('data', common.mustCall((out) => {
|
||||
strictEqual(`${out}`, `${expectedUID}${EOL}`);
|
||||
}));
|
||||
strictEqual(`${cp.spawnSync('id', ['-u']).stdout}`, `${expectedUID}${EOL}`);
|
||||
|
||||
delete Object.prototype.uid;
|
||||
}
|
||||
|
||||
{
|
||||
Object.prototype.execPath = '/not/existing/malicious/path';
|
||||
|
||||
// Does not throw ENOENT
|
||||
cp.fork(fixtures.path('empty.js'));
|
||||
|
||||
delete Object.prototype.execPath;
|
||||
}
|
||||
|
||||
for (const shellCommandArgument of ['-L && echo "tampered"']) {
|
||||
Object.prototype.shell = true;
|
||||
const cmd = 'pwd';
|
||||
let cmdExitCode = '';
|
||||
|
||||
const program = cp.spawn(cmd, [shellCommandArgument], { cwd: expectedCWD });
|
||||
program.stderr.on('data', common.mustCall());
|
||||
program.stdout.on('data', common.mustNotCall());
|
||||
|
||||
program.on('exit', common.mustCall((code) => {
|
||||
notStrictEqual(code, 0);
|
||||
}));
|
||||
|
||||
cp.execFile(cmd, [shellCommandArgument], { cwd: expectedCWD },
|
||||
common.mustCall((err) => {
|
||||
notStrictEqual(err.code, 0);
|
||||
})
|
||||
);
|
||||
|
||||
throws(() => {
|
||||
cp.execFileSync(cmd, [shellCommandArgument], { cwd: expectedCWD });
|
||||
}, (e) => {
|
||||
notStrictEqual(e.status, 0);
|
||||
return true;
|
||||
});
|
||||
|
||||
cmdExitCode = cp.spawnSync(cmd, [shellCommandArgument], { cwd: expectedCWD }).status;
|
||||
notStrictEqual(cmdExitCode, 0);
|
||||
|
||||
delete Object.prototype.shell;
|
||||
}
|
||||
86
test/js/node/test/parallel/test-child-process-recv-handle.js
Normal file
86
test/js/node/test/parallel/test-child-process-recv-handle.js
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
// Test that a Linux specific quirk in the handle passing protocol is handled
|
||||
// correctly. See https://github.com/joyent/node/issues/5330 for details.
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
if (process.argv[2] === 'worker')
|
||||
worker();
|
||||
else
|
||||
primary();
|
||||
|
||||
function primary() {
|
||||
// spawn() can only create one IPC channel so we use stdin/stdout as an
|
||||
// ad-hoc command channel.
|
||||
const proc = spawn(process.execPath, [
|
||||
'--expose-internals', __filename, 'worker',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
|
||||
});
|
||||
let handle = null;
|
||||
proc.on('exit', () => {
|
||||
handle.close();
|
||||
});
|
||||
proc.stdout.on('data', common.mustCall((data) => {
|
||||
assert.strictEqual(data.toString(), 'ok\r\n');
|
||||
net.createServer(common.mustNotCall()).listen(0, function() {
|
||||
handle = this._handle;
|
||||
proc.send('one');
|
||||
proc.send('two', handle);
|
||||
proc.send('three');
|
||||
proc.stdin.write('ok\r\n');
|
||||
});
|
||||
}));
|
||||
proc.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
function worker() {
|
||||
const { kChannelHandle } = require('internal/child_process');
|
||||
process[kChannelHandle].readStop(); // Make messages batch up.
|
||||
process.stdout.ref();
|
||||
process.stdout.write('ok\r\n');
|
||||
process.stdin.once('data', common.mustCall((data) => {
|
||||
assert.strictEqual(data.toString(), 'ok\r\n');
|
||||
process[kChannelHandle].readStart();
|
||||
}));
|
||||
let n = 0;
|
||||
process.on('message', common.mustCall((msg, handle) => {
|
||||
n += 1;
|
||||
if (n === 1) {
|
||||
assert.strictEqual(msg, 'one');
|
||||
assert.strictEqual(handle, undefined);
|
||||
} else if (n === 2) {
|
||||
assert.strictEqual(msg, 'two');
|
||||
assert.ok(handle !== null && typeof handle === 'object');
|
||||
handle.close();
|
||||
} else if (n === 3) {
|
||||
assert.strictEqual(msg, 'three');
|
||||
assert.strictEqual(handle, undefined);
|
||||
process.exit();
|
||||
}
|
||||
}, 3));
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
'use strict';
|
||||
const { mustNotCall } = require('../common');
|
||||
|
||||
// Regression test for https://github.com/nodejs/node/issues/44768
|
||||
|
||||
const { throws } = require('assert');
|
||||
const {
|
||||
exec,
|
||||
execFile,
|
||||
execFileSync,
|
||||
execSync,
|
||||
fork,
|
||||
spawn,
|
||||
spawnSync,
|
||||
} = require('child_process');
|
||||
|
||||
// Tests for the 'command' argument
|
||||
|
||||
throws(() => exec(`${process.execPath} ${__filename} AAA BBB\0XXX CCC`, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => exec('BBB\0XXX AAA CCC', mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(`${process.execPath} ${__filename} AAA BBB\0XXX CCC`), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync('BBB\0XXX AAA CCC'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'file' argument
|
||||
|
||||
throws(() => spawn('BBB\0XXX'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile('BBB\0XXX', mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync('BBB\0XXX'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn('BBB\0XXX'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync('BBB\0XXX'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'modulePath' argument
|
||||
|
||||
throws(() => fork('BBB\0XXX'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'args' argument
|
||||
|
||||
// Not testing exec() and execSync() because these accept 'args' as a part of
|
||||
// 'command' as space-separated arguments.
|
||||
|
||||
throws(() => execFile(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC'], mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => fork(__filename, ['AAA', 'BBB\0XXX', 'CCC']), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, [__filename, 'AAA', 'BBB\0XXX', 'CCC']), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.cwd' argument
|
||||
|
||||
throws(() => exec(process.execPath, { cwd: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile(process.execPath, { cwd: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, { cwd: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(process.execPath, { cwd: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => fork(__filename, { cwd: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn(process.execPath, { cwd: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, { cwd: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.argv0' argument
|
||||
|
||||
throws(() => exec(process.execPath, { argv0: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile(process.execPath, { argv0: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, { argv0: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(process.execPath, { argv0: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => fork(__filename, { argv0: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn(process.execPath, { argv0: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, { argv0: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.shell' argument
|
||||
|
||||
throws(() => exec(process.execPath, { shell: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile(process.execPath, { shell: 'BBB\0XXX' }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, { shell: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(process.execPath, { shell: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Not testing fork() because it doesn't accept the shell option (internally it
|
||||
// explicitly sets shell to false).
|
||||
|
||||
throws(() => spawn(process.execPath, { shell: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, { shell: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.env' argument
|
||||
|
||||
throws(() => exec(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => exec(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFile(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }, mustNotCall()), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execFileSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => execSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => fork(__filename, { env: { 'AAA': 'BBB\0XXX' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => fork(__filename, { env: { 'BBB\0XXX': 'AAA' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawn(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, { env: { 'AAA': 'BBB\0XXX' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
throws(() => spawnSync(process.execPath, { env: { 'BBB\0XXX': 'AAA' } }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.execPath' argument
|
||||
throws(() => fork(__filename, { execPath: 'BBB\0XXX' }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
|
||||
// Tests for the 'options.execArgv' argument
|
||||
if(typeof Bun === 'undefined') { // This test is disabled in bun because bun does not support execArgv.
|
||||
throws(() => fork(__filename, { execArgv: ['AAA', 'BBB\0XXX', 'CCC'] }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
name: 'TypeError',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
const net = require('net');
|
||||
|
||||
if (process.argv[2] !== 'child') {
|
||||
// The parent process forks a child process, starts a TCP server, and connects
|
||||
// to the server. The accepted connection is passed to the child process,
|
||||
// where the socket is written. Then, the child signals the parent process to
|
||||
// write to the same socket.
|
||||
let result = '';
|
||||
|
||||
process.on('exit', () => {
|
||||
assert.strictEqual(result, 'childparent');
|
||||
});
|
||||
|
||||
const child = cp.fork(__filename, ['child']);
|
||||
|
||||
// Verify that the child exits successfully
|
||||
child.on('exit', common.mustCall((exitCode, signalCode) => {
|
||||
assert.strictEqual(exitCode, 0);
|
||||
assert.strictEqual(signalCode, null);
|
||||
}));
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
child.on('message', common.mustCall((msg) => {
|
||||
assert.strictEqual(msg, 'child_done');
|
||||
socket.end('parent', () => {
|
||||
server.close();
|
||||
child.disconnect();
|
||||
});
|
||||
}));
|
||||
|
||||
child.send('socket', socket, { keepOpen: true }, common.mustSucceed());
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
const socket = net.connect(server.address().port, common.localhostIPv4);
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', (data) => result += data);
|
||||
});
|
||||
} else {
|
||||
// The child process receives the socket from the parent, writes data to
|
||||
// the socket, then signals the parent process to write
|
||||
process.on('message', common.mustCall((msg, socket) => {
|
||||
assert.strictEqual(msg, 'socket');
|
||||
socket.write('child', () => process.send('child_done'));
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// subprocess.send() will return false if the channel has closed or when the
|
||||
// backlog of unsent messages exceeds a threshold that makes it unwise to send
|
||||
// more. Otherwise, the method returns true.
|
||||
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const { fork, spawn } = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
// Just a script that stays alive (does not listen to `process.on('message')`).
|
||||
const subScript = fixtures.path('child-process-persistent.js');
|
||||
|
||||
{
|
||||
// Test `send` return value on `fork` that opens and IPC by default.
|
||||
const n = fork(subScript);
|
||||
// `subprocess.send` should always return `true` for the first send.
|
||||
const rv = n.send({ h: 'w' }, assert.ifError);
|
||||
assert.strictEqual(rv, true);
|
||||
n.kill('SIGKILL');
|
||||
}
|
||||
|
||||
{
|
||||
// Test `send` return value on `spawn` and saturate backlog with handles.
|
||||
// Call `spawn` with options that open an IPC channel.
|
||||
const spawnOptions = { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] };
|
||||
const s = spawn(process.execPath, [subScript], spawnOptions);
|
||||
|
||||
const server = net.createServer(common.mustNotCall()).listen(0, () => {
|
||||
const handle = server._handle;
|
||||
|
||||
// Sending a handle and not giving the tickQueue time to acknowledge should
|
||||
// create the internal backlog, but leave it empty.
|
||||
const rv1 = s.send('one', handle, (err) => { if (err) assert.fail(err); });
|
||||
assert.strictEqual(rv1, true);
|
||||
// Since the first `send` included a handle (should be unacknowledged),
|
||||
// we can safely queue up only one more message.
|
||||
const rv2 = s.send('two', (err) => { if (err) assert.fail(err); });
|
||||
assert.strictEqual(rv2, true);
|
||||
// The backlog should now be indicate to backoff.
|
||||
const rv3 = s.send('three', (err) => { if (err) assert.fail(err); });
|
||||
assert.strictEqual(rv3, false);
|
||||
const rv4 = s.send('four', (err) => {
|
||||
if (err) assert.fail(err);
|
||||
// `send` queue should have been drained.
|
||||
const rv5 = s.send('5', handle, (err) => { if (err) assert.fail(err); });
|
||||
assert.strictEqual(rv5, true);
|
||||
|
||||
// End test and cleanup.
|
||||
s.kill();
|
||||
handle.close();
|
||||
server.close();
|
||||
});
|
||||
assert.strictEqual(rv4, false);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { fork, spawn } = require('child_process');
|
||||
const net = require('net');
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
// Run in a child process because the PIPE file descriptor stays open until
|
||||
// Node.js completes, blocking the tmpdir and preventing cleanup.
|
||||
|
||||
if (process.argv[2] !== 'child') {
|
||||
// Parent
|
||||
tmpdir.refresh();
|
||||
|
||||
// Run test
|
||||
const child = fork(__filename, ['child'], { stdio: 'inherit' });
|
||||
child.on('exit', common.mustCall(function(code) {
|
||||
assert.strictEqual(code, 0);
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Child
|
||||
const server = net.createServer((conn) => {
|
||||
spawn(process.execPath, ['-v'], {
|
||||
stdio: ['ignore', conn, 'ignore']
|
||||
}).on('close', common.mustCall(() => {
|
||||
conn.end();
|
||||
}));
|
||||
}).listen(common.PIPE, () => {
|
||||
const client = net.connect(common.PIPE, common.mustCall());
|
||||
client.once('data', () => {
|
||||
client.end(() => {
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
'use strict';
|
||||
|
||||
// Node.js on Windows should not be able to spawn batch files directly,
|
||||
// only when the 'shell' option is set. An undocumented feature of the
|
||||
// Win32 CreateProcess API allows spawning .bat and .cmd files directly
|
||||
// but it does not sanitize arguments. We cannot do that automatically
|
||||
// because it's sometimes impossible to escape arguments unambiguously.
|
||||
//
|
||||
// Expectation: spawn() and spawnSync() raise EINVAL if and only if:
|
||||
//
|
||||
// 1. 'shell' option is unset
|
||||
// 2. Platform is Windows
|
||||
// 3. Filename ends in .bat or .cmd, case-insensitive
|
||||
//
|
||||
// exec() and execSync() are unchanged.
|
||||
|
||||
const common = require('../common');
|
||||
const cp = require('child_process');
|
||||
const assert = require('assert');
|
||||
const { isWindows } = common;
|
||||
|
||||
const expectedCode = isWindows ? 'EINVAL' : 'ENOENT';
|
||||
const expectedStatus = isWindows ? 1 : 127;
|
||||
|
||||
const suffixes =
|
||||
'BAT|bAT|BaT|baT|BAt|bAt|Bat|bat|CMD|cMD|CmD|cmD|CMd|cMd|Cmd|cmd|cmd |cmd .|cmd ....'
|
||||
.split('|');
|
||||
|
||||
function testExec(filename) {
|
||||
return new Promise((resolve) => {
|
||||
cp.exec(filename).once('exit', common.mustCall(function(status) {
|
||||
assert.strictEqual(status, expectedStatus);
|
||||
resolve();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function testExecSync(filename) {
|
||||
let e;
|
||||
try {
|
||||
cp.execSync(filename);
|
||||
} catch (_e) {
|
||||
e = _e;
|
||||
}
|
||||
if (!e) throw new Error(`Exception expected for ${filename}`);
|
||||
assert.strictEqual(e.status, expectedStatus);
|
||||
}
|
||||
|
||||
function testSpawn(filename, code) {
|
||||
// Batch file case is a synchronous error, file-not-found is asynchronous.
|
||||
if (code === 'EINVAL') {
|
||||
let e;
|
||||
try {
|
||||
cp.spawn(filename);
|
||||
} catch (_e) {
|
||||
e = _e;
|
||||
}
|
||||
if (!e) throw new Error(`Exception expected for ${filename}`);
|
||||
assert.strictEqual(e.code, code);
|
||||
} else {
|
||||
return new Promise((resolve) => {
|
||||
cp.spawn(filename).once('error', common.mustCall(function(e) {
|
||||
assert.strictEqual(e.code, code);
|
||||
resolve();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testSpawnSync(filename, code) {
|
||||
{
|
||||
const r = cp.spawnSync(filename);
|
||||
assert.strictEqual(r.error.code, code);
|
||||
}
|
||||
{
|
||||
const r = cp.spawnSync(filename, { shell: true });
|
||||
assert.strictEqual(r.status, expectedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
testExecSync('./nosuchdir/nosuchfile');
|
||||
testSpawnSync('./nosuchdir/nosuchfile', 'ENOENT');
|
||||
for (const suffix of suffixes) {
|
||||
testExecSync(`./nosuchdir/nosuchfile.${suffix}`);
|
||||
testSpawnSync(`./nosuchdir/nosuchfile.${suffix}`, expectedCode);
|
||||
}
|
||||
|
||||
go().catch((ex) => { throw ex; });
|
||||
|
||||
async function go() {
|
||||
await testExec('./nosuchdir/nosuchfile');
|
||||
await testSpawn('./nosuchdir/nosuchfile', 'ENOENT');
|
||||
for (const suffix of suffixes) {
|
||||
await testExec(`./nosuchdir/nosuchfile.${suffix}`);
|
||||
await testSpawn(`./nosuchdir/nosuchfile.${suffix}`, expectedCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// This test is modified to not test node internals, only public APIs.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
setInterval(() => {}, 1000);
|
||||
} else {
|
||||
const { SIGKILL } = require('os').constants.signals;
|
||||
|
||||
function spawn(killSignal) {
|
||||
const child = cp.spawnSync(process.execPath,
|
||||
[__filename, 'child'],
|
||||
{ killSignal, timeout: 100 });
|
||||
assert.strictEqual(child.status, null);
|
||||
assert.strictEqual(child.error.code, 'ETIMEDOUT');
|
||||
return child;
|
||||
}
|
||||
|
||||
// Verify that an error is thrown for unknown signals.
|
||||
assert.throws(() => {
|
||||
spawn('SIG_NOT_A_REAL_SIGNAL');
|
||||
}, { code: 'ERR_UNKNOWN_SIGNAL', name: 'TypeError' });
|
||||
|
||||
// Verify that the default kill signal is SIGTERM.
|
||||
{
|
||||
const child = spawn(undefined);
|
||||
|
||||
assert.strictEqual(child.signal, 'SIGTERM');
|
||||
}
|
||||
|
||||
// Verify that a string signal name is handled properly.
|
||||
{
|
||||
const child = spawn('SIGKILL');
|
||||
|
||||
assert.strictEqual(child.signal, 'SIGKILL');
|
||||
}
|
||||
|
||||
// Verify that a numeric signal is handled properly.
|
||||
{
|
||||
assert.strictEqual(typeof SIGKILL, 'number');
|
||||
|
||||
const child = spawn(SIGKILL);
|
||||
|
||||
assert.strictEqual(child.signal, 'SIGKILL');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// This test checks that the maxBuffer option for child_process.spawnSync()
|
||||
// works as expected.
|
||||
|
||||
const assert = require('assert');
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
const { getSystemErrorName } = require('util');
|
||||
const msgOut = 'this is stdout';
|
||||
const msgOutBuf = Buffer.from(`${msgOut}\n`);
|
||||
|
||||
const args = [
|
||||
'-e',
|
||||
`console.log("${msgOut}");`,
|
||||
];
|
||||
|
||||
// Verify that an error is returned if maxBuffer is surpassed.
|
||||
{
|
||||
const ret = spawnSync(process.execPath, args, { maxBuffer: 1 });
|
||||
|
||||
assert.ok(ret.error, 'maxBuffer should error');
|
||||
assert.strictEqual(ret.error.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(ret.error.errno), 'ENOBUFS');
|
||||
// We can have buffers larger than maxBuffer because underneath we alloc 64k
|
||||
// that matches our read sizes.
|
||||
assert.deepStrictEqual(ret.stdout, msgOutBuf);
|
||||
}
|
||||
|
||||
// Verify that a maxBuffer size of Infinity works.
|
||||
{
|
||||
const ret = spawnSync(process.execPath, args, { maxBuffer: Infinity });
|
||||
|
||||
assert.ifError(ret.error);
|
||||
assert.deepStrictEqual(ret.stdout, msgOutBuf);
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 1024 * 1024.
|
||||
{
|
||||
const args = ['-e', "console.log('a'.repeat(1024 * 1024))"];
|
||||
const ret = spawnSync(process.execPath, args);
|
||||
|
||||
assert.ok(ret.error, 'maxBuffer should error');
|
||||
assert.strictEqual(ret.error.code, 'ENOBUFS');
|
||||
assert.strictEqual(getSystemErrorName(ret.error.errno), 'ENOBUFS');
|
||||
}
|
||||
|
||||
// Default maxBuffer size is 1024 * 1024.
|
||||
{
|
||||
const args = ['-e', "console.log('a'.repeat(1024 * 1024 - 1))"];
|
||||
const ret = spawnSync(process.execPath, args);
|
||||
|
||||
assert.ifError(ret.error);
|
||||
assert.deepStrictEqual(
|
||||
ret.stdout.toString().trim(),
|
||||
'a'.repeat(1024 * 1024 - 1)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// This test is modified to not test node internals, only public APIs. It is also modified to use `-p` rather than `-pe` because Bun does not support `-pe`.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
|
||||
// Verify that a shell is, in fact, executed
|
||||
const doesNotExist = cp.spawnSync('does-not-exist', { shell: true });
|
||||
|
||||
assert.notStrictEqual(doesNotExist.file, 'does-not-exist');
|
||||
assert.strictEqual(doesNotExist.error, undefined);
|
||||
assert.strictEqual(doesNotExist.signal, null);
|
||||
|
||||
if (common.isWindows)
|
||||
assert.strictEqual(doesNotExist.status, 1); // Exit code of cmd.exe
|
||||
else
|
||||
assert.strictEqual(doesNotExist.status, 127); // Exit code of /bin/sh
|
||||
|
||||
// Verify that passing arguments works
|
||||
const echo = cp.spawnSync('echo', ['foo'], { shell: true });
|
||||
|
||||
assert.strictEqual(echo.stdout.toString().trim(), 'foo');
|
||||
|
||||
// Verify that shell features can be used
|
||||
const cmd = 'echo bar | cat';
|
||||
const command = cp.spawnSync(cmd, { shell: true });
|
||||
|
||||
assert.strictEqual(command.stdout.toString().trim(), 'bar');
|
||||
|
||||
// Verify that the environment is properly inherited
|
||||
const env = cp.spawnSync(`"${common.isWindows ? process.execPath : '$NODE'}" -p process.env.BAZ`, {
|
||||
env: { ...process.env, BAZ: 'buzz', NODE: process.execPath },
|
||||
shell: true
|
||||
});
|
||||
|
||||
assert.strictEqual(env.stdout.toString().trim(), 'buzz');
|
||||
|
||||
// Verify that the shell internals work properly across platforms.
|
||||
{
|
||||
const originalComspec = process.env.comspec;
|
||||
|
||||
// Enable monkey patching process.platform.
|
||||
const originalPlatform = process.platform;
|
||||
let platform = null;
|
||||
Object.defineProperty(process, 'platform', { get: () => platform });
|
||||
|
||||
function test(testPlatform, shell, shellOutput) {
|
||||
platform = testPlatform;
|
||||
const cmd = 'not_a_real_command';
|
||||
|
||||
cp.spawnSync(cmd, { shell });
|
||||
}
|
||||
|
||||
// Test Unix platforms with the default shell.
|
||||
test('darwin', true, '/bin/sh');
|
||||
|
||||
// Test Unix platforms with a user specified shell.
|
||||
test('darwin', '/bin/csh', '/bin/csh');
|
||||
|
||||
// Test Android platforms.
|
||||
test('android', true, '/system/bin/sh');
|
||||
|
||||
// Test Windows platforms with a user specified shell.
|
||||
test('win32', 'powershell.exe', 'powershell.exe');
|
||||
|
||||
// Test Windows platforms with the default shell and no comspec.
|
||||
delete process.env.comspec;
|
||||
test('win32', true, 'cmd.exe');
|
||||
|
||||
// Test Windows platforms with the default shell and a comspec value.
|
||||
process.env.comspec = 'powershell.exe';
|
||||
test('win32', true, process.env.comspec);
|
||||
|
||||
// Restore the original value of process.platform.
|
||||
platform = originalPlatform;
|
||||
|
||||
// Restore the original comspec environment variable if necessary.
|
||||
if (originalComspec)
|
||||
process.env.comspec = originalComspec;
|
||||
}
|
||||
62
test/js/node/test/parallel/test-child-process-stdin.js
Normal file
62
test/js/node/test/parallel/test-child-process-stdin.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const {
|
||||
mustCall,
|
||||
mustCallAtLeast,
|
||||
mustNotCall,
|
||||
} = require('../common');
|
||||
const assert = require('assert');
|
||||
const debug = require('util').debuglog('test');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
const cat = spawn('cat');
|
||||
cat.stdin.write('hello');
|
||||
cat.stdin.write(' ');
|
||||
cat.stdin.write('world');
|
||||
|
||||
assert.strictEqual(cat.stdin.writable, true);
|
||||
assert.strictEqual(cat.stdin.readable, false);
|
||||
|
||||
cat.stdin.end();
|
||||
|
||||
let response = '';
|
||||
|
||||
cat.stdout.setEncoding('utf8');
|
||||
cat.stdout.on('data', mustCallAtLeast((chunk) => {
|
||||
debug(`stdout: ${chunk}`);
|
||||
response += chunk;
|
||||
}));
|
||||
|
||||
cat.stdout.on('end', mustCall());
|
||||
|
||||
cat.stderr.on('data', mustNotCall());
|
||||
|
||||
cat.stderr.on('end', mustCall());
|
||||
|
||||
cat.on('exit', mustCall((status) => {
|
||||
assert.strictEqual(status, 0);
|
||||
}));
|
||||
|
||||
cat.on('close', mustCall(() => {
|
||||
assert.strictEqual(response, 'hello world');
|
||||
}));
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
// Regression test for https://github.com/nodejs/node/issues/27097.
|
||||
// Check that (cat [p1] ; cat [p2]) | cat [p3] works.
|
||||
|
||||
const p3 = spawn('cat', { stdio: ['pipe', 'pipe', 'inherit'] });
|
||||
const p1 = spawn('cat', { stdio: ['pipe', p3.stdin, 'inherit'] });
|
||||
const p2 = spawn('cat', { stdio: ['pipe', p3.stdin, 'inherit'] });
|
||||
p3.stdout.setEncoding('utf8');
|
||||
|
||||
// Write three different chunks:
|
||||
// - 'hello' from this process to p1 to p3 back to us
|
||||
// - 'world' from this process to p2 to p3 back to us
|
||||
// - 'foobar' from this process to p3 back to us
|
||||
// Do so sequentially in order to avoid race conditions.
|
||||
p1.stdin.end('hello\n');
|
||||
p3.stdout.once('data', common.mustCall((chunk) => {
|
||||
assert.strictEqual(chunk, 'hello\n');
|
||||
p2.stdin.end('world\n');
|
||||
p3.stdout.once('data', common.mustCall((chunk) => {
|
||||
assert.strictEqual(chunk, 'world\n');
|
||||
p3.stdin.end('foobar\n');
|
||||
p3.stdout.once('data', common.mustCall((chunk) => {
|
||||
assert.strictEqual(chunk, 'foobar\n');
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
if (common.isWindows) {
|
||||
// https://github.com/nodejs/node/issues/48300
|
||||
common.skip('Does not work with cygwin quirks on Windows');
|
||||
}
|
||||
|
||||
const assert = require('assert');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
// Check that, once a child process has ended, it’s safe to read from a pipe
|
||||
// that the child had used as input.
|
||||
// We simulate that using cat | (head -n1; ...)
|
||||
|
||||
const p1 = spawn('cat', { stdio: ['pipe', 'pipe', 'inherit'] });
|
||||
const p2 = spawn('head', ['-n1'], { stdio: [p1.stdout, 'pipe', 'inherit'] });
|
||||
|
||||
// First, write the line that gets passed through p2, making 'head' exit.
|
||||
p1.stdin.write('hello\n');
|
||||
p2.stdout.setEncoding('utf8');
|
||||
p2.stdout.on('data', common.mustCall((chunk) => {
|
||||
assert.strictEqual(chunk, 'hello\n');
|
||||
}));
|
||||
p2.on('exit', common.mustCall(() => {
|
||||
// We can now use cat’s output, because 'head' is no longer reading from it.
|
||||
p1.stdin.end('world\n');
|
||||
p1.stdout.setEncoding('utf8');
|
||||
p1.stdout.on('data', common.mustCall((chunk) => {
|
||||
assert.strictEqual(chunk, 'world\n');
|
||||
}));
|
||||
p1.stdout.resume();
|
||||
}));
|
||||
20
test/js/node/test/parallel/test-child-process-uid-gid.js
Normal file
20
test/js/node/test/parallel/test-child-process-uid-gid.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const spawn = require('child_process').spawn;
|
||||
const expectedError = common.isWindows ? /\bENOTSUP\b/ : /\bEPERM\b/;
|
||||
|
||||
if (common.isIBMi)
|
||||
common.skip('IBMi has a different behavior');
|
||||
|
||||
if (common.isWindows || process.getuid() !== 0) {
|
||||
assert.throws(() => {
|
||||
spawn('echo', ['fhqwhgads'], { uid: 0 });
|
||||
}, expectedError);
|
||||
}
|
||||
|
||||
if (common.isWindows || !process.getgroups().some((gid) => gid === 0)) {
|
||||
assert.throws(() => {
|
||||
spawn('echo', ['fhqwhgads'], { gid: 0 });
|
||||
}, expectedError);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// This test is modified to not test node internals, only public APIs. windowsHide is not observable,
|
||||
// so this only tests that the flag does not cause an error.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const cp = require('child_process');
|
||||
const { test } = require('node:test');
|
||||
const cmd = process.execPath;
|
||||
const args = ['-p', '42'];
|
||||
const options = { windowsHide: true };
|
||||
|
||||
test('spawnSync() passes windowsHide correctly', (t) => {
|
||||
const child = cp.spawnSync(cmd, args, options);
|
||||
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert.strictEqual(child.signal, null);
|
||||
assert.strictEqual(child.stdout.toString().trim(), '42');
|
||||
assert.strictEqual(child.stderr.toString().trim(), '');
|
||||
});
|
||||
|
||||
test('spawn() passes windowsHide correctly', (t, done) => {
|
||||
const child = cp.spawn(cmd, args, options);
|
||||
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(signal, null);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
test('execFile() passes windowsHide correctly', (t, done) => {
|
||||
cp.execFile(cmd, args, options, common.mustSucceed((stdout, stderr) => {
|
||||
assert.strictEqual(stdout.trim(), '42');
|
||||
assert.strictEqual(stderr.trim(), '');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
29
test/js/node/test/parallel/test-pipe-unref.js
Normal file
29
test/js/node/test/parallel/test-pipe-unref.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
const { fork } = require('child_process');
|
||||
|
||||
// This test should end immediately after `unref` is called
|
||||
// The pipe will stay open as Node.js completes, thus run in a child process
|
||||
// so that tmpdir can be cleaned up.
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
if (process.argv[2] !== 'child') {
|
||||
// Parent
|
||||
tmpdir.refresh();
|
||||
|
||||
// Run test
|
||||
const child = fork(__filename, ['child'], { stdio: 'inherit' });
|
||||
child.on('exit', common.mustCall(function(code) {
|
||||
assert.strictEqual(code, 0);
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Child
|
||||
const s = net.Server();
|
||||
s.listen(common.PIPE);
|
||||
s.unref();
|
||||
42
test/js/node/test/parallel/test-stdio-pipe-redirect.js
Normal file
42
test/js/node/test/parallel/test-stdio-pipe-redirect.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (!common.isMainThread)
|
||||
common.skip("Workers don't have process-like stdio");
|
||||
|
||||
// Test if Node handles redirecting one child process stdout to another
|
||||
// process stdin without crashing.
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
const writeSize = 100;
|
||||
const totalDots = 10000;
|
||||
|
||||
const who = process.argv.length <= 2 ? 'parent' : process.argv[2];
|
||||
|
||||
switch (who) {
|
||||
case 'parent': {
|
||||
const consumer = spawn(process.argv0, [process.argv[1], 'consumer'], {
|
||||
stdio: ['pipe', 'ignore', 'inherit'],
|
||||
});
|
||||
const producer = spawn(process.argv0, [process.argv[1], 'producer'], {
|
||||
stdio: ['pipe', consumer.stdin, 'inherit'],
|
||||
});
|
||||
process.stdin.on('data', () => {});
|
||||
producer.on('exit', process.exit);
|
||||
break;
|
||||
}
|
||||
case 'producer': {
|
||||
const buffer = Buffer.alloc(writeSize, '.');
|
||||
let written = 0;
|
||||
const write = () => {
|
||||
if (written < totalDots) {
|
||||
written += writeSize;
|
||||
process.stdout.write(buffer, write);
|
||||
}
|
||||
};
|
||||
write();
|
||||
break;
|
||||
}
|
||||
case 'consumer':
|
||||
process.stdin.on('data', () => {});
|
||||
break;
|
||||
}
|
||||
27
test/js/node/test/parallel/test-stdout-close-catch.js
Normal file
27
test/js/node/test/parallel/test-stdout-close-catch.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { getSystemErrorName } = require('util');
|
||||
|
||||
const testScript = fixtures.path('catch-stdout-error.js');
|
||||
|
||||
const child = child_process.exec(
|
||||
...common.escapePOSIXShell`"${process.execPath}" "${testScript}" | "${process.execPath}" -pe "process.stdin.on('data' , () => process.exit(1))"`
|
||||
);
|
||||
let output = '';
|
||||
|
||||
child.stderr.on('data', function(c) {
|
||||
output += c;
|
||||
});
|
||||
|
||||
|
||||
child.on('close', common.mustCall(function(code) {
|
||||
output = JSON.parse(output);
|
||||
|
||||
assert.strictEqual(output.code, 'EPIPE');
|
||||
assert.strictEqual(getSystemErrorName(output.errno), 'EPIPE');
|
||||
assert.strictEqual(output.syscall, 'write');
|
||||
console.log('ok');
|
||||
}));
|
||||
78
test/js/node/test/sequential/test-child-process-emfile.js
Normal file
78
test/js/node/test/sequential/test-child-process-emfile.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows)
|
||||
common.skip('no RLIMIT_NOFILE on Windows');
|
||||
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
const ulimit = Number(child_process.execSync('ulimit -Hn'));
|
||||
if (ulimit > 64 || Number.isNaN(ulimit)) {
|
||||
const [cmd, opts] = common.escapePOSIXShell`ulimit -n 64 && "${process.execPath}" "${__filename}"`;
|
||||
// Sorry about this nonsense. It can be replaced if
|
||||
// https://github.com/nodejs/node-v0.x-archive/pull/2143#issuecomment-2847886
|
||||
// ever happens.
|
||||
const result = child_process.spawnSync(
|
||||
'/bin/sh',
|
||||
['-c', cmd],
|
||||
opts,
|
||||
);
|
||||
assert.strictEqual(result.stdout.toString(), '');
|
||||
assert.strictEqual(result.stderr.toString(), '');
|
||||
assert.strictEqual(result.status, 0);
|
||||
assert.strictEqual(result.error, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const openFds = [];
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
openFds.push(fs.openSync(__filename, 'r'));
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.code, 'EMFILE');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should emit an error, not throw.
|
||||
const proc = child_process.spawn(process.execPath, ['-e', '0']);
|
||||
|
||||
// Verify that stdio is not setup on EMFILE or ENFILE.
|
||||
assert.strictEqual(proc.stdin, undefined);
|
||||
assert.strictEqual(proc.stdout, undefined);
|
||||
assert.strictEqual(proc.stderr, undefined);
|
||||
assert.strictEqual(proc.stdio, undefined);
|
||||
|
||||
proc.on('error', common.mustCall(function(err) {
|
||||
assert.strictEqual(err.code, 'EMFILE');
|
||||
}));
|
||||
|
||||
proc.on('exit', common.mustNotCall('"exit" event should not be emitted'));
|
||||
|
||||
// Close one fd for LSan
|
||||
if (openFds.length >= 1) {
|
||||
fs.closeSync(openFds.pop());
|
||||
}
|
||||
84
test/js/node/test/sequential/test-child-process-pass-fd.js
Normal file
84
test/js/node/test/sequential/test-child-process-pass-fd.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// On some OS X versions, when passing fd's between processes:
|
||||
// When the handle associated to a specific file descriptor is closed by the
|
||||
// sender process before it's received in the destination, the handle is indeed
|
||||
// closed while it should remain opened. In order to fix this behavior, don't
|
||||
// close the handle until the `NODE_HANDLE_ACK` is received by the sender.
|
||||
// This test is basically `test-cluster-net-send` but creating lots of workers
|
||||
// so the issue reproduces on OS X consistently.
|
||||
|
||||
if (common.isPi) {
|
||||
common.skip('Too slow for Raspberry Pi devices');
|
||||
}
|
||||
|
||||
const assert = require('assert');
|
||||
const { fork } = require('child_process');
|
||||
const net = require('net');
|
||||
|
||||
const N = 80;
|
||||
let messageCallbackCount = 0;
|
||||
|
||||
function forkWorker() {
|
||||
const messageCallback = (msg, handle) => {
|
||||
messageCallbackCount++;
|
||||
assert.strictEqual(msg, 'handle');
|
||||
assert.ok(handle);
|
||||
worker.send('got');
|
||||
|
||||
let recvData = '';
|
||||
handle.on('data', common.mustCall((data) => {
|
||||
recvData += data;
|
||||
}));
|
||||
|
||||
handle.on('end', () => {
|
||||
assert.strictEqual(recvData, 'hello');
|
||||
worker.kill();
|
||||
});
|
||||
};
|
||||
|
||||
const worker = fork(__filename, ['child']);
|
||||
worker.on('error', (err) => {
|
||||
if (/\bEAGAIN\b/.test(err.message)) {
|
||||
forkWorker();
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
worker.once('message', messageCallback);
|
||||
}
|
||||
|
||||
if (process.argv[2] !== 'child') {
|
||||
for (let i = 0; i < N; ++i) {
|
||||
forkWorker();
|
||||
}
|
||||
process.on('exit', () => { assert.strictEqual(messageCallbackCount, N); });
|
||||
} else {
|
||||
let socket;
|
||||
let cbcalls = 0;
|
||||
function socketConnected() {
|
||||
if (++cbcalls === 2)
|
||||
process.send('handle', socket);
|
||||
}
|
||||
|
||||
// As a side-effect, listening for the message event will ref the IPC channel,
|
||||
// so the child process will stay alive as long as it has a parent process/IPC
|
||||
// channel. Once this is done, we can unref our client and server sockets, and
|
||||
// the only thing keeping this worker alive will be IPC. This is important,
|
||||
// because it means a worker with no parent will have no referenced handles,
|
||||
// thus no work to do, and will exit immediately, preventing process leaks.
|
||||
process.on('message', common.mustCall());
|
||||
|
||||
const server = net.createServer((c) => {
|
||||
process.once('message', (msg) => {
|
||||
assert.strictEqual(msg, 'got');
|
||||
c.end('hello');
|
||||
});
|
||||
socketConnected();
|
||||
}).unref();
|
||||
server.listen(0, common.localhostIPv4, () => {
|
||||
const { port } = server.address();
|
||||
socket = net.connect(port, common.localhostIPv4, socketConnected).unref();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user