initial pass at lib/child_process_uv.js
authorRyan Dahl <ry@tinyclouds.org>
Sun, 31 Jul 2011 22:58:10 +0000 (15:58 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Sun, 31 Jul 2011 22:58:10 +0000 (15:58 -0700)
Makefile
lib/child_process.js [deleted file]
lib/child_process_legacy.js [new file with mode: 0644]
lib/net_uv.js
src/node.js
src/process_wrap.cc
test/simple/test-process-wrap.js

index 7e7f4bd733144818e28463c50ee6e7f1803c11c9..5dbcdd1a00dfbca3dc7daa3564a637c85f119967 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -233,6 +233,10 @@ UVTEST += simple/test-tls-request-timeout
 #UVTEST += simple/test-tls-server-verify # broken
 UVTEST += simple/test-tls-set-encoding
 
+# child_process
+UVTEST += simple/test-child-process-exit-code.js
+UVTEST += simple/test-child-process-buffering.js
+
 
 test-uv: all
        NODE_USE_UV=1 python tools/test.py $(UVTEST)
diff --git a/lib/child_process.js b/lib/child_process.js
deleted file mode 100644 (file)
index edcbaa0..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-// 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 util = require('util');
-var EventEmitter = require('events').EventEmitter;
-var Stream = require('net_legacy').Stream; // FIXME
-var InternalChildProcess = process.binding('child_process').ChildProcess;
-var constants;
-
-
-var spawn = exports.spawn = function(path, args /*, options, customFds */) {
-  var child = new ChildProcess();
-  child.spawn.apply(child, arguments);
-  return child;
-};
-
-
-function setupChannel(target, fd) {
-  target._channel = new Stream(fd);
-  target._channel.writable = true;
-  target._channel.readable = true;
-
-  target._channel.resume();
-  target._channel.setEncoding('utf8');
-
-  var buffer = '';
-  target._channel.on('data', function(d) {
-    buffer += d;
-    var i;
-    while ((i = buffer.indexOf('\n')) >= 0) {
-      var json = buffer.slice(0, i);
-      buffer = buffer.slice(i + 1);
-      var m = JSON.parse(json);
-      target.emit('message', m);
-    }
-  });
-
-  target.send = function(m) {
-    target._channel.write(JSON.stringify(m) + '\n');
-  };
-}
-
-
-exports.fork = function(modulePath, args, options) {
-  if (!options) options = {};
-  options.wantChannel = true;
-
-  if (!args) args = [];
-  args.unshift(modulePath);
-
-  // Unless they gave up customFds, just use the parent process
-  if (!options.customFds) options.customFds = [0, 1, 2];
-
-  var child = spawn(process.execPath, args, options);
-
-  setupChannel(child, child.fds[3]);
-
-  child.on('exit', function() {
-    child._channel.destroy();
-  });
-
-  return child;
-};
-
-
-exports._forkChild = function(fd) {
-  setupChannel(process, fd);
-};
-
-
-exports.exec = function(command /*, options, callback */) {
-  var _slice = Array.prototype.slice;
-  var args = ['/bin/sh', ['-c', command]].concat(_slice.call(arguments, 1));
-  return exports.execFile.apply(this, args);
-};
-
-
-// execFile('something.sh', { env: ENV }, function() { })
-
-exports.execFile = function(file /* args, options, callback */) {
-  var options = { encoding: 'utf8',
-                  timeout: 0,
-                  maxBuffer: 200 * 1024,
-                  killSignal: 'SIGTERM',
-                  setsid: false,
-                  cwd: null,
-                  env: null };
-  var args, optionArg, callback;
-
-  // Parse the parameters.
-
-  if (typeof arguments[arguments.length - 1] === 'function') {
-    callback = arguments[arguments.length - 1];
-  }
-
-  if (Array.isArray(arguments[1])) {
-    args = arguments[1];
-    if (typeof arguments[2] === 'object') optionArg = arguments[2];
-  } else {
-    args = [];
-    if (typeof arguments[1] === 'object') optionArg = arguments[1];
-  }
-
-  // Merge optionArg into options
-  if (optionArg) {
-    var keys = Object.keys(options);
-    for (var i = 0, len = keys.length; i < len; i++) {
-      var k = keys[i];
-      if (optionArg[k] !== undefined) options[k] = optionArg[k];
-    }
-  }
-
-  var child = spawn(file, args, {cwd: options.cwd, env: options.env});
-  var stdout = '';
-  var stderr = '';
-  var killed = false;
-  var exited = false;
-  var timeoutId;
-
-  var err;
-
-  function exithandler(code, signal) {
-    if (exited) return;
-    exited = true;
-
-    if (timeoutId) {
-      clearTimeout(timeoutId);
-      timeoutId = null;
-    }
-
-    if (!callback) return;
-
-    if (err) {
-      callback(err, stdout, stderr);
-    } else if (code === 0 && signal === null) {
-      callback(null, stdout, stderr);
-    } else {
-      var e = new Error('Command failed: ' + stderr);
-      e.killed = child.killed || killed;
-      e.code = code;
-      e.signal = signal;
-      callback(e, stdout, stderr);
-    }
-  }
-
-  function kill() {
-    killed = true;
-    child.kill(options.killSignal);
-    process.nextTick(function() {
-      exithandler(null, options.killSignal);
-    });
-  }
-
-  if (options.timeout > 0) {
-    timeoutId = setTimeout(function() {
-      kill();
-      timeoutId = null;
-    }, options.timeout);
-  }
-
-  child.stdout.setEncoding(options.encoding);
-  child.stderr.setEncoding(options.encoding);
-
-  child.stdout.addListener('data', function(chunk) {
-    stdout += chunk;
-    if (stdout.length > options.maxBuffer) {
-      err = new Error('maxBuffer exceeded.');
-      kill();
-    }
-  });
-
-  child.stderr.addListener('data', function(chunk) {
-    stderr += chunk;
-    if (stderr.length > options.maxBuffer) {
-      err = new Error('maxBuffer exceeded.');
-      kill();
-    }
-  });
-
-  child.addListener('exit', exithandler);
-
-  return child;
-};
-
-
-function ChildProcess() {
-  EventEmitter.call(this);
-
-  var self = this;
-
-  this.killed = false;
-
-  var gotCHLD = false;
-  var exitCode;
-  var termSignal;
-  var internal = this._internal = new InternalChildProcess();
-
-  var stdin = this.stdin = new Stream();
-  var stdout = this.stdout = new Stream();
-  var stderr = this.stderr = new Stream();
-
-  var stderrClosed = false;
-  var stdoutClosed = false;
-
-  stderr.addListener('close', function() {
-    stderrClosed = true;
-    if (gotCHLD && (!self.stdout || stdoutClosed)) {
-      self.emit('exit', exitCode, termSignal);
-    }
-  });
-
-  stdout.addListener('close', function() {
-    stdoutClosed = true;
-    if (gotCHLD && (!self.stderr || stderrClosed)) {
-      self.emit('exit', exitCode, termSignal);
-    }
-  });
-
-  internal.onexit = function(code, signal) {
-    gotCHLD = true;
-    exitCode = code;
-    termSignal = signal;
-    if (self.stdin) {
-      self.stdin.end();
-    }
-    if ((!self.stdout || !self.stdout.readable) &&
-        (!self.stderr || !self.stderr.readable)) {
-      self.emit('exit', exitCode, termSignal);
-    }
-  };
-
-  this.__defineGetter__('pid', function() { return internal.pid; });
-}
-util.inherits(ChildProcess, EventEmitter);
-
-
-ChildProcess.prototype.kill = function(sig) {
-  if (!this.killed && !this.exited) {
-    if (!constants) constants = process.binding('constants');
-    sig = sig || 'SIGTERM';
-    if (!constants[sig]) throw new Error('Unknown signal: ' + sig);
-    this.killed = this._internal.kill(constants[sig]);
-  }
-};
-
-
-ChildProcess.prototype.spawn = function(path, args, options, customFds) {
-  args = args || [];
-
-  var cwd, env, setsid, uid, gid;
-  if (!options || options.cwd === undefined &&
-      options.env === undefined &&
-      options.customFds === undefined &&
-      options.gid === undefined &&
-      options.uid === undefined) {
-    // Deprecated API: (path, args, options, env, customFds)
-    cwd = '';
-    env = options || process.env;
-    customFds = customFds || [-1, -1, -1];
-    setsid = false;
-    uid = -1;
-    gid = -1;
-  } else {
-    // Recommended API: (path, args, options)
-    cwd = options.cwd || '';
-    env = options.env || process.env;
-    customFds = options.customFds || [-1, -1, -1];
-    setsid = options.setsid ? true : false;
-    uid = options.hasOwnProperty('uid') ? options.uid : -1;
-    gid = options.hasOwnProperty('gid') ? options.gid : -1;
-  }
-
-  var envPairs = [];
-  var keys = Object.keys(env);
-  for (var key in env) {
-    envPairs.push(key + '=' + env[key]);
-  }
-
-  if (options && options.wantChannel) {
-    // The FILLMEIN will be replaced in C land with an integer!
-    // AWFUL! :D
-    envPairs.push('NODE_CHANNEL_FD=FILLMEIN');
-  }
-
-  var fds = this._internal.spawn(path,
-                                 args,
-                                 cwd,
-                                 envPairs,
-                                 customFds,
-                                 setsid,
-                                 uid,
-                                 gid);
-  this.fds = fds;
-
-  if (customFds[0] === -1 || customFds[0] === undefined) {
-    this.stdin.open(fds[0]);
-    this.stdin.writable = true;
-    this.stdin.readable = false;
-  } else {
-    this.stdin = null;
-  }
-
-  if (customFds[1] === -1 || customFds[1] === undefined) {
-    this.stdout.open(fds[1]);
-    this.stdout.writable = false;
-    this.stdout.readable = true;
-    this.stdout.resume();
-  } else {
-    this.stdout = null;
-  }
-
-  if (customFds[2] === -1 || customFds[2] === undefined) {
-    this.stderr.open(fds[2]);
-    this.stderr.writable = false;
-    this.stderr.readable = true;
-    this.stderr.resume();
-  } else {
-    this.stderr = null;
-  }
-};
-
diff --git a/lib/child_process_legacy.js b/lib/child_process_legacy.js
new file mode 100644 (file)
index 0000000..edcbaa0
--- /dev/null
@@ -0,0 +1,339 @@
+// 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 util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var Stream = require('net_legacy').Stream; // FIXME
+var InternalChildProcess = process.binding('child_process').ChildProcess;
+var constants;
+
+
+var spawn = exports.spawn = function(path, args /*, options, customFds */) {
+  var child = new ChildProcess();
+  child.spawn.apply(child, arguments);
+  return child;
+};
+
+
+function setupChannel(target, fd) {
+  target._channel = new Stream(fd);
+  target._channel.writable = true;
+  target._channel.readable = true;
+
+  target._channel.resume();
+  target._channel.setEncoding('utf8');
+
+  var buffer = '';
+  target._channel.on('data', function(d) {
+    buffer += d;
+    var i;
+    while ((i = buffer.indexOf('\n')) >= 0) {
+      var json = buffer.slice(0, i);
+      buffer = buffer.slice(i + 1);
+      var m = JSON.parse(json);
+      target.emit('message', m);
+    }
+  });
+
+  target.send = function(m) {
+    target._channel.write(JSON.stringify(m) + '\n');
+  };
+}
+
+
+exports.fork = function(modulePath, args, options) {
+  if (!options) options = {};
+  options.wantChannel = true;
+
+  if (!args) args = [];
+  args.unshift(modulePath);
+
+  // Unless they gave up customFds, just use the parent process
+  if (!options.customFds) options.customFds = [0, 1, 2];
+
+  var child = spawn(process.execPath, args, options);
+
+  setupChannel(child, child.fds[3]);
+
+  child.on('exit', function() {
+    child._channel.destroy();
+  });
+
+  return child;
+};
+
+
+exports._forkChild = function(fd) {
+  setupChannel(process, fd);
+};
+
+
+exports.exec = function(command /*, options, callback */) {
+  var _slice = Array.prototype.slice;
+  var args = ['/bin/sh', ['-c', command]].concat(_slice.call(arguments, 1));
+  return exports.execFile.apply(this, args);
+};
+
+
+// execFile('something.sh', { env: ENV }, function() { })
+
+exports.execFile = function(file /* args, options, callback */) {
+  var options = { encoding: 'utf8',
+                  timeout: 0,
+                  maxBuffer: 200 * 1024,
+                  killSignal: 'SIGTERM',
+                  setsid: false,
+                  cwd: null,
+                  env: null };
+  var args, optionArg, callback;
+
+  // Parse the parameters.
+
+  if (typeof arguments[arguments.length - 1] === 'function') {
+    callback = arguments[arguments.length - 1];
+  }
+
+  if (Array.isArray(arguments[1])) {
+    args = arguments[1];
+    if (typeof arguments[2] === 'object') optionArg = arguments[2];
+  } else {
+    args = [];
+    if (typeof arguments[1] === 'object') optionArg = arguments[1];
+  }
+
+  // Merge optionArg into options
+  if (optionArg) {
+    var keys = Object.keys(options);
+    for (var i = 0, len = keys.length; i < len; i++) {
+      var k = keys[i];
+      if (optionArg[k] !== undefined) options[k] = optionArg[k];
+    }
+  }
+
+  var child = spawn(file, args, {cwd: options.cwd, env: options.env});
+  var stdout = '';
+  var stderr = '';
+  var killed = false;
+  var exited = false;
+  var timeoutId;
+
+  var err;
+
+  function exithandler(code, signal) {
+    if (exited) return;
+    exited = true;
+
+    if (timeoutId) {
+      clearTimeout(timeoutId);
+      timeoutId = null;
+    }
+
+    if (!callback) return;
+
+    if (err) {
+      callback(err, stdout, stderr);
+    } else if (code === 0 && signal === null) {
+      callback(null, stdout, stderr);
+    } else {
+      var e = new Error('Command failed: ' + stderr);
+      e.killed = child.killed || killed;
+      e.code = code;
+      e.signal = signal;
+      callback(e, stdout, stderr);
+    }
+  }
+
+  function kill() {
+    killed = true;
+    child.kill(options.killSignal);
+    process.nextTick(function() {
+      exithandler(null, options.killSignal);
+    });
+  }
+
+  if (options.timeout > 0) {
+    timeoutId = setTimeout(function() {
+      kill();
+      timeoutId = null;
+    }, options.timeout);
+  }
+
+  child.stdout.setEncoding(options.encoding);
+  child.stderr.setEncoding(options.encoding);
+
+  child.stdout.addListener('data', function(chunk) {
+    stdout += chunk;
+    if (stdout.length > options.maxBuffer) {
+      err = new Error('maxBuffer exceeded.');
+      kill();
+    }
+  });
+
+  child.stderr.addListener('data', function(chunk) {
+    stderr += chunk;
+    if (stderr.length > options.maxBuffer) {
+      err = new Error('maxBuffer exceeded.');
+      kill();
+    }
+  });
+
+  child.addListener('exit', exithandler);
+
+  return child;
+};
+
+
+function ChildProcess() {
+  EventEmitter.call(this);
+
+  var self = this;
+
+  this.killed = false;
+
+  var gotCHLD = false;
+  var exitCode;
+  var termSignal;
+  var internal = this._internal = new InternalChildProcess();
+
+  var stdin = this.stdin = new Stream();
+  var stdout = this.stdout = new Stream();
+  var stderr = this.stderr = new Stream();
+
+  var stderrClosed = false;
+  var stdoutClosed = false;
+
+  stderr.addListener('close', function() {
+    stderrClosed = true;
+    if (gotCHLD && (!self.stdout || stdoutClosed)) {
+      self.emit('exit', exitCode, termSignal);
+    }
+  });
+
+  stdout.addListener('close', function() {
+    stdoutClosed = true;
+    if (gotCHLD && (!self.stderr || stderrClosed)) {
+      self.emit('exit', exitCode, termSignal);
+    }
+  });
+
+  internal.onexit = function(code, signal) {
+    gotCHLD = true;
+    exitCode = code;
+    termSignal = signal;
+    if (self.stdin) {
+      self.stdin.end();
+    }
+    if ((!self.stdout || !self.stdout.readable) &&
+        (!self.stderr || !self.stderr.readable)) {
+      self.emit('exit', exitCode, termSignal);
+    }
+  };
+
+  this.__defineGetter__('pid', function() { return internal.pid; });
+}
+util.inherits(ChildProcess, EventEmitter);
+
+
+ChildProcess.prototype.kill = function(sig) {
+  if (!this.killed && !this.exited) {
+    if (!constants) constants = process.binding('constants');
+    sig = sig || 'SIGTERM';
+    if (!constants[sig]) throw new Error('Unknown signal: ' + sig);
+    this.killed = this._internal.kill(constants[sig]);
+  }
+};
+
+
+ChildProcess.prototype.spawn = function(path, args, options, customFds) {
+  args = args || [];
+
+  var cwd, env, setsid, uid, gid;
+  if (!options || options.cwd === undefined &&
+      options.env === undefined &&
+      options.customFds === undefined &&
+      options.gid === undefined &&
+      options.uid === undefined) {
+    // Deprecated API: (path, args, options, env, customFds)
+    cwd = '';
+    env = options || process.env;
+    customFds = customFds || [-1, -1, -1];
+    setsid = false;
+    uid = -1;
+    gid = -1;
+  } else {
+    // Recommended API: (path, args, options)
+    cwd = options.cwd || '';
+    env = options.env || process.env;
+    customFds = options.customFds || [-1, -1, -1];
+    setsid = options.setsid ? true : false;
+    uid = options.hasOwnProperty('uid') ? options.uid : -1;
+    gid = options.hasOwnProperty('gid') ? options.gid : -1;
+  }
+
+  var envPairs = [];
+  var keys = Object.keys(env);
+  for (var key in env) {
+    envPairs.push(key + '=' + env[key]);
+  }
+
+  if (options && options.wantChannel) {
+    // The FILLMEIN will be replaced in C land with an integer!
+    // AWFUL! :D
+    envPairs.push('NODE_CHANNEL_FD=FILLMEIN');
+  }
+
+  var fds = this._internal.spawn(path,
+                                 args,
+                                 cwd,
+                                 envPairs,
+                                 customFds,
+                                 setsid,
+                                 uid,
+                                 gid);
+  this.fds = fds;
+
+  if (customFds[0] === -1 || customFds[0] === undefined) {
+    this.stdin.open(fds[0]);
+    this.stdin.writable = true;
+    this.stdin.readable = false;
+  } else {
+    this.stdin = null;
+  }
+
+  if (customFds[1] === -1 || customFds[1] === undefined) {
+    this.stdout.open(fds[1]);
+    this.stdout.writable = false;
+    this.stdout.readable = true;
+    this.stdout.resume();
+  } else {
+    this.stdout = null;
+  }
+
+  if (customFds[2] === -1 || customFds[2] === undefined) {
+    this.stderr.open(fds[2]);
+    this.stderr.writable = false;
+    this.stderr.readable = true;
+    this.stderr.resume();
+  } else {
+    this.stderr = null;
+  }
+};
+
index aee816fe6ce169da0d3ce949371224893a1c941b..a8c339c248bafcaf66f54d97f0acafe0407aa701 100644 (file)
@@ -3,8 +3,19 @@ var stream = require('stream');
 var timers = require('timers');
 var util = require('util');
 var assert = require('assert');
-var TCP = process.binding('tcp_wrap').TCP;
-var Pipe = process.binding('pipe_wrap').Pipe;
+
+// constructor for lazy loading
+function createPipe() {
+  var Pipe = process.binding('pipe_wrap').Pipe;
+  return new Pipe();
+}
+
+// constructor for lazy loading
+function createTCP() {
+  var TCP = process.binding('tcp_wrap').TCP;
+  return new TCP();
+}
+
 
 /* Bit flags for socket._flags */
 var FLAG_GOT_EOF      = 1 << 0;
@@ -35,7 +46,7 @@ exports.connect = exports.createConnection = function(port /* [host], [cb] */) {
   var s;
 
   if (isPipeName(port)) {
-    s = new Socket({handle:new Pipe});
+    s = new Socket({ handle: createPipe() });
   } else {
     s = new Socket();
   }
@@ -411,7 +422,7 @@ Socket.prototype.connect = function(port /* [host], [cb] */) {
   var pipe = isPipeName(port);
 
   if (this.destroyed || !this._handle) {
-    this._handle = pipe ? new Pipe() : new TCP();
+    this._handle = pipe ? createPipe() : createTCP();
     initSocketHandle(this);
   }
 
@@ -544,7 +555,8 @@ function listen(self, address, port, addressType) {
 
   if (!self._handle) {
     // assign handle in listen, and clean up if bind or listen fails
-    self._handle = (port == -1 && addressType == -1) ? new Pipe : new TCP;
+    self._handle =
+        (port == -1 && addressType == -1) ? createPipe() : createTCP();
   }
 
   self._handle.socket = self;
index 8676887698016e47e64b0040ff10c1b68b5a973e..ab9ee171dffc490036fa7bf074773aa4af415d0c 100644 (file)
       case 'net':
         return process.features.uv ? 'net_uv' : 'net_legacy';
 
+      case 'child_process':
+        return process.features.uv ? 'child_process_uv' :
+                                     'child_process_legacy';
+
       case 'timers':
         return process.features.uv ? 'timers_uv' : 'timers_legacy';
 
index 4b0f90a855b943edaf83d5c2107d60be49589696..6d9a230a9c0c9bf826e9ec297660ea2da3c9337b 100644 (file)
@@ -149,6 +149,8 @@ class ProcessWrap : public HandleWrap {
     wrap->SetHandle((uv_handle_t*)&wrap->process_);
     assert(wrap->process_.data == wrap);
 
+    wrap->object_->Set(String::New("pid"), Integer::New(wrap->process_.pid));
+
     if (options.args) {
       for (int i = 0; options.args[i]; i++) free(options.args[i]);
       delete [] options.args;
index 70c1e43fce134c7bec275fa12df0a24c2ab7df6d..b52f3fcbc4e69bc1ec057acda817350a27e0364c 100644 (file)
@@ -30,11 +30,14 @@ var processExited = false;
 var gotPipeEOF = false;
 var gotPipeData = false;
 
-p.onexit = function() {
+p.onexit = function(exitCode, signal) {
   console.log("exit");
   p.close();
   pipe.readStart();
 
+  assert.equal(0, exitCode);
+  assert.equal(0, signal);
+
   processExited = true;
 }