Skip to content
Permalink
Browse files

Merge pull request #215 from Marsup/context-timeout

Add optional timeouts on before(Each), after(Each)
  • Loading branch information
geek committed Sep 24, 2014
2 parents fa8034f + 8c0300d commit 7770c600017920636628496176fd7c914879bec0
Showing with 137 additions and 27 deletions.
  1. +10 −0 README.md
  2. +6 −1 lib/cli.js
  3. +24 −8 lib/index.js
  4. +24 −8 lib/runner.js
  5. +73 −10 test/reporters.js
@@ -27,6 +27,7 @@ global manipulation. Our goal with **lab** is to keep the execution engine as si
- `-I`, `--ignore` - ignore a list of globals for the leak detection (comma separated)
- `-l`, `--leaks` - disables global variable leak detection.
- `-m`, `--timeout` - individual tests timeout in milliseconds (zero disables timeout). Defaults to 2 seconds.
- `-M`, `--context-timeout` - default timeouts for before, after, beforeEach and afterEach in milliseconds. Disabled by default.
- `-o`, `--output` - file to write the report to, otherwise sent to stdout.
- `-p`, `--parallel` - sets parallel execution as default test option. Defaults to serial execution.
- `-r`, `--reporter` - the reporter used to generate the test results. Defaults to `console`. Options are:
@@ -120,9 +121,18 @@ Both `test()` and `experiment()` accept an optional `options` argument which mus
- `skip` - skip execution. Cannot be overridden in children once parent is set to skip.
- `only` - marks all other tests or experiments with `skip`. This doesn't mark all other experiments and tests in a suite of scripts as skipped, instead it works within a single test script.

`before()`, `after()`, `beforeEach()`, `afterEach()` accept an optional `options` argument which must be an object with the following optional keys:
- `timeout` - set a specific timeout in milliseconds. Disabled by default or the value of `-M`.

