diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index 1e0270f2b98b2f..8c1d0f035d0769 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -589,4 +589,91 @@ done with care and by default will talk over the fd represented an environmental variable `NODE_CHANNEL_FD` on the child process. The input and output on this fd is expected to be line delimited JSON objects. +## child_process.spawnSync(command, [args], [options]) + +* `command` {String} The command to run +* `args` {Array} List of string arguments +* `options` {Object} + * `cwd` {String} Current working directory of the child process + * `input` {String|Buffer} The value which will be passed as stdin to the spawned process + - supplying this value will override `stdio[0]` + * `stdio` {Array} Child's stdio configuration. + * `env` {Object} Environment key-value pairs + * `uid` {Number} Sets the user identity of the process. (See setuid(2).) + * `gid` {Number} Sets the group identity of the process. (See setgid(2).) + * `timeout` {Number} In milliseconds the maximum amount of time the process is allowed to run. (Default: undefined) + * `killSignal` {String} The signal value to be used when the spawned process will be killed. (Default: 'SIGTERM') + * `maxBuffer` {Number} + * `encoding` {String} The encoding used for all stdio inputs and outputs. (Default: 'buffer') +* return: {Object} + * `pid` {Number} Pid of the child process + * `output` {Array} Array of results from stdio output + * `stdout` {Buffer|String} The contents of `output[1]` + * `stderr` {Buffer|String} The contents of `output[2]` + * `status` {Number} The exit code of the child process + * `signal` {String} The signal used to kill the child process + * `error` {Error} The error object if the child process failed or timedout + +`spawnSync` will not return until the child process has fully closed. When a +timeout has been encountered and `killSignal` is sent, the method won't return +until the process has completely exited. That is to say, if the process handles +the `SIGTERM` signal and doesn't exit, your process will wait until the child +process has exited. + +## child_process.execFileSync(command, [args], [options]) + +* `command` {String} The command to run +* `args` {Array} List of string arguments +* `options` {Object} + * `cwd` {String} Current working directory of the child process + * `input` {String|Buffer} The value which will be passed as stdin to the spawned process + - supplying this value will override `stdio[0]` + * `stdio` {Array} Child's stdio configuration. + * `env` {Object} Environment key-value pairs + * `uid` {Number} Sets the user identity of the process. (See setuid(2).) + * `gid` {Number} Sets the group identity of the process. (See setgid(2).) + * `timeout` {Number} In milliseconds the maximum amount of time the process is allowed to run. (Default: undefined) + * `killSignal` {String} The signal value to be used when the spawned process will be killed. (Default: 'SIGTERM') + * `maxBuffer` {Number} + * `encoding` {String} The encoding used for all stdio inputs and outputs. (Default: 'buffer') +* return: {Buffer|String} The stdout from the command + +`execFileSync` will not return until the child process has fully closed. When a +timeout has been encountered and `killSignal` is sent, the method won't return +until the process has completely exited. That is to say, if the process handles +the `SIGTERM` signal and doesn't exit, your process will wait until the child +process has exited. + +If the process times out, or has a non-zero exit code, this method ***will*** +throw. The `Error` object will contain the entire result from +[`child_process.spawnSync`](#child_process_child_process_spawnsync_command_args_options) + + +## child_process.execSync(command, [options]) + +* `command` {String} The command to run +* `options` {Object} + * `cwd` {String} Current working directory of the child process + * `input` {String|Buffer} The value which will be passed as stdin to the spawned process + - supplying this value will override `stdio[0]` + * `stdio` {Array} Child's stdio configuration. + * `env` {Object} Environment key-value pairs + * `uid` {Number} Sets the user identity of the process. (See setuid(2).) + * `gid` {Number} Sets the group identity of the process. (See setgid(2).) + * `timeout` {Number} In milliseconds the maximum amount of time the process is allowed to run. (Default: undefined) + * `killSignal` {String} The signal value to be used when the spawned process will be killed. (Default: 'SIGTERM') + * `maxBuffer` {Number} + * `encoding` {String} The encoding used for all stdio inputs and outputs. (Default: 'buffer') +* return: {Buffer|String} The stdout from the command + +`execSync` will not return until the child process has fully closed. When a +timeout has been encountered and `killSignal` is sent, the method won't return +until the process has completely exited. That is to say, if the process handles +the `SIGTERM` signal and doesn't exit, your process will wait until the child +process has exited. + +If the process times out, or has a non-zero exit code, this method ***will*** +throw. The `Error` object will contain the entire result from +[`child_process.spawnSync`](#child_process_child_process_spawnsync_command_args_options) + [EventEmitter]: events.html#events_class_events_eventemitter diff --git a/lib/child_process.js b/lib/child_process.js index d32b13d035505e..1f121d901f5511 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -29,6 +29,7 @@ var util = require('util'); var Process = process.binding('process_wrap').Process; var uv = process.binding('uv'); +var spawn_sync; // Lazy-loaded process.binding('spawn_sync') var constants; // Lazy-loaded process.binding('constants') var errnoException = util._errnoException; @@ -505,7 +506,7 @@ function setupChannel(target, channel) { // queue is flushed. if (!this._handleQueue) this._disconnect(); - } + }; target._disconnect = function() { assert(this._channel); @@ -585,7 +586,7 @@ exports._forkChild = function(fd) { }; -exports.exec = function(command /*, options, callback */) { +function normalizeExecArgs(command /*, options, callback */) { var file, args, options, callback; if (util.isFunction(arguments[1])) { @@ -611,7 +612,22 @@ exports.exec = function(command /*, options, callback */) { if (options && options.shell) file = options.shell; - return exports.execFile(file, args, options, callback); + return { + cmd: command, + file: file, + args: args, + options: options, + callback: callback + }; +} + + +exports.exec = function(command /*, options, callback */) { + var opts = normalizeExecArgs.apply(null, arguments); + return exports.execFile(opts.file, + opts.args, + opts.options, + opts.callback); }; @@ -777,8 +793,132 @@ exports.execFile = function(file /* args, options, callback */) { }; -var spawn = exports.spawn = function(file /*, args, options*/) { +function _convertCustomFds(options) { + if (options && options.customFds && !options.stdio) { + options.stdio = options.customFds.map(function(fd) { + return fd === -1 ? 'pipe' : fd; + }); + } +} + + +function _validateStdio(stdio, sync) { + var ipc, + ipcFd; + + // Replace shortcut with an array + if (util.isString(stdio)) { + switch (stdio) { + case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break; + case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break; + case 'inherit': stdio = [0, 1, 2]; break; + default: throw new TypeError('Incorrect value of stdio option: ' + stdio); + } + } else if (!util.isArray(stdio)) { + throw new TypeError('Incorrect value of stdio option: ' + + util.inspect(stdio)); + } + + // At least 3 stdio will be created + // Don't concat() a new Array() because it would be sparse, and + // stdio.reduce() would skip the sparse elements of stdio. + // See http://stackoverflow.com/a/5501711/3561 + while (stdio.length < 3) stdio.push(undefined); + + // Translate stdio into C++-readable form + // (i.e. PipeWraps or fds) + stdio = stdio.reduce(function(acc, stdio, i) { + function cleanup() { + acc.filter(function(stdio) { + return stdio.type === 'pipe' || stdio.type === 'ipc'; + }).forEach(function(stdio) { + if (stdio.handle) + stdio.handle.close(); + }); + } + + // Defaults + if (util.isNullOrUndefined(stdio)) { + stdio = i < 3 ? 'pipe' : 'ignore'; + } + + if (stdio === null || stdio === 'ignore') { + acc.push({type: 'ignore'}); + } else if (stdio === 'pipe' || util.isNumber(stdio) && stdio < 0) { + var a = { + type: 'pipe', + readable: i === 0, + writable: i !== 0 + }; + + if (!sync) + a.handle = createPipe(); + + acc.push(a); + } else if (stdio === 'ipc') { + if (sync || !util.isUndefined(ipc)) { + // Cleanup previously created pipes + cleanup(); + if (!sync) + throw Error('Child process can have only one IPC pipe'); + else + throw Error('You cannot use IPC with synchronous forks'); + } + + ipc = createPipe(true); + ipcFd = i; + + acc.push({ + type: 'pipe', + handle: ipc, + ipc: true + }); + } else if (stdio === 'inherit') { + acc.push({ + type: 'inherit', + fd: i + }); + } else if (util.isNumber(stdio) || util.isNumber(stdio.fd)) { + acc.push({ + type: 'fd', + fd: stdio.fd || stdio + }); + } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) || + getHandleWrapType(stdio._handle)) { + var handle = getHandleWrapType(stdio) ? + stdio : + getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle; + + acc.push({ + type: 'wrap', + wrapType: getHandleWrapType(handle), + handle: handle + }); + } else if (util.isBuffer(stdio) || util.isString(stdio)) { + if (!sync) { + cleanup(); + throw new TypeError('Asynchronous forks do not support Buffer input: ' + + util.inspect(stdio)); + } + } else { + // Cleanup + cleanup(); + throw new TypeError('Incorrect value for stdio stream: ' + + util.inspect(stdio)); + } + + return acc; + }, []); + + return {stdio: stdio, ipc: ipc, ipcFd: ipcFd}; +} + + +function normalizeSpawnArguments(/*file, args, options*/) { var args, options; + + var file = arguments[0]; + if (Array.isArray(arguments[1])) { args = arguments[1].slice(0); options = arguments[2]; @@ -787,6 +927,9 @@ var spawn = exports.spawn = function(file /*, args, options*/) { options = arguments[1]; } + if (!options) + options = {}; + args.unshift(file); var env = (options ? options.env : null) || process.env; @@ -795,12 +938,26 @@ var spawn = exports.spawn = function(file /*, args, options*/) { envPairs.push(key + '=' + env[key]); } + _convertCustomFds(options); + + return { + file: file, + args: args, + options: options, + envPairs: envPairs + }; +} + + +var spawn = exports.spawn = function(/*file, args, options*/) { + var opts = normalizeSpawnArguments.apply(null, arguments); + + var file = opts.file; + var args = opts.args; + var options = opts.options; + var envPairs = opts.envPairs; + var child = new ChildProcess(); - if (options && options.customFds && !options.stdio) { - options.stdio = options.customFds.map(function(fd) { - return fd === -1 ? 'pipe' : fd; - }); - } child.spawn({ file: file, @@ -923,78 +1080,11 @@ ChildProcess.prototype.spawn = function(options) { // If no `stdio` option was given - use default stdio = options.stdio || 'pipe'; - // Replace shortcut with an array - if (util.isString(stdio)) { - switch (stdio) { - case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break; - case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break; - case 'inherit': stdio = [0, 1, 2]; break; - default: throw new TypeError('Incorrect value of stdio option: ' + stdio); - } - } else if (!util.isArray(stdio)) { - throw new TypeError('Incorrect value of stdio option: ' + stdio); - } - - // At least 3 stdio will be created - // Don't concat() a new Array() because it would be sparse, and - // stdio.reduce() would skip the sparse elements of stdio. - // See http://stackoverflow.com/a/5501711/3561 - while (stdio.length < 3) stdio.push(undefined); - - // Translate stdio into C++-readable form - // (i.e. PipeWraps or fds) - stdio = stdio.reduce(function(acc, stdio, i) { - function cleanup() { - acc.filter(function(stdio) { - return stdio.type === 'pipe' || stdio.type === 'ipc'; - }).forEach(function(stdio) { - stdio.handle.close(); - }); - } - - // Defaults - if (util.isNullOrUndefined(stdio)) { - stdio = i < 3 ? 'pipe' : 'ignore'; - } - - if (stdio === 'ignore') { - acc.push({type: 'ignore'}); - } else if (stdio === 'pipe' || util.isNumber(stdio) && stdio < 0) { - acc.push({type: 'pipe', handle: createPipe()}); - } else if (stdio === 'ipc') { - if (!util.isUndefined(ipc)) { - // Cleanup previously created pipes - cleanup(); - throw Error('Child process can have only one IPC pipe'); - } - - ipc = createPipe(true); - ipcFd = i; - - acc.push({ type: 'pipe', handle: ipc, ipc: true }); - } else if (util.isNumber(stdio) || util.isNumber(stdio.fd)) { - acc.push({ type: 'fd', fd: stdio.fd || stdio }); - } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) || - getHandleWrapType(stdio._handle)) { - var handle = getHandleWrapType(stdio) ? - stdio : - getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle; - - acc.push({ - type: 'wrap', - wrapType: getHandleWrapType(handle), - handle: handle - }); - } else { - // Cleanup - cleanup(); - throw new TypeError('Incorrect value for stdio stream: ' + stdio); - } - - return acc; - }, []); + stdio = _validateStdio(stdio, false); - options.stdio = stdio; + ipc = stdio.ipc; + ipcFd = stdio.ipcFd; + stdio = options.stdio = stdio.stdio; if (!util.isUndefined(ipc)) { // Let child process know about opened IPC channel @@ -1113,3 +1203,129 @@ ChildProcess.prototype.ref = function() { ChildProcess.prototype.unref = function() { if (this._handle) this._handle.unref(); }; + + +function lookupSignal(signal) { + if (typeof signal === 'number') + return signal; + + if (!constants) + constants = process.binding('constants'); + + if (!(signal in constants)) + throw new Error('Unknown signal: ' + signal); + + return constants[signal]; +} + + +function spawnSync(/*file, args, options*/) { + var opts = normalizeSpawnArguments.apply(null, arguments); + + var options = opts.options; + var envPairs = opts.envPairs; + + var i; + + options.file = opts.file; + options.args = opts.args; + + if (options.killSignal) + options.killSignal = lookupSignal(options.killSignal); + + options.stdio = _validateStdio(options.stdio || 'pipe', true).stdio; + + if (options.input) { + var stdin = options.stdio[0] = util._extend({}, options.stdio[0]); + stdin.input = options.input; + } + + // We may want to pass data in on any given fd, ensure it is a valid buffer + for (i = 0; i < options.stdio.length; i++) { + var input = options.stdio[i] && options.stdio[i].input; + if (input != null) { + var pipe = options.stdio[i] = util._extend({}, options.stdio[i]); + if (Buffer.isBuffer(input)) + pipe.input = input; + else if (util.isString(input)) + pipe.input = new Buffer(input, options.encoding); + else + throw new TypeError(util.format( + 'stdio[%d] should be Buffer or string not %s', + i, + typeof input)); + } + } + + if (!spawn_sync) + spawn_sync = process.binding('spawn_sync'); + + var result = spawn_sync.spawn(options); + + if (result.output && options.encoding) { + for (i = 0; i < result.output.length; i++) { + if (!result.output[i]) + continue; + result.output[i] = result.output[i].toString(options.encoding); + } + } + + result.stdout = result.output && result.output[1]; + result.stderr = result.output && result.output[2]; + + if (result.error) + result.error = errnoException(result.error, 'spawnSync'); + + util._extend(result, opts); + + return result; +} +exports.spawnSync = spawnSync; + + +function checkExecSyncError(ret) { + if (ret.error || ret.status !== 0) { + var err = ret.error; + ret.error = null; + + if (!err) { + var cmd = ret.cmd ? ret.cmd : ret.args.join(' '); + err = new Error(util.format('Command failed: %s\n%s', + cmd, + ret.stderr.toString())); + } + + util._extend(err, ret); + return err; + } + + return false; +} + + +function execFileSync(/*command, options*/) { + var ret = spawnSync.apply(null, arguments); + + var err = checkExecSyncError(ret); + + if (err) + throw err; + else + return ret.stdout; +} +exports.execFileSync = execFileSync; + + +function execSync(/*comand, options*/) { + var opts = normalizeExecArgs.apply(null, arguments); + var ret = spawnSync(opts.file, opts.args, opts.options); + ret.cmd = opts.cmd; + + var err = checkExecSyncError(ret); + + if (err) + throw err; + else + return ret.stdout; +} +exports.execSync = execSync; diff --git a/test/simple/test-child-process-execsync.js b/test/simple/test-child-process-execsync.js new file mode 100644 index 00000000000000..101ac4fcc7220e --- /dev/null +++ b/test/simple/test-child-process-execsync.js @@ -0,0 +1,82 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); +var util = require('util'); +var os = require('os'); + +var execSync = require('child_process').execSync; +var execFileSync = require('child_process').execFileSync; + +var TIMER = 200; +var SLEEP = 1000; + +var start = Date.now(); +var err; +var caught = false; +try +{ + var cmd = util.format('%s -e "setTimeout(function(){}, %d);"', + process.execPath, SLEEP); + var ret = execSync(cmd, {timeout: TIMER}); +} catch (e) { + caught = true; + assert.strictEqual(e.errno, 'ETIMEDOUT'); + err = e; +} finally { + assert.strictEqual(ret, undefined, 'we should not have a return value'); + assert.strictEqual(caught, true, 'execSync should throw'); + var end = Date.now() - start; + assert(end < SLEEP); + assert(err.status > 128 || err.signal); +} + +assert.throws(function() { + execSync('iamabadcommand'); +}, /Command failed: iamabadcommand/); + +var msg = 'foobar'; +var msgBuf = new Buffer(msg + '\n'); + +// console.log ends every line with just '\n', even on Windows. +cmd = util.format('%s -e "console.log(\'%s\');"', process.execPath, msg); + +var ret = execSync(cmd); + +assert.strictEqual(ret.length, msgBuf.length); +assert.deepEqual(ret, msgBuf, 'execSync result buffer should match'); + +ret = execSync(cmd, { encoding: 'utf8' }); + +assert.strictEqual(ret, msg + '\n', 'execSync encoding result should match'); + +var args = [ + '-e', + util.format('console.log("%s");', msg) +]; +ret = execFileSync(process.execPath, args); + +assert.deepEqual(ret, msgBuf); + +ret = execFileSync(process.execPath, args, { encoding: 'utf8' }); + +assert.strictEqual(ret, msg + '\n', 'execFileSync encoding result should match'); diff --git a/test/simple/test-child-process-spawnsync-input.js b/test/simple/test-child-process-spawnsync-input.js new file mode 100644 index 00000000000000..2bcf043f80ac32 --- /dev/null +++ b/test/simple/test-child-process-spawnsync-input.js @@ -0,0 +1,124 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); +var os = require('os'); +var util = require('util'); + +var spawnSync = require('child_process').spawnSync; + +function checkRet(ret) { + assert.strictEqual(ret.status, 0); + assert.strictEqual(ret.error, undefined); +} + +var msgOut = 'this is stdout'; +var msgErr = 'this is stderr'; + +// this is actually not os.EOL? +var msgOutBuf = new Buffer(msgOut + '\n'); +var msgErrBuf = new Buffer(msgErr + '\n'); + +var args = [ + '-e', + util.format('console.log("%s"); console.error("%s");', msgOut, msgErr) +]; + +var ret; + + +if (process.argv.indexOf('spawnchild') !== -1) { + switch (process.argv[3]) { + case '1': + ret = spawnSync(process.execPath, args, { stdio: 'inherit' }); + checkRet(ret); + break; + case '2': + ret = spawnSync(process.execPath, args, { + stdio: ['inherit', 'inherit', 'inherit'] + }); + checkRet(ret); + break; + } + process.exit(0); + return; +} + + +function verifyBufOutput(ret) { + checkRet(ret); + assert.deepEqual(ret.stdout, msgOutBuf); + assert.deepEqual(ret.stderr, msgErrBuf); +} + + +verifyBufOutput(spawnSync(process.execPath, [__filename, 'spawnchild', 1])); +verifyBufOutput(spawnSync(process.execPath, [__filename, 'spawnchild', 2])); + +var options = { + input: 1234 +}; + +assert.throws(function() { + spawnSync('cat', [], options); +}, /TypeError:.*should be Buffer or string not number/); + + +options = { + input: 'hello world' +}; + +ret = spawnSync('cat', [], options); + +checkRet(ret); +assert.strictEqual(ret.stdout.toString('utf8'), options.input); +assert.strictEqual(ret.stderr.toString('utf8'), ''); + +options = { + input: new Buffer('hello world') +}; + +ret = spawnSync('cat', [], options); + +checkRet(ret); +assert.deepEqual(ret.stdout, options.input); +assert.deepEqual(ret.stderr, new Buffer('')); + +verifyBufOutput(spawnSync(process.execPath, args)); + +ret = spawnSync(process.execPath, args, { encoding: 'utf8' }); + +checkRet(ret); +assert.strictEqual(ret.stdout, msgOut + '\n'); +assert.strictEqual(ret.stderr, msgErr + '\n'); + +options = { + maxBuffer: 1 +}; + +ret = spawnSync(process.execPath, args, options); + +assert.ok(ret.error, 'maxBuffer should error'); +assert.strictEqual(ret.error.errno, 'ENOBUFS'); +// we can have buffers larger than maxBuffer because underneath we alloc 64k +// that matches our read sizes +assert.deepEqual(ret.stdout, msgOutBuf); diff --git a/test/simple/test-child-process-spawnsync-timeout.js b/test/simple/test-child-process-spawnsync-timeout.js new file mode 100644 index 00000000000000..691f58786eebf9 --- /dev/null +++ b/test/simple/test-child-process-spawnsync-timeout.js @@ -0,0 +1,46 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); + +var spawnSync = require('child_process').spawnSync; + +var TIMER = 200; +var SLEEP = 1000; + +switch (process.argv[2]) { + case 'child': + setTimeout(function() { + console.log('child fired'); + process.exit(1); + }, SLEEP); + break; + default: + var start = Date.now(); + var ret = spawnSync(process.execPath, [__filename, 'child'], {timeout: TIMER}); + assert.strictEqual(ret.error.errno, 'ETIMEDOUT'); + console.log(ret); + var end = Date.now() - start; + assert(end < SLEEP); + assert(ret.status > 128 || ret.signal); + break; +} diff --git a/test/simple/test-child-process-spawnsync.js b/test/simple/test-child-process-spawnsync.js new file mode 100644 index 00000000000000..f19200166a2726 --- /dev/null +++ b/test/simple/test-child-process-spawnsync.js @@ -0,0 +1,52 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); + +var spawnSync = require('child_process').spawnSync; + +var TIMER = 100; +var SLEEP = 1000; + +var start = Date.now(); +var timeout = 0; + +setTimeout(function() { + console.log('timer fired'); + timeout = Date.now(); +}, TIMER); + +console.log('sleep started'); +var ret = spawnSync('sleep', ['1']); +console.log('sleep exited'); + +process.on('exit', function() { + assert.strictEqual(ret.status, 0); + + var delta = Date.now() - start; + + var expected_timeout = start + TIMER; + var tdlta = timeout - expected_timeout; + + assert(delta > SLEEP); + assert(tdlta > TIMER && tdlta < SLEEP); +});