Skip to content
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

Allow to retry failed test from test context for #1773 #1989

Merged
merged 1 commit into from
Dec 28, 2015
Merged
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
5 changes: 5 additions & 0 deletions bin/_mocha
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ program
.option('--prof', 'log statistical profiling information')
.option('--recursive', 'include sub directories')
.option('--reporters', 'display available reporters')
.option('--retries <times>', 'set numbers of time to retry a failed test case')
.option('--throw-deprecation', 'throw an exception anytime a deprecated function is used')
.option('--trace', 'trace function calls')
.option('--trace-deprecation', 'show stack traces on deprecations')
Expand Down Expand Up @@ -284,6 +285,10 @@ if (program.delay) mocha.delay();

mocha.globals(globals);

// --retries

if (program.retries) mocha.suite.retries(program.retries);

// custom compiler support

var extensions = ['js'];
Expand Down
15 changes: 15 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ Context.prototype.skip = function() {
return this;
};

/**
* Allow a number of retries on failed tests
*
* @api private
* @param {number} n
* @return {Context} self
*/
Context.prototype.retries = function(n) {
if (!arguments.length) {
return this.runnable().retries();
}
this.runnable().retries(n);
return this;
};

/**
* Inspect the context void of `._runnable`.
*
Expand Down
7 changes: 7 additions & 0 deletions lib/interfaces/bdd.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,12 @@ module.exports = function(suite) {
context.xit = context.xspecify = context.it.skip = function(title) {
context.it(title);
};

/**
* Number of attempts to retry.
*/
context.it.retries = function(n) {
context.retries(n);
};
});
};
9 changes: 9 additions & 0 deletions lib/interfaces/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ module.exports = function(suites, context) {
*/
skip: function(title) {
context.test(title);
},

/**
* Number of retry attempts
*
* @param {string} n
*/
retries: function(n) {
context.retries(n);
}
}
};
Expand Down
1 change: 1 addition & 0 deletions lib/interfaces/qunit.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,6 @@ module.exports = function(suite) {
};

context.test.skip = common.test.skip;
context.test.retries = common.test.retries;
});
};
1 change: 1 addition & 0 deletions lib/interfaces/tdd.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,6 @@ module.exports = function(suite) {
};

