From 2b9aa94f3a4fd432f4fdbf82428d09d4b8f6d74a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 2 Nov 2017 16:55:44 +0000 Subject: [PATCH] Add rc property to exec node outputs 1 and 2 (#1401) * Add rc property to exec node outputs 1 and 2 to close #1399 * improve test coverage and add tests for new msg.rc * make spawn test slightly more robust to different environments * added debug for spawn test * let spawn error test be even more relaxed * don't necessarily clone msg.payload in exec node stderr as per suggestion --- nodes/core/core/75-exec.html | 8 ++ nodes/core/core/75-exec.js | 20 +-- test/nodes/core/core/75-exec_spec.js | 180 +++++++++++++++++---------- 3 files changed, 134 insertions(+), 74 deletions(-) diff --git a/nodes/core/core/75-exec.html b/nodes/core/core/75-exec.html index 0efa61058..713cd9699 100644 --- a/nodes/core/core/75-exec.html +++ b/nodes/core/core/75-exec.html @@ -75,12 +75,20 @@
payload string
the standard output of the command.
+
+
rc object
+
exec mode only, a copy of the return code object (also available on port 3)
+
  • Standard error
    payload string
    the standard error of the command.
    +
    +
    rc object
    +
    exec mode only, a copy of the return code object (also available on port 3)
    +
  • Return code
    diff --git a/nodes/core/core/75-exec.js b/nodes/core/core/75-exec.js index 8b10bec28..2638dd1da 100644 --- a/nodes/core/core/75-exec.js +++ b/nodes/core/core/75-exec.js @@ -125,14 +125,15 @@ module.exports = function(RED) { if (node.append.trim() !== "") { cl += " "+node.append; } /* istanbul ignore else */ if (RED.settings.verbose) { node.log(cl); } - child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) { + child = exec(cl, {encoding:'binary', maxBuffer:10000000}, function (error, stdout, stderr) { + var msg2, msg3; + delete msg.payload; + if (stderr) { + msg2 = RED.util.cloneMessage(msg); + msg2.payload = stderr; + } msg.payload = Buffer.from(stdout,"binary"); if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); } - var msg2 = null; - if (stderr) { - msg2 = {payload: stderr}; - } - var msg3 = null; node.status({}); //console.log('[exec] stdout: ' + stdout); //console.log('[exec] stderr: ' + stderr); @@ -142,10 +143,15 @@ module.exports = function(RED) { if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } node.log('error:' + error); - } else if (node.oldrc === "false") { + } + else if (node.oldrc === "false") { msg3 = {payload:{code:0}}; } if (!msg3) { node.status({}); } + else { + msg.rc = msg3.payload; + if (msg2) { msg2.rc = msg3.payload; } + } node.send([msg,msg2,msg3]); if (child.tout) { clearTimeout(child.tout); } delete node.activeProcesses[child.pid]; diff --git a/test/nodes/core/core/75-exec_spec.js b/test/nodes/core/core/75-exec_spec.js index 10a9f5fd3..c56647bee 100644 --- a/test/nodes/core/core/75-exec_spec.js +++ b/test/nodes/core/core/75-exec_spec.js @@ -76,11 +76,15 @@ describe('exec node', function() { msg.should.have.property("payload"); msg.payload.should.be.a.String(); msg.payload.should.equal("echo"); + msg.should.have.property("rc"); + msg.rc.should.have.property("code",0); msg = messages[1]; msg.should.have.property("payload"); msg.payload.should.be.a.String(); msg.payload.should.equal("ECHO"); + msg.should.have.property("rc"); + msg.rc.should.have.property("code",0); msg = messages[2]; msg.should.have.property("payload"); @@ -88,7 +92,8 @@ describe('exec node', function() { child_process.exec.restore(); done(); - } catch(err) { + } + catch(err) { child_process.exec.restore(); done(err); } @@ -136,20 +141,23 @@ describe('exec node', function() { msg.should.have.property("payload"); msg.payload.should.be.a.String(); msg.payload.should.equal("echo and more"); + msg.should.have.property("rc"); + msg.rc.should.have.property("code",0); msg = messages[1]; msg.should.have.property("payload"); msg.payload.should.be.a.String(); msg.payload.should.equal("ECHO AND MORE"); + msg.should.have.property("rc"); + msg.rc.should.have.property("code",0); child_process.exec.restore(); done(); - } catch(err) { + } + catch(err) { child_process.exec.restore(); done(err); } }; - - n2.on("input", function(msg) { messages[0] = msg; completeTest(); @@ -169,7 +177,7 @@ describe('exec node', function() { function(arg1, arg2, arg3, arg4) { //console.log(arg1); // arg3(error,stdout,stderr); - arg3("error",new Buffer([0x01,0x02,0x03,0x88])); + arg3("error",new Buffer([0x01,0x02,0x03,0x88]),new Buffer([0x01,0x02,0x03,0x88])); }); helper.load(execNode, flow, function() { var n1 = helper.getNode("n1"); @@ -199,33 +207,11 @@ describe('exec node', function() { // Although Windows timeout command is equivalent to sleep, this cannot be used because it promptly outputs a message. flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"0.3", oldrc:"false"}, {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; - } else { + } + else { flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"0.3", oldrc:"false"}, {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; } - helper.load(execNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var n3 = helper.getNode("n3"); - var n4 = helper.getNode("n4"); - n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("signal","SIGTERM"); - done(); - }); - n1.receive({}); - }); - }); - - it('should be able to kill a long running command', function(done) { - var flow; - if (osType === "Windows_NT") { - flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"}, - {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; - } else { - flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"}, - {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; - } helper.load(execNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -236,9 +222,42 @@ describe('exec node', function() { msg.should.have.property("payload"); msg.payload.should.have.property("signal","SIGTERM"); done(); - } catch(err) { - done(err); } + catch(err) { done(err); } + }); + n1.receive({}); + }); + }); + + it('should be able to kill a long running command', function(done) { + var flow; + if (osType === "Windows_NT") { + flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"}, + {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; + } + else { + flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"}, + {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; + } + helper.load(execNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + var n4 = helper.getNode("n4"); + n2.on("input", function(msg) { + try { + msg.should.have.property("rc"); + msg.rc.should.have.property("code",null); + msg.rc.should.have.property("signal","SIGTERM"); + } catch(err) { done(err); } + }); + n4.on("input", function(msg) { + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("signal","SIGTERM"); + done(); + } + catch(err) { done(err); } }); setTimeout(function() { n1.receive({kill:""}); @@ -262,14 +281,19 @@ describe('exec node', function() { var n2 = helper.getNode("n2"); var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); + n2.on("input", function(msg) { + try { + msg.should.have.property("rc"); + msg.rc.should.have.property("code",null); + msg.rc.should.have.property("signal","SIGINT"); + } catch(err) { done(err); } + }); n4.on("input", function(msg) { try { msg.should.have.property("payload"); msg.payload.should.have.property("signal",sig); done(); - } catch(err) { - done(err); - } + } catch(err) { done(err); } }); setTimeout(function() { n1.receive({kill:"SIGINT"}); @@ -278,7 +302,6 @@ describe('exec node', function() { }); }); - it('should return the rc for a failing command', function(done) { var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"error", addpay:false, append:"", oldrc:"false"}, {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; @@ -305,6 +328,9 @@ describe('exec node', function() { msg.should.have.property("payload"); msg.payload.should.be.a.String(); msg.payload.should.equal("error"); + msg.should.have.property("rc"); + msg.rc.should.have.property("code",1); + msg.rc.should.have.property("message",undefined); msg = messages[1]; msg.should.have.property("payload"); @@ -317,7 +343,8 @@ describe('exec node', function() { child_process.exec.restore(); done(); - } catch(err) { + } + catch(err) { child_process.exec.restore(); done(err); } @@ -369,9 +396,8 @@ describe('exec node', function() { msg.payload.should.be.a.String(); msg.payload.should.equal(expected); done(); - } catch(err) { - done(err); } + catch(err) { done(err); } }); n1.receive({payload:"hello world"}); }); @@ -397,10 +423,13 @@ describe('exec node', function() { var n4 = helper.getNode("n4"); n2.on("input", function(msg) { //console.log(msg); - msg.should.have.property("payload"); - msg.payload.should.be.a.String(); - msg.payload.should.equal(expected); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.be.a.String(); + msg.payload.should.equal(expected); + done(); + } + catch(err) { done(err); } }); n1.receive({payload:12345}); }); @@ -431,9 +460,8 @@ describe('exec node', function() { msg.payload.length.should.equal(7); } done(); - } catch(err) { - done(err); } + catch(err) { done(err); } }); n1.receive({payload:new Buffer([0x01,0x02,0x03,0x88])}); }); @@ -474,12 +502,12 @@ describe('exec node', function() { should.exist(msg.payload); msg.payload.should.have.property("code",0); done(); - } catch(err) { + } + catch(err) { done(err); } }; - n2.on("input", function(msg) { messages[0] = msg; completeTest(); @@ -501,10 +529,13 @@ describe('exec node', function() { var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("code"); - msg.payload.code.should.be.below(0); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("code"); + msg.payload.code.should.be.below(0); + done(); + } + catch(err) { done(err); } }); n1.receive({payload:null}); }); @@ -521,7 +552,7 @@ describe('exec node', function() { } else { flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"mkdir /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"}, {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}]; - expected = "mkdir: /foo/bar/doo: No such file or directory\n"; + expected = ' directory'; } helper.load(execNode, flow, function() { var n1 = helper.getNode("n1"); @@ -529,14 +560,20 @@ describe('exec node', function() { var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); n3.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.be.a.String(); - msg.payload.should.equal(expected); + try { + msg.should.have.property("payload"); + msg.payload.should.be.a.String(); + msg.payload.indexOf(expected).should.not.be.equal(-1); + } + catch(err) { done(err); } }); n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("code",1); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("code",1); + done(); + } + catch(err) { done(err); } }); n1.receive({payload:null}); }); @@ -557,10 +594,13 @@ describe('exec node', function() { var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("code",null); - msg.payload.should.have.property("signal","SIGTERM"); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("code",null); + msg.payload.should.have.property("signal","SIGTERM"); + done(); + } + catch(err) { done(err); } }); n1.receive({}); }); @@ -581,9 +621,12 @@ describe('exec node', function() { var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("signal","SIGTERM"); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("signal","SIGTERM"); + done(); + } + catch(err) { done(err); } }); setTimeout(function() { n1.receive({kill:""}); @@ -608,9 +651,12 @@ describe('exec node', function() { var n3 = helper.getNode("n3"); var n4 = helper.getNode("n4"); n4.on("input", function(msg) { - msg.should.have.property("payload"); - msg.payload.should.have.property("signal",sig); - done(); + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("signal",sig); + done(); + } + catch(err) { done(err); } }); setTimeout(function() { n1.receive({kill:"SIGINT"});