From 77e0a4ae1bad937d108fa949eeebf0a0c213c675 Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Thu, 2 Jul 2015 23:06:55 +0200 Subject: [PATCH 1/6] fix for #22 works for me in linux needs to be tested on unix and windows --- index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index a964a9c..55ed12e 100755 --- a/index.js +++ b/index.js @@ -70,7 +70,11 @@ function close (code) { for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { children[i].removeAllListeners('close'); - children[i].kill('SIGINT'); + if (process.platform === 'win32') { + children[i].kill("SIGINT") + } else { + spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]) + } if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); } } @@ -92,6 +96,7 @@ cmds.forEach(function (cmd) { var child = spawn(sh,[shFlag,cmd], { cwd: process.cwd, env: process.env, + detached: true, stdio: ['pipe', process.stdout, process.stderr] }) .on('close', childClose); From 735f36fa101f57b6ad9846814e7422669e14ad2d Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Fri, 3 Jul 2015 00:05:31 +0200 Subject: [PATCH 2/6] added unit tests --- index.js | 16 +++++++++--- test/index.coffee | 65 +++++++++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index 55ed12e..c0f4948 100755 --- a/index.js +++ b/index.js @@ -66,19 +66,27 @@ function status () { // closes all children and the process function close (code) { - var i, len; + var i, len, close = 0, opened = 0; + if (children.length == 1) process.exit(code); for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { + opened++; children[i].removeAllListeners('close'); if (process.platform === 'win32') { - children[i].kill("SIGINT") + children[i].kill("SIGINT"); } else { - spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]) + spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]); } if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); + children[i].on('close', function() { + closed++; + if (opened == closed) { + process.exit(code); + } + }); } } - process.exit(code); + } // cross platform compatibility diff --git a/test/index.coffee b/test/index.coffee index 2f740fc..ed0d166 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -1,20 +1,20 @@ chai = require "chai" should = chai.should() -spawn = require("child_process").spawn; +spawn = require("child_process").spawn Promise = require("bluebird") # cross platform compatibility if process.platform == "win32" - sh = "cmd"; - shFlag = "/c"; + sh = "cmd" + shFlag = "/c" else - sh = "sh"; - shFlag = "-c"; + sh = "sh" + shFlag = "-c" # children -waitingProcess = "\\\"node -e 'setTimeout(function(){},10000);'\\\"" -failingProcess = "\\\"node -e 'throw new Error(\"someError\");'\\\"" +waitingProcess = "\"node -e 'setTimeout(function(){},10000);'\"" +failingProcess = "\"node -e 'throw new Error();'\"" usageInfo = """ -h, --help output usage information @@ -23,9 +23,24 @@ usageInfo = """ """.split("\n") spawnParallelshell = (cmd) -> - return spawn sh, [shFlag, "node './index.js' " + cmd], { - cwd: process.cwd - } + return spawn sh, [shFlag, "node './index.js' " + cmd], { + detached: true, + cwd: process.cwd + } + +killPs = (ps) -> + if process.platform == 'win32' + ps.kill "SIGINT" + else + spawn(sh,[shFlag,"kill -INT -"+ps.pid]) + +spyOnPs = (ps) -> + ps.stdout.setEncoding("utf8") + ps.stdout.on "data", (data) -> + console.log data + ps.stderr.setEncoding("utf8") + ps.stderr.on "data", (data) -> + console.log "err: "+data testOutput = (cmd, expectedOutput) -> return new Promise (resolve) -> @@ -43,27 +58,31 @@ testOutput = (cmd, expectedOutput) -> describe "parallelshell", -> it "should print on -h and --help", (done) -> - Promise.all([testOutput("-h", usageInfo), testOutput("-help", usageInfo)]) + Promise.all([testOutput("-h", usageInfo), testOutput("--help", usageInfo)]) .finally done - it "should close with exitCode 2 on child error", (done) -> + it "should close with exitCode 1 on child error", (done) -> ps = spawnParallelshell(failingProcess) ps.on "close", () -> - ps.exitCode.should.equal 2 + ps.exitCode.should.equal 1 done() it "should run with a normal child", (done) -> ps = spawnParallelshell(waitingProcess) + ps.on "close", () -> + ps.signalCode.should.equal "SIGINT" + done() + setTimeout (() -> should.not.exist(ps.signalCode) - ps.kill() - done() - ),100 + killPs(ps) + ),50 + it "should close sibling processes on child error", (done) -> ps = spawnParallelshell([waitingProcess,failingProcess,waitingProcess].join(" ")) ps.on "close", () -> - ps.exitCode.should.equal 2 + ps.exitCode.should.equal 1 done() it "should wait for sibling processes on child error when called with -w or --wait", (done) -> @@ -72,13 +91,15 @@ describe "parallelshell", -> setTimeout (() -> should.not.exist(ps.signalCode) should.not.exist(ps2.signalCode) - ps.kill() - ps2.kill() - done() - ),100 + killPs(ps) + killPs(ps2) + ),50 + Promise.all [new Promise((resolve) -> ps.on("close",resolve)), + new Promise (resolve) -> ps2.on("close",resolve)] + .finally done it "should close on CTRL+C / SIGINT", (done) -> ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" ")) ps.on "close", () -> ps.signalCode.should.equal "SIGINT" done() - ps.kill("SIGINT") + killPs(ps) From 15d6cd1ee7a155b314972a1124623a367700ccba Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Fri, 3 Jul 2015 11:56:44 +0200 Subject: [PATCH 3/6] tiny bugfix --- index.js | 3 +- test/out | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 test/out diff --git a/index.js b/index.js index c0f4948..4544f53 100755 --- a/index.js +++ b/index.js @@ -67,7 +67,7 @@ function status () { // closes all children and the process function close (code) { var i, len, close = 0, opened = 0; - if (children.length == 1) process.exit(code); + for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { opened++; @@ -86,6 +86,7 @@ function close (code) { }); } } + if (opened == closed) process.exit(code); } diff --git a/test/out b/test/out new file mode 100644 index 0000000..7220d30 --- /dev/null +++ b/test/out @@ -0,0 +1,216 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + console.error('`' + this.cmd + '` failed with exit code ' + code); + } else { + console.log('`' + this.cmd + '` ended successfully'); + } + } + if (code > 0 && !wait) close(code); + status(); +} + +function status () { + if (verbose) { + var i, len; + console.log('\n'); + console.log('### Status ###'); + for (i = 0, len = children.length; i < len; i++) { + if (children[i].exitCode === null) { + console.log('`' + children[i].cmd + '` is still running'); + } else if (children[i].exitCode > 0) { + console.log('`' + children[i].cmd + '` errored'); + } else { + console.log('`' + children[i].cmd + '` finished'); + } + } + console.log('\n'); + } +} + +// closes all children and the process +function close (code) { + var i, len, close = 0, opened = 0; + + for (i = 0, len = children.length; i < len; i++) { + if (!children[i].exitCode) { + opened++; + children[i].removeAllListeners('close'); + if (process.platform === 'win32') { + children[i].kill("SIGINT"); + } else { + spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]); + } + if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); + children[i].on('close', function() { + closed++; + if (opened == closed) { + process.exit(code); + } + }); + } + } + if (opened == closed) process.exit(code); + +} + +// cross platform compatibility +if (process.platform === 'win32') { + sh = 'cmd'; + shFlag = '/c'; +} else { + sh = 'sh'; + shFlag = '-c'; +} + +// start the children +children = []; +cmds.forEach(function (cmd) { + var child = spawn(sh,[shFlag,cmd], { + cwd: process.cwd, + env: process.env, + detached: true, + stdio: ['pipe', process.stdout, process.stderr] + }) + .on('close', childClose); + child.cmd = cmd + children.push(child) +}); + +// close all children on ctrl+c +process.on('SIGINT', close) + +}).call(this,require('_process')) +},{"_process":3,"child_process":2}],2:[function(require,module,exports){ + +},{}],3:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + currentQueue[queueIndex].run(); + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}]},{},[1]); From aee061c62e68e80a417c49f8123706d0855f697a Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Fri, 3 Jul 2015 14:02:28 +0200 Subject: [PATCH 4/6] tiny bugfix 2 --- index.js | 2 +- test/out | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 4544f53..6f027d0 100755 --- a/index.js +++ b/index.js @@ -66,7 +66,7 @@ function status () { // closes all children and the process function close (code) { - var i, len, close = 0, opened = 0; + var i, len, closed = 0, opened = 0; for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { diff --git a/test/out b/test/out index 7220d30..77e7869 100644 --- a/test/out +++ b/test/out @@ -68,7 +68,7 @@ function status () { // closes all children and the process function close (code) { - var i, len, close = 0, opened = 0; + var i, len, closed = 0, opened = 0; for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { From 3b29f57e9a64414eccbad9329985672ce9d37308 Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Fri, 3 Jul 2015 19:28:33 +0200 Subject: [PATCH 5/6] removed accidential added file --- test/out | 216 ------------------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 test/out diff --git a/test/out b/test/out deleted file mode 100644 index 77e7869..0000000 --- a/test/out +++ /dev/null @@ -1,216 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { - console.error('`' + this.cmd + '` failed with exit code ' + code); - } else { - console.log('`' + this.cmd + '` ended successfully'); - } - } - if (code > 0 && !wait) close(code); - status(); -} - -function status () { - if (verbose) { - var i, len; - console.log('\n'); - console.log('### Status ###'); - for (i = 0, len = children.length; i < len; i++) { - if (children[i].exitCode === null) { - console.log('`' + children[i].cmd + '` is still running'); - } else if (children[i].exitCode > 0) { - console.log('`' + children[i].cmd + '` errored'); - } else { - console.log('`' + children[i].cmd + '` finished'); - } - } - console.log('\n'); - } -} - -// closes all children and the process -function close (code) { - var i, len, closed = 0, opened = 0; - - for (i = 0, len = children.length; i < len; i++) { - if (!children[i].exitCode) { - opened++; - children[i].removeAllListeners('close'); - if (process.platform === 'win32') { - children[i].kill("SIGINT"); - } else { - spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]); - } - if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); - children[i].on('close', function() { - closed++; - if (opened == closed) { - process.exit(code); - } - }); - } - } - if (opened == closed) process.exit(code); - -} - -// cross platform compatibility -if (process.platform === 'win32') { - sh = 'cmd'; - shFlag = '/c'; -} else { - sh = 'sh'; - shFlag = '-c'; -} - -// start the children -children = []; -cmds.forEach(function (cmd) { - var child = spawn(sh,[shFlag,cmd], { - cwd: process.cwd, - env: process.env, - detached: true, - stdio: ['pipe', process.stdout, process.stderr] - }) - .on('close', childClose); - child.cmd = cmd - children.push(child) -}); - -// close all children on ctrl+c -process.on('SIGINT', close) - -}).call(this,require('_process')) -},{"_process":3,"child_process":2}],2:[function(require,module,exports){ - -},{}],3:[function(require,module,exports){ -// shim for using process in browser - -var process = module.exports = {}; -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; - -function cleanUpNextTick() { - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } -} - -function drainQueue() { - if (draining) { - return; - } - var timeout = setTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - currentQueue[queueIndex].run(); - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - clearTimeout(timeout); -} - -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - setTimeout(drainQueue, 0); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -// TODO(shtylman) -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - -},{}]},{},[1]); From f094e9af4711293a58b05fe4b5978be714ad53ae Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Tue, 28 Jul 2015 11:45:41 +0200 Subject: [PATCH 6/6] moved to use exec on unix --- index.js | 12 +++++----- test/index.coffee | 56 +++++++++++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 6f027d0..c93f8a0 100755 --- a/index.js +++ b/index.js @@ -72,11 +72,7 @@ function close (code) { if (!children[i].exitCode) { opened++; children[i].removeAllListeners('close'); - if (process.platform === 'win32') { - children[i].kill("SIGINT"); - } else { - spawn(sh,[shFlag,'kill -TERM -'+children[i].pid]); - } + children[i].kill("SIGINT"); if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); children[i].on('close', function() { closed++; @@ -86,7 +82,7 @@ function close (code) { }); } } - if (opened == closed) process.exit(code); + if (opened == closed) {process.exit(code);} } @@ -102,10 +98,12 @@ if (process.platform === 'win32') { // start the children children = []; cmds.forEach(function (cmd) { + if (process.platform != 'win32') { + cmd = "exec "+cmd; + } var child = spawn(sh,[shFlag,cmd], { cwd: process.cwd, env: process.env, - detached: true, stdio: ['pipe', process.stdout, process.stderr] }) .on('close', childClose); diff --git a/test/index.coffee b/test/index.coffee index ed0d166..02aebba 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -3,14 +3,15 @@ should = chai.should() spawn = require("child_process").spawn Promise = require("bluebird") +verbose = 0 + # cross platform compatibility if process.platform == "win32" sh = "cmd" - shFlag = "/c" + shArg = "/c" else sh = "sh" - shFlag = "-c" - + shArg = "-c" # children waitingProcess = "\"node -e 'setTimeout(function(){},10000);'\"" @@ -22,29 +23,34 @@ usageInfo = """ -w, --wait will not close sibling processes on error """.split("\n") +cmdWrapper = (cmd) -> + if process.platform != "win32" + cmd = "exec "+cmd + if verbose + console.log "Calling: "+cmd + return cmd + spawnParallelshell = (cmd) -> - return spawn sh, [shFlag, "node './index.js' " + cmd], { - detached: true, + return spawn sh, [shArg, cmdWrapper("node ./index.js "+cmd )], { cwd: process.cwd } killPs = (ps) -> - if process.platform == 'win32' - ps.kill "SIGINT" - else - spawn(sh,[shFlag,"kill -INT -"+ps.pid]) - -spyOnPs = (ps) -> - ps.stdout.setEncoding("utf8") - ps.stdout.on "data", (data) -> - console.log data - ps.stderr.setEncoding("utf8") - ps.stderr.on "data", (data) -> - console.log "err: "+data + ps.kill "SIGINT" + +spyOnPs = (ps, verbosity=1) -> + if verbose >= verbosity + ps.stdout.setEncoding("utf8") + ps.stdout.on "data", (data) -> + console.log data + ps.stderr.setEncoding("utf8") + ps.stderr.on "data", (data) -> + console.log "err: "+data testOutput = (cmd, expectedOutput) -> return new Promise (resolve) -> ps = spawnParallelshell(cmd) + spyOnPs ps, 3 ps.stdout.setEncoding("utf8") output = [] ps.stdout.on "data", (data) -> @@ -52,23 +58,26 @@ testOutput = (cmd, expectedOutput) -> lines.pop() if lines[lines.length-1] == "" output = output.concat(lines) ps.stdout.on "end", () -> - for line,i in output - line.should.equal expectedOutput[i] + for line,i in expectedOutput + line.should.equal output[i] resolve() describe "parallelshell", -> it "should print on -h and --help", (done) -> Promise.all([testOutput("-h", usageInfo), testOutput("--help", usageInfo)]) - .finally done + .then -> done() + .catch done it "should close with exitCode 1 on child error", (done) -> ps = spawnParallelshell(failingProcess) + spyOnPs ps, 2 ps.on "close", () -> ps.exitCode.should.equal 1 done() it "should run with a normal child", (done) -> ps = spawnParallelshell(waitingProcess) + spyOnPs ps, 1 ps.on "close", () -> ps.signalCode.should.equal "SIGINT" done() @@ -81,6 +90,7 @@ describe "parallelshell", -> it "should close sibling processes on child error", (done) -> ps = spawnParallelshell([waitingProcess,failingProcess,waitingProcess].join(" ")) + spyOnPs ps,2 ps.on "close", () -> ps.exitCode.should.equal 1 done() @@ -88,6 +98,8 @@ describe "parallelshell", -> it "should wait for sibling processes on child error when called with -w or --wait", (done) -> ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" ")) ps2 = spawnParallelshell(["--wait",waitingProcess,failingProcess,waitingProcess].join(" ")) + spyOnPs ps,2 + spyOnPs ps2,2 setTimeout (() -> should.not.exist(ps.signalCode) should.not.exist(ps2.signalCode) @@ -96,9 +108,11 @@ describe "parallelshell", -> ),50 Promise.all [new Promise((resolve) -> ps.on("close",resolve)), new Promise (resolve) -> ps2.on("close",resolve)] - .finally done + .then -> done() + .catch done it "should close on CTRL+C / SIGINT", (done) -> ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" ")) + spyOnPs ps,2 ps.on "close", () -> ps.signalCode.should.equal "SIGINT" done()