context.test.skip = common.test.skip;
context.test.retries = common.test.retries;
});
};
16 changes: 16 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function image(name) {
* - `reporter` reporter instance, defaults to `mocha.reporters.spec`
* - `globals` array of accepted globals
* - `timeout` timeout in milliseconds
* - `retries` number of times to retry failed tests
* - `bail` bail on the first test failure
* - `slow` milliseconds to wait before considering a test slow
* - `ignoreLeaks` ignore global leaks
Expand All @@ -88,6 +89,9 @@ function Mocha(options) {
if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
this.timeout(options.timeout);
}
if (typeof options.retries !== 'undefined' && options.retries !== null) {
this.retries(options.retries);
}
this.useColors(options.useColors);
if (options.enableTimeouts !== null) {
this.enableTimeouts(options.enableTimeouts);
Expand Down Expand Up @@ -372,6 +376,18 @@ Mocha.prototype.timeout = function(timeout) {
return this;
};

/**
* Set the number of times to retry failed tests.
*
* @param {Number} retry times
* @return {Mocha}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate annotations here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I fixed that, but turns out there're actually a bunch of other methods w/ dupe annotations (timeout, enableTimeout...)

* @api public
*/
Mocha.prototype.retries = function(n) {
this.suite.retries(n);
return this;
};

/**
* Set slowness threshold in milliseconds.
*
Expand Down
1 change: 1 addition & 0 deletions lib/reporters/json-cov.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function coverage(filename, data) {
function clean(test) {
return {
duration: test.duration,
currentRetry: test.currentRetry(),
fullTitle: test.fullTitle(),
title: test.title
};
Expand Down
3 changes: 2 additions & 1 deletion lib/reporters/json-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function clean(test) {
return {
title: test.title,
fullTitle: test.fullTitle(),
duration: test.duration
duration: test.duration,
currentRetry: test.currentRetry()
};
}
1 change: 1 addition & 0 deletions lib/reporters/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function clean(test) {
title: test.title,
fullTitle: test.fullTitle(),
duration: test.duration,
currentRetry: test.currentRetry(),
err: errorJSON(test.err || {})
};
}
Expand Down
26 changes: 26 additions & 0 deletions lib/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function Runnable(title, fn) {
this._enableTimeouts = true;
this.timedOut = false;
this._trace = new Error('done() called multiple times');
this._retries = -1;
this._currentRetry = 0;
}

/**
Expand Down Expand Up @@ -128,6 +130,30 @@ Runnable.prototype.skip = function() {
throw new Pending();
};

/**
* Set number of retries.
*
* @api private
*/
Runnable.prototype.retries = function(n) {
if (!arguments.length) {
return this._retries;
}
this._retries = n;
};

/**
* Get current retry
*
* @api private
*/
Runnable.prototype.currentRetry = function(n) {
if (!arguments.length) {
return this._currentRetry;
}
this._currentRetry = n;
};

/**
* Return the full title generated by recursively concatenating the parent's
* full title.
Expand Down
8 changes: 8 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,16 @@ Runner.prototype.runTests = function(suite, fn) {
test = self.test;

if (err) {
var retry = test.currentRetry();
if (err instanceof Pending) {
self.emit('pending', test);
} else if (retry < test.retries()) {
test.currentRetry(retry + 1);
tests.unshift(test);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add more tests to the test suite?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

up to the collaborators I guess.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given var tests = suite.tests.slice();, adding it to tests is a fine approach. As long as suite.tests isn't being modified :)


// Early return + hook trigger so that it doesn't
// increment the count wrong
return self.hookUp('afterEach', next);
} else {
self.fail(test, err);
}
Expand Down
24 changes: 24 additions & 0 deletions lib/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function Suite(title, parentContext) {
this._enableTimeouts = true;
this._slow = 75;
this._bail = false;
this._retries = -1;
this.delayed = false;
}

Expand All @@ -79,6 +80,7 @@ Suite.prototype.clone = function() {
debug('clone');
suite.ctx = this.ctx;
suite.timeout(this.timeout());
suite.retries(this.retries());
suite.enableTimeouts(this.enableTimeouts());
suite.slow(this.slow());
suite.bail(this.bail());
Expand Down Expand Up @@ -107,6 +109,22 @@ Suite.prototype.timeout = function(ms) {
return this;
};

/**
* Set number of times to retry a failed test.
*
* @api private
* @param {number|string} n
* @return {Suite|number} for chaining
*/
Suite.prototype.retries = function(n) {
if (!arguments.length) {
return this._retries;
}
debug('retries %d', n);
this._retries = parseInt(n, 10) || 0;
return this;
};

/**
* Set timeout to `enabled`.
*
Expand Down Expand Up @@ -179,6 +197,7 @@ Suite.prototype.beforeAll = function(title, fn) {
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.retries(this.retries());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
Expand Down Expand Up @@ -208,6 +227,7 @@ Suite.prototype.afterAll = function(title, fn) {
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.retries(this.retries());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
Expand Down Expand Up @@ -237,6 +257,7 @@ Suite.prototype.beforeEach = function(title, fn) {
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.retries(this.retries());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
Expand Down Expand Up @@ -266,6 +287,7 @@ Suite.prototype.afterEach = function(title, fn) {
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.retries(this.retries());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
Expand All @@ -284,6 +306,7 @@ Suite.prototype.afterEach = function(title, fn) {
Suite.prototype.addSuite = function(suite) {
suite.parent = this;
suite.timeout(this.timeout());
suite.retries(this.retries());
suite.enableTimeouts(this.enableTimeouts());
suite.slow(this.slow());
suite.bail(this.bail());
Expand All @@ -302,6 +325,7 @@ Suite.prototype.addSuite = function(suite) {
Suite.prototype.addTest = function(test) {
test.parent = this;
test.timeout(this.timeout());
test.retries(this.retries());
test.enableTimeouts(this.enableTimeouts());
test.slow(this.slow());
test.ctx = this.ctx;
Expand Down
5 changes: 5 additions & 0 deletions test/integration/fixtures/options/retries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('retries', function() {
it('should fail', function () {
throw new Error('retry failure');
});
});
11 changes: 11 additions & 0 deletions test/integration/fixtures/retries/early-pass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
describe('retries', function() {
this.retries(1);
var times = 0;

it('should pass after 1 retry', function() {
times++;
if (times !== 2) {
throw new Error('retry error ' + times);
}
});
});
25 changes: 25 additions & 0 deletions test/integration/fixtures/retries/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
describe('retries', function() {
var times = 0;
before(function () {
console.log('before');
});

after(function () {
console.log('after');
});

beforeEach(function() {
console.log('before each', times);
});

afterEach(function () {
console.log('after each', times);
});

it('should allow override and run appropriate hooks', function(){
this.retries(4);
console.log('TEST', times);
times++;
throw new Error('retry error');
});
});
9 changes: 9 additions & 0 deletions test/integration/fixtures/retries/nested.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
describe('retries', function() {
this.retries(3);
describe('nested', function () {
it('should fail after only 1 retry', function(){
this.retries(1);
throw new Error('retry error');
});
});
});
16 changes: 16 additions & 0 deletions test/integration/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,20 @@ describe('options', function() {
});
});
});

describe('--retries', function() {
it('retries after a certain threshold', function (done) {
args = ['--retries', '3'];
run('options/retries.js', args, function(err, res) {
assert(!err);
assert.equal(res.stats.pending, 0);
assert.equal(res.stats.passes, 0);
assert.equal(res.stats.tests, 1);
assert.equal(res.tests[0].currentRetry, 3);
assert.equal(res.stats.failures, 1);
assert.equal(res.code, 1);
done();
});
})
});
});
Loading