Skip to content

Support retries on failures #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tasks/loop-mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
73 changes: 51 additions & 22 deletions tasks/process-loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -113,4 +142,4 @@ module.exports = function exports (grunt, _spawn) {
});
}
};
};
};
38 changes: 37 additions & 1 deletion test/process-loop-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -150,4 +186,4 @@ describe("Process Loop", function () {
done()
})
})
})
})