```javascript
lab.experiment('math', { timeout: 1000 }, function () {
lab.before({ timeout: 500 }, function (done) {
doSomething();
done();
});
lab.test('returns true when 1 + 1 equals 2', { parallel: true }, function (done) {
Lab.expect(1+1).to.equal(2);
@@ -121,6 +121,11 @@ internals.options = function () {
default: null,
description: 'enable color output (defaults to terminal capabilities)'
},
'context-timeout': {
alias: 'M',
type: 'number',
description: 'timeout for before, after, beforeEach, afterEach in milliseconds'
},
dry: {
alias: 'd',
type: 'boolean',
@@ -223,7 +228,7 @@ internals.options = function () {
paths: argv._ ? [].concat(argv._) : ['test']
};

var keys = ['coverage', 'colors', 'dry', 'environment', 'flat', 'grep', 'globals', 'timeout', 'parallel', 'reporter', 'threshold'];
var keys = ['coverage', 'colors', 'dry', 'environment', 'flat', 'grep', 'globals', 'timeout', 'parallel', 'reporter', 'threshold', 'context-timeout'];
for (var i = 0, il = keys.length; i < il; ++i) {
if (argv.hasOwnProperty(keys[i]) && argv[keys[i]] !== undefined) {
options[keys[i]] = argv[keys[i]];
@@ -143,47 +143,63 @@ internals.experiment = function (title /*, options, fn */) {
};


internals.before = function (fn) {
internals.before = function (/* options, */ fn) {

var options = arguments.length === 2 ? arguments[0] : {};
fn = arguments.length === 2 ? arguments[1] : fn;

var before = {
title: 'Before ' + this._titles.join(' '),
fn: fn
fn: fn,
options: options
};

this._current.befores = this._current.befores || [];
this._current.befores.push(before);
};


internals.after = function (fn) {
internals.after = function (/* options, */ fn) {

var options = arguments.length === 2 ? arguments[0] : {};
fn = arguments.length === 2 ? arguments[1] : fn;

var after = {
title: 'After ' + this._titles.join(' '),
fn: fn
fn: fn,
options: options
};

this._current.afters = this._current.afters || [];
this._current.afters.push(after);
};


internals.beforeEach = function (fn) {
internals.beforeEach = function (/* options, */ fn) {

var options = arguments.length === 2 ? arguments[0] : {};
fn = arguments.length === 2 ? arguments[1] : fn;

var beforeEach = {
title: 'Before each ' + this._titles.join(' '),
fn: fn
fn: fn,
options: options
};

this._current.beforeEaches = this._current.beforeEaches || [];
this._current.beforeEaches.push(beforeEach);
};


internals.afterEach = function (fn) {
internals.afterEach = function (/* options, */ fn) {

var options = arguments.length === 2 ? arguments[0] : {};
fn = arguments.length === 2 ? arguments[1] : fn;

var afterEach = {
title: 'After each ' + this._titles.join(' '),
fn: fn
fn: fn,
options: options
};

this._current.afterEaches = this._current.afterEaches || [];
@@ -129,7 +129,7 @@ internals.executeExperiments = function (experiments, state, skip, callback) {
return next();
}

internals.executeDeps(experiment.befores, function (err) {
internals.executeDeps(experiment.befores, state, function (err) {

if (err) {
internals.fail([experiment], state, skip, '\'before\' action failed');
@@ -158,10 +158,10 @@ internals.executeExperiments = function (experiments, state, skip, callback) {
return next();
}

internals.executeDeps(experiment.afters, next);
internals.executeDeps(experiment.afters, state, next);
}
];

Items.serial(steps, function (item, next) {

item(next);
@@ -182,15 +182,31 @@ internals.executeExperiments = function (experiments, state, skip, callback) {
};


internals.executeDeps = function (deps, callback) {
internals.executeDeps = function (deps, state, callback) {

if (!deps) {
return callback();
}

Items.serial(deps, function (dep, next) {

dep.fn.call(null, next);
var finish = function (err) {

clearTimeout(timeoutId);
next(err);
};

var timeout = dep.options['timeout'] || state.options['context-timeout'];
if (timeout) {
var timeoutId = setTimeout(function () {

var error = new Error('Timed out (' + timeout + 'ms) - ' + dep.title);
error.timeout = true;
finish(error);
});
}

dep.fn.call(null, finish);
}, callback);
};

@@ -238,7 +254,7 @@ internals.executeTests = function (experiment, state, skip, callback) {

// Before each

internals.executeDeps(befores, function (err) {
internals.executeDeps(befores, state, function (err) {

if (err) {
internals.failTest(test, state, skip, '\'before each\' action failed');
@@ -282,10 +298,10 @@ internals.executeTests = function (experiment, state, skip, callback) {

// After each

return internals.executeDeps(afters, next);
return internals.executeDeps(afters, state, next);
}
];

Items.serial(steps, function (item, next) {

item(next);
@@ -263,21 +263,84 @@ describe('Reporter', function () {
});
});

it('generates a report with timeout', function (done) {
describe('timeouts', function () {

var script = Lab.script();
script.experiment('test', function () {
it('generates a report with timeout', function (done) {

script.test('works', function (finished) { });
var script = Lab.script();
script.experiment('test', function () {

script.test('works', function (finished) { });
});

Lab.report(script, { reporter: 'console', colors: false, timeout: 1 }, function (err, code, output) {

expect(err).to.not.exist;
expect(code).to.equal(1);
var result = output.replace(/\/[\/\w]+\.js\:\d+\:\d+/g, '<trace>');
expect(result).to.match(/^\n \n x\n\nFailed tests:\n\n 1\) test works:\n\n Error: Timed out \(\d+ms\)\n\n\n\n1 of 1 tests failed\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/);
done();
});
});

Lab.report(script, { reporter: 'console', colors: false, timeout: 1 }, function (err, code, output) {
var tests = [
{
type: 'before',
expect: /^\n \n x\n\nTest script errors:\n\nTimed out \(1ms\) - Before test\n at null\.<anonymous> \(<trace>\)\n at Timer\.listOnTimeout \[as ontimeout\] \(<trace>\)\n\nThere were 1 test script error\(s\)\.\n\nFailed tests:\n\n 1\) test works:\n\n \n\n \n\n\n1 of 1 tests failed\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/
},
{
type: 'after',
expect: /^\n \n \.\n\nTest script errors:\n\nTimed out \(1ms\) - After test\n at null\.<anonymous> \(<trace>\)\n at Timer\.listOnTimeout \[as ontimeout\] \(<trace>\)\n\nThere were 1 test script error\(s\)\.\n\n1 tests complete\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/
},
{
type: 'beforeEach',
expect: /^\n \n x\n\nTest script errors:\n\nTimed out \(1ms\) - Before each test\n at null\.<anonymous> \(<trace>\)\n at Timer\.listOnTimeout \[as ontimeout\] \(<trace>\)\n\nThere were 1 test script error\(s\)\.\n\nFailed tests:\n\n 1\) test works:\n\n \n\n \n\n\n1 of 1 tests failed\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/
},
{
type: 'afterEach',
expect: /^\n \n \.\n\nTest script errors:\n\nTimed out \(1ms\) - After each test\n at null\.<anonymous> \(<trace>\)\n at Timer\.listOnTimeout \[as ontimeout\] \(<trace>\)\n\nThere were 1 test script error\(s\)\.\n\n1 tests complete\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/
}
];

expect(err).to.not.exist;
expect(code).to.equal(1);
var result = output.replace(/\/[\/\w]+\.js\:\d+\:\d+/g, '<trace>');
expect(result).to.match(/^\n \n x\n\nFailed tests:\n\n 1\) test works:\n\n Error: Timed out \(\d+ms\)\n\n\n\n1 of 1 tests failed\nTest duration: \d+ ms\nNo global variable leaks detected\n\n$/);
done();
tests.forEach(function (test) {

it('generates a report with timeout on ' + test.type, function (done) {

var script = Lab.script();
script.experiment('test', function () {

script[test.type](function (finished) { });
script.test('works', function (finished) { finished() });
});

Lab.report(script, { reporter: 'console', colors: false, 'context-timeout': 1 }, function (err, code, output) {

expect(err).to.not.exist;
expect(code).to.equal(1);
var result = output.replace(/\/?[\/\w]+\.js\:\d+\:\d+/g, '<trace>');
expect(result).to.match(test.expect);
done();
});
});

it('generates a report with inline timeout on ' + test.type, function (done) {

var script = Lab.script();
script.experiment('test', function () {

script[test.type]({ timeout: 1 }, function (finished) { });
script.test('works', function (finished) { finished() });
});

Lab.report(script, { reporter: 'console', colors: false }, function (err, code, output) {

expect(err).to.not.exist;
expect(code).to.equal(1);
var result = output.replace(/\/?[\/\w]+\.js\:\d+\:\d+/g, '<trace>');
expect(result).to.match(test.expect);
done();
});
});
});
});

0 comments on commit 7770c60

Please sign in to comment.
You can’t perform that action at this time.