From fcb6a7493920d344319edd5580793fcaa05f4db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gaul?= Date: Thu, 23 Jul 2015 23:20:13 +0200 Subject: [PATCH 1/5] introduce --first option With --first, all processes are terminated after the first process terminated (regardless of the exit code). The exit code is the exit code of the process that terminated first. --- index.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index a964a9c..07751fb 100755 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ 'use strict'; var spawn = require('child_process').spawn; -var sh, shFlag, children, args, wait, cmds, verbose, i ,len; +var sh, shFlag, children, args, wait, first, cmds, verbose, i ,len; // parsing argv cmds = []; args = process.argv.slice(2); @@ -14,6 +14,10 @@ for (i = 0, len = args.length; i < len; i++) { case '--wait': wait = true; break; + case '-f': + case '--first': + first = true; + break; case '-v': case '--verbose': verbose = true; @@ -21,8 +25,9 @@ for (i = 0, len = args.length; i < len; i++) { case '-h': case '--help': console.log('-h, --help output usage information'); - console.log('-v, --verbose verbose logging') - console.log('-w, --wait will not close sibling processes on error') + console.log('-v, --verbose verbose logging'); + console.log('-w, --wait will not close sibling processes on error'); + console.log('-f, --first close all sibling processes after first exits (succes/error)'); process.exit(); break; } @@ -31,6 +36,11 @@ for (i = 0, len = args.length; i < len; i++) { } } +if (wait && first) { + console.error('--wait and --first cannot be used together'); + process.exit(1); +} + // called on close of a child process function childClose (code) { var i, len; @@ -42,7 +52,7 @@ function childClose (code) { console.log('`' + this.cmd + '` ended successfully'); } } - if (code > 0 && !wait) close(code); + if (first || code > 0 && !wait) close(code); status(); } From dc938294f535d2d7c9dcb69d5d3d6f160d017717 Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Wed, 29 Jul 2015 12:28:56 +0200 Subject: [PATCH 2/5] some updated --- README.md | 7 +++++++ index.js | 51 ++++++++++++++++++++++++++++------------------ test/index.coffee | 52 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 718dac5..bcc7b3b 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,13 @@ This will execute the commands `echo 1` `echo 2` and `echo 3` simultaneously. Note that on Windows, you need to use double-quotes to avoid confusing the argument parser. +##### Nested usage + +```bash +parallelshell "echo 1" "parallelshell 'echo 2' 'parallelshell \'echo 3\' \'parallelshell \'\'echo 4\'\' \''" +``` +Closing on Windows will be unreliable in this case.. + Available options: ``` -h, --help output usage information diff --git a/index.js b/index.js index c93f8a0..bb43f25 100755 --- a/index.js +++ b/index.js @@ -37,49 +37,50 @@ function childClose (code) { code = code ? (code.code || code) : code; if (verbose) { if (code > 0) { - console.error('`' + this.cmd + '` failed with exit code ' + code); + console.error('parallelshell: `' + this.cmd + '` failed with exit code ' + code); } else { - console.log('`' + this.cmd + '` ended successfully'); + console.log('parallelshell: `' + this.cmd + '` ended successfully'); } } if (code > 0 && !wait) close(code); - status(); } function status () { if (verbose) { var i, len; - console.log('\n'); - console.log('### Status ###'); + console.log('parallelshell: Status'); for (i = 0, len = children.length; i < len; i++) { if (children[i].exitCode === null) { - console.log('`' + children[i].cmd + '` is still running'); + console.log('parallelshell: `' + children[i].cmd + '` is still running'); } else if (children[i].exitCode > 0) { - console.log('`' + children[i].cmd + '` errored'); + console.log('parallelshell: `' + children[i].cmd + '` errored'); } else { - console.log('`' + children[i].cmd + '` finished'); + console.log('parallelshell: `' + children[i].cmd + '` finished'); } } - console.log('\n'); } } // closes all children and the process function close (code) { - var i, len, closed = 0, opened = 0; + var i, len, closeHandler, closed = 0, opened = 0; for (i = 0, len = children.length; i < len; i++) { if (!children[i].exitCode) { opened++; children[i].removeAllListeners('close'); children[i].kill("SIGINT"); - if (verbose) console.log('`' + children[i].cmd + '` will now be closed'); - children[i].on('close', function() { - closed++; - if (opened == closed) { - process.exit(code); - } - }); + if (verbose) console.log('parallelshell: `' + children[i].cmd + '` will now be closed'); + closeHandler = function (child) { + child.on('close', function() { + if (verbose) console.log('parallelshell: `' + child.cmd + '` closed successfully'); + closed++; + if (opened == closed) { + process.exit(code); + } + }); + }(children[i]) + } } if (opened == closed) {process.exit(code);} @@ -99,12 +100,15 @@ if (process.platform === 'win32') { children = []; cmds.forEach(function (cmd) { if (process.platform != 'win32') { - cmd = "exec "+cmd; + cmd = "exec "+cmd; + } else { + cmd = cmd.replace(/'/g,"\""); } var child = spawn(sh,[shFlag,cmd], { cwd: process.cwd, env: process.env, - stdio: ['pipe', process.stdout, process.stderr] + stdio: ['pipe', process.stdout, process.stderr], + windowsVerbatimArguments: process.platform === 'win32' }) .on('close', childClose); child.cmd = cmd @@ -112,4 +116,11 @@ cmds.forEach(function (cmd) { }); // close all children on ctrl+c -process.on('SIGINT', close) +process.on('SIGINT', function() { + if (verbose) console.log('parallelshell: recieved SIGINT'); + close(); +}); + +process.on('exit', function(code) { + if (verbose) console.log('parallelshell: exit code:', code); +}); diff --git a/test/index.coffee b/test/index.coffee index 02aebba..aadbb35 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -26,6 +26,7 @@ usageInfo = """ cmdWrapper = (cmd) -> if process.platform != "win32" cmd = "exec "+cmd + if verbose console.log "Calling: "+cmd return cmd @@ -33,10 +34,15 @@ cmdWrapper = (cmd) -> spawnParallelshell = (cmd) -> return spawn sh, [shArg, cmdWrapper("node ./index.js "+cmd )], { cwd: process.cwd + windowsVerbatimArguments: process.platform == 'win32' } killPs = (ps) -> - ps.kill "SIGINT" + if process.platform != "win32" + ps.kill "SIGINT" + else + killer = spawn sh, [shArg, "taskkill /F /T /PID "+ps.pid] + spyOnPs killer, 3 spyOnPs = (ps, verbosity=1) -> if verbose >= verbosity @@ -79,25 +85,26 @@ describe "parallelshell", -> ps = spawnParallelshell(waitingProcess) spyOnPs ps, 1 ps.on "close", () -> - ps.signalCode.should.equal "SIGINT" done() - setTimeout (() -> should.not.exist(ps.signalCode) killPs(ps) - ),50 + ),150 it "should close sibling processes on child error", (done) -> - ps = spawnParallelshell([waitingProcess,failingProcess,waitingProcess].join(" ")) + ps = spawnParallelshell([waitingProcess,failingProcess].join(" ")) spyOnPs ps,2 + ps.on "exit", () -> + ps.exitCode.should.equal 1 + done() ps.on "close", () -> ps.exitCode.should.equal 1 done() 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(" ")) + ps = spawnParallelshell(["-w",waitingProcess,failingProcess].join(" ")) + ps2 = spawnParallelshell(["--wait",waitingProcess,failingProcess].join(" ")) spyOnPs ps,2 spyOnPs ps2,2 setTimeout (() -> @@ -105,15 +112,40 @@ describe "parallelshell", -> should.not.exist(ps2.signalCode) killPs(ps) killPs(ps2) - ),50 + ),250 Promise.all [new Promise((resolve) -> ps.on("close",resolve)), new Promise (resolve) -> ps2.on("close",resolve)] .then -> done() .catch done it "should close on CTRL+C / SIGINT", (done) -> - ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" ")) + ps = spawnParallelshell(["-w",waitingProcess].join(" ")) spyOnPs ps,2 ps.on "close", () -> - ps.signalCode.should.equal "SIGINT" done() killPs(ps) + it "should work with chained commands", (done) -> + output = ["1","2"] + if process.platform == "win32" + output[0] += "\r" + output[1] += "\r" + testOutput("\"echo 1&& echo 2\"", output) + .then done + .catch done + it "should work nested", (done) -> + output = ["1","2"] + if process.platform == "win32" + output[0] += "\r" + output[1] += "\r" + testOutput("\"echo 1\" \"node ./index.js 'echo 2'\"", output) + .then done + .catch done + + it "should work with setting ENV", (done) -> + output = ["test1"] + if process.platform == "win32" + setString = "set test=test1&" + else + setString = "test=test1 " + testOutput("\"#{setString}node -e 'console.log(process.env.test);'\"", output) + .then done + .catch done From 31728f62d916b2c2a75ed9e02af332bfb052e8bf Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Wed, 29 Jul 2015 12:52:31 +0200 Subject: [PATCH 3/5] tests for --first --- index.js | 5 ----- test/index.coffee | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 94cf1b1..7c498db 100755 --- a/index.js +++ b/index.js @@ -52,12 +52,7 @@ function childClose (code) { console.log('parallelshell: `' + this.cmd + '` ended successfully'); } } -<<<<<<< HEAD if (first || code > 0 && !wait) close(code); - status(); -======= - if (code > 0 && !wait) close(code); ->>>>>>> dev2 } function status () { diff --git a/test/index.coffee b/test/index.coffee index aadbb35..a405f22 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -14,7 +14,8 @@ else shArg = "-c" # children -waitingProcess = "\"node -e 'setTimeout(function(){},10000);'\"" +waitingProcess = (time=10000) -> + return "\"node -e 'setTimeout(function(){},#{time});'\"" failingProcess = "\"node -e 'throw new Error();'\"" usageInfo = """ @@ -53,17 +54,21 @@ spyOnPs = (ps, verbosity=1) -> ps.stderr.on "data", (data) -> console.log "err: "+data -testOutput = (cmd, expectedOutput) -> +testOutput = (cmd, expectedOutput, std="out") -> return new Promise (resolve) -> ps = spawnParallelshell(cmd) + if std == "out" + std = ps.stdout + else + std = ps.stderr spyOnPs ps, 3 - ps.stdout.setEncoding("utf8") + std.setEncoding("utf8") output = [] - ps.stdout.on "data", (data) -> + std.on "data", (data) -> lines = data.split("\n") lines.pop() if lines[lines.length-1] == "" output = output.concat(lines) - ps.stdout.on "end", () -> + std.on "end", () -> for line,i in expectedOutput line.should.equal output[i] resolve() @@ -82,7 +87,7 @@ describe "parallelshell", -> done() it "should run with a normal child", (done) -> - ps = spawnParallelshell(waitingProcess) + ps = spawnParallelshell(waitingProcess()) spyOnPs ps, 1 ps.on "close", () -> done() @@ -93,7 +98,7 @@ describe "parallelshell", -> it "should close sibling processes on child error", (done) -> - ps = spawnParallelshell([waitingProcess,failingProcess].join(" ")) + ps = spawnParallelshell([waitingProcess(),failingProcess].join(" ")) spyOnPs ps,2 ps.on "exit", () -> ps.exitCode.should.equal 1 @@ -103,8 +108,8 @@ describe "parallelshell", -> done() it "should wait for sibling processes on child error when called with -w or --wait", (done) -> - ps = spawnParallelshell(["-w",waitingProcess,failingProcess].join(" ")) - ps2 = spawnParallelshell(["--wait",waitingProcess,failingProcess].join(" ")) + ps = spawnParallelshell(["-w",waitingProcess(),failingProcess].join(" ")) + ps2 = spawnParallelshell(["--wait",waitingProcess(),failingProcess].join(" ")) spyOnPs ps,2 spyOnPs ps2,2 setTimeout (() -> @@ -118,7 +123,7 @@ describe "parallelshell", -> .then -> done() .catch done it "should close on CTRL+C / SIGINT", (done) -> - ps = spawnParallelshell(["-w",waitingProcess].join(" ")) + ps = spawnParallelshell(["-w",waitingProcess()].join(" ")) spyOnPs ps,2 ps.on "close", () -> done() @@ -149,3 +154,17 @@ describe "parallelshell", -> testOutput("\"#{setString}node -e 'console.log(process.env.test);'\"", output) .then done .catch done + + it "should work with first", (done) -> + ps = spawnParallelshell(["--first",waitingProcess(10),waitingProcess(10000)].join(" ")) + ps.on "close", () -> + ps.exitCode.should.equal 0 + done() + ps.on "exit", () -> + ps.exitCode.should.equal 0 + done() + + it "should not work with first and wait", (done) -> + testOutput("--wait --first", ["--wait and --first cannot be used together"], "err") + .then done + .catch done \ No newline at end of file From 507552fe27ad9768fc892189fd53e4fa0cd44307 Mon Sep 17 00:00:00 2001 From: Paul Pflugradt Date: Wed, 29 Jul 2015 13:07:16 +0200 Subject: [PATCH 4/5] moved to kill by pgid on unix --- index.js | 15 +++++++++------ test/index.coffee | 24 +++++++++--------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index 7c498db..441a6a7 100755 --- a/index.js +++ b/index.js @@ -79,7 +79,11 @@ function close (code) { if (!children[i].exitCode) { opened++; children[i].removeAllListeners('close'); - children[i].kill("SIGINT"); + if (process.platform != "win32") { + spawn(sh, [shFlag, "kill -INT -"+children[i].pid]); + } else { + children[i].kill("SIGINT"); + } if (verbose) console.log('parallelshell: `' + children[i].cmd + '` will now be closed'); closeHandler = function (child) { child.on('close', function() { @@ -90,7 +94,7 @@ function close (code) { } }); }(children[i]) - + } } if (opened == closed) {process.exit(code);} @@ -109,16 +113,15 @@ if (process.platform === 'win32') { // start the children children = []; cmds.forEach(function (cmd) { - if (process.platform != 'win32') { - cmd = "exec "+cmd; - } else { + if (process.platform === 'win32') { cmd = cmd.replace(/'/g,"\""); } var child = spawn(sh,[shFlag,cmd], { cwd: process.cwd, env: process.env, stdio: ['pipe', process.stdout, process.stderr], - windowsVerbatimArguments: process.platform === 'win32' + windowsVerbatimArguments: process.platform === 'win32', + detached: process.platform != 'win32' }) .on('close', childClose); child.cmd = cmd diff --git a/test/index.coffee b/test/index.coffee index a405f22..aa114c4 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -25,9 +25,6 @@ usageInfo = """ """.split("\n") cmdWrapper = (cmd) -> - if process.platform != "win32" - cmd = "exec "+cmd - if verbose console.log "Calling: "+cmd return cmd @@ -36,14 +33,17 @@ spawnParallelshell = (cmd) -> return spawn sh, [shArg, cmdWrapper("node ./index.js "+cmd )], { cwd: process.cwd windowsVerbatimArguments: process.platform == 'win32' + detached: process.platform != 'win32' } killPs = (ps) -> - if process.platform != "win32" - ps.kill "SIGINT" - else + if verbose + console.log "killing" + if process.platform == "win32" killer = spawn sh, [shArg, "taskkill /F /T /PID "+ps.pid] - spyOnPs killer, 3 + else + killer = spawn sh, [shArg, "kill -INT -"+ps.pid] + spyOnPs killer, 3 spyOnPs = (ps, verbosity=1) -> if verbose >= verbosity @@ -103,9 +103,6 @@ describe "parallelshell", -> ps.on "exit", () -> ps.exitCode.should.equal 1 done() - ps.on "close", () -> - ps.exitCode.should.equal 1 - done() it "should wait for sibling processes on child error when called with -w or --wait", (done) -> ps = spawnParallelshell(["-w",waitingProcess(),failingProcess].join(" ")) @@ -154,12 +151,9 @@ describe "parallelshell", -> testOutput("\"#{setString}node -e 'console.log(process.env.test);'\"", output) .then done .catch done - + it "should work with first", (done) -> ps = spawnParallelshell(["--first",waitingProcess(10),waitingProcess(10000)].join(" ")) - ps.on "close", () -> - ps.exitCode.should.equal 0 - done() ps.on "exit", () -> ps.exitCode.should.equal 0 done() @@ -167,4 +161,4 @@ describe "parallelshell", -> it "should not work with first and wait", (done) -> testOutput("--wait --first", ["--wait and --first cannot be used together"], "err") .then done - .catch done \ No newline at end of file + .catch done From 6d26a353a0a6554ccd1f08b73275e0bdb3ff971d Mon Sep 17 00:00:00 2001 From: Andrew Martin Date: Tue, 13 Oct 2015 15:19:53 +0100 Subject: [PATCH 5/5] Return exit code 0 on delayed child process error Now only kills running processes. with @fabiosantoscode --- index.js | 2 +- test/index.coffee | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 441a6a7..f3fd1aa 100755 --- a/index.js +++ b/index.js @@ -76,7 +76,7 @@ function close (code) { var i, len, closeHandler, closed = 0, opened = 0; for (i = 0, len = children.length; i < len; i++) { - if (!children[i].exitCode) { + if (children[i].exitCode === null) { opened++; children[i].removeAllListeners('close'); if (process.platform != "win32") { diff --git a/test/index.coffee b/test/index.coffee index aa114c4..0b433a9 100644 --- a/test/index.coffee +++ b/test/index.coffee @@ -16,6 +16,8 @@ else # children waitingProcess = (time=10000) -> return "\"node -e 'setTimeout(function(){},#{time});'\"" +waitingFailingProcess = (time=10000) -> + return "\"node -e 'setTimeout(function(){ throw new Error(); },#{time});'\"" failingProcess = "\"node -e 'throw new Error();'\"" usageInfo = """ @@ -86,6 +88,13 @@ describe "parallelshell", -> ps.exitCode.should.equal 1 done() + it "should close with exitCode 1 on delayed child error", (done) -> + ps = spawnParallelshell([waitingFailingProcess(100),waitingProcess(1),waitingProcess(500)].join(" ")) + spyOnPs ps, 2 + ps.on "exit", () -> + ps.exitCode.should.equal 1 + done() + it "should run with a normal child", (done) -> ps = spawnParallelshell(waitingProcess()) spyOnPs ps, 1 @@ -96,7 +105,6 @@ describe "parallelshell", -> killPs(ps) ),150 - it "should close sibling processes on child error", (done) -> ps = spawnParallelshell([waitingProcess(),failingProcess].join(" ")) spyOnPs ps,2