diff --git a/README.md b/README.md index 89fd343..89f34c8 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ any supported mocha command line argument is accepted. * parallel (optional: defaults to false): To engage a parallel testing ability, specify parallel.type = "file|directory". Optionally specify parallel.limit to limit the concurrent running processes * reportLocation (required if using xunit-file reporter): specify where xunit report files should be written. Note: if you are using "xunit-file" as your reporter, you need to add it to your package.json * noFail (optional: defaults to false): If true, the task will exit as zero regardless of any mocha test failures +* retries (optional: defaults to 0): To re-run mocha if there is any test failure ### iterations options Array of JSON objects. mocha will loop for each item, using its properties for the mocha run diff --git a/tasks/loop-mocha.js b/tasks/loop-mocha.js index dd5e3fa..dd5a75b 100644 --- a/tasks/loop-mocha.js +++ b/tasks/loop-mocha.js @@ -123,7 +123,7 @@ module.exports = function (grunt) { // spill off the processes. This can be quite a few. // the results will be in the form // [[returnCode, itterationName], ...] - require('./process-loop.js')(grunt)({ + require('./process-loop.js')(grunt, null, loopOptions)({ filesSrc: filesSrc, mocha_path: mocha_path, reportLocation: reportLocation, localopts: localopts, localOtherOptionsStringified: localOtherOptionsStringified, itLabel: itLabel, localMochaOptions: localMochaOptions, loopOptions: loopOptions } , cb); diff --git a/tasks/process-loop.js b/tasks/process-loop.js index ba8f29e..924c802 100644 --- a/tasks/process-loop.js +++ b/tasks/process-loop.js @@ -19,6 +19,16 @@ module.exports = function exports (grunt, _spawn) { } // Give us a function that will work with async return function processLoop(op, cb) { + // rip up the op into vars + var filesSrc = op.filesSrc + , mocha_path = op.mocha_path + , reportLocation = op.reportLocation + , localopts = op.localopts + , localOtherOptionsStringified = op.localOtherOptionsStringified + , itLabel = op.itLabel + , localMochaOptions = op.localMochaOptions + , loopOptions = op.loopOptions; + function work(_itLabel, _filesSrc, _env, _op, _cb) { // inform the world that we are going to start @@ -39,31 +49,50 @@ module.exports = function exports (grunt, _spawn) { // more notify grunt.log.writeln("[grunt-loop-mocha] mocha argv: ", _op.toString()); - // start a process - var child = spawn(mocha_path - , _op - , {env: _env}); + var retries = parseInt(loopOptions.retries, 10) || 0; - // pipe the output (in paralell this is going to be noisey) - child.stdout.pipe(process.stdout); - child.stderr.pipe(process.stderr); + function spawnProcess() { + // start a process + var child = spawn(mocha_path, _op, {env: _env}); - // report back the outcome - child.on('close', function (code) { - _cb(null, [code, _itLabel]); - }); + // pipe the output (in paralell this is going to be noisey) + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + + // report back the outcome + child.on('close', function (code) { + // trigger callback when there is no error or + // when retry is not required + if (!code || retries < 0) { + _cb(null, [code, _itLabel]); + } + }); + retries--; + return child; + } + + function retryIfRequired(proc) { + proc.on('exit', function (code, signal) { + if (code && retries > -1) { + setTimeout(function () { + var child = spawnProcess(); + retryIfRequired(child); + }, 1000); + } else { + process.on('exit', function () { + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code); + } + }); + } + }); + } + + retryIfRequired(spawnProcess()); } - // rip up the op into vars - var filesSrc = op.filesSrc - , mocha_path = op.mocha_path - , reportLocation = op.reportLocation - , localopts = op.localopts - , localOtherOptionsStringified = op.localOtherOptionsStringified - , itLabel = op.itLabel - , localMochaOptions = op.localMochaOptions - , loopOptions = op.loopOptions; - console.log('loopOptions', loopOptions); var limit = (loopOptions.parallel && loopOptions.parallel.limit) ? loopOptions.parallel.limit : 5; var parallelType = (loopOptions.parallel && loopOptions.parallel.type) ? loopOptions.parallel.type : "none"; var env = _.merge(process.env, localOtherOptionsStringified); @@ -113,4 +142,4 @@ module.exports = function exports (grunt, _spawn) { }); } }; -}; \ No newline at end of file +}; diff --git a/test/process-loop-test.js b/test/process-loop-test.js index 3147300..93c3346 100644 --- a/test/process-loop-test.js +++ b/test/process-loop-test.js @@ -46,6 +46,42 @@ describe("Process Loop", function () { }) }) + it("should call @spawnTwice@ if retries is set to 1", function (done) { + var spawnCount = 0; + + var testMe = process({log: {writeln: noop}} + , function (path, op, env) { + spawnCount++; + + // We need to hand some stuff back... + var ee = new EventEmitter() + ee.stdout = {pipe: noop} + ee.stderr = {pipe: noop} + setTimeout(function () { + var code = 0; + if (spawnCount == 1) { + // Return error code on first attempt + code = 1; + } + + ee.emit('exit', code) + ee.emit('close', code) + }, 1) + return ee + }) + + testMe({filesSrc: ['one'], localopts: [], itLabel: 'Test Label', localMochaOptions: {}, + loopOptions: {retries: 1}}, function (err, results) { + expect(spawnCount) + .to.equal(2) + expect(results) + .to.deep.equal([ + [ 0, 'Test Label' ] + ]) + done() + }) + }) + it("should call spawn @onceEachFile@", function (done) { var testMe = process({log: {writeln: noop}} , function (path, op, env) { @@ -150,4 +186,4 @@ describe("Process Loop", function () { done() }) }) -}) \ No newline at end of file +})