summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy J Fontaine <tjfontaine@gmail.com>2014-02-10 21:40:48 +0100
committerBert Belder <bertbelder@gmail.com>2014-02-10 21:41:44 +0100
commit297fee70b978ee0c5fa492b5656109ca1a087239 (patch)
treee2beed9f46476480ac270471b5eea8505c0e0521
parentfa4eb47caacde4435c16f4ebef0c4f3fa001ccd2 (diff)
downloadnode-execSync-wip.tar.gz
child_process: js bits for spawnSync/execSyncexecSync-wip
This implements the user-facing APIs that lets one run a child process and block until it exits. Some logic that these new functions had in common with the existing spawn/exec/execFile implementation was refactored into separate functions, so it could be shared. Docs and tests are included.
-rw-r--r--doc/api/child_process.markdown69
-rw-r--r--lib/child_process.js376
-rw-r--r--test/simple/test-child-process-execsync.js82
-rw-r--r--test/simple/test-child-process-spawnsync-input.js124
-rw-r--r--test/simple/test-child-process-spawnsync-timeout.js46
-rw-r--r--test/simple/test-child-process-spawnsync.js52
6 files changed, 669 insertions, 80 deletions
diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown
index 1e0270f2b..8bea6c6e5 100644
--- a/doc/api/child_process.markdown
+++ b/doc/api/child_process.markdown
@@ -589,4 +589,73 @@ 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
+
+## 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
+
+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
+
+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 d32b13d03..1f121d901 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 000000000..101ac4fcc
--- /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 000000000..2bcf043f8
--- /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 000000000..691f58786
--- /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 000000000..f19200166
--- /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);
+});