js-algorithms/node_modules/mocha/lib/runnable.js
2015-07-24 11:14:18 +04:30

281 lines
6.0 KiB
JavaScript

/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter
, debug = require('debug')('mocha:runnable')
, Pending = require('./pending')
, milliseconds = require('./ms')
, utils = require('./utils');
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
var Date = global.Date
, setTimeout = global.setTimeout
, setInterval = global.setInterval
, clearTimeout = global.clearTimeout
, clearInterval = global.clearInterval;
/**
* Object#toString().
*/
var toString = Object.prototype.toString;
/**
* Expose `Runnable`.
*/
module.exports = Runnable;
/**
* Initialize a new `Runnable` with the given `title` and callback `fn`.
*
* @param {String} title
* @param {Function} fn
* @api private
*/
function Runnable(title, fn) {
this.title = title;
this.fn = fn;
this.async = fn && fn.length;
this.sync = ! this.async;
this._timeout = 2000;
this._slow = 75;
this._enableTimeouts = true;
this.timedOut = false;
this._trace = new Error('done() called multiple times')
}
/**
* Inherit from `EventEmitter.prototype`.
*/
Runnable.prototype.__proto__ = EventEmitter.prototype;
/**
* Set & get timeout `ms`.
*
* @param {Number|String} ms
* @return {Runnable|Number} ms or self
* @api private
*/
Runnable.prototype.timeout = function(ms){
if (0 == arguments.length) return this._timeout;
if (ms === 0) this._enableTimeouts = false;
if ('string' == typeof ms) ms = milliseconds(ms);
debug('timeout %d', ms);
this._timeout = ms;
if (this.timer) this.resetTimeout();
return this;
};
/**
* Set & get slow `ms`.
*
* @param {Number|String} ms
* @return {Runnable|Number} ms or self
* @api private
*/
Runnable.prototype.slow = function(ms){
if (0 === arguments.length) return this._slow;
if ('string' == typeof ms) ms = milliseconds(ms);
debug('timeout %d', ms);
this._slow = ms;
return this;
};
/**
* Set and & get timeout `enabled`.
*
* @param {Boolean} enabled
* @return {Runnable|Boolean} enabled or self
* @api private
*/
Runnable.prototype.enableTimeouts = function(enabled){
if (arguments.length === 0) return this._enableTimeouts;
debug('enableTimeouts %s', enabled);
this._enableTimeouts = enabled;
return this;
};
/**
* Halt and mark as pending.
*
* @api private
*/
Runnable.prototype.skip = function(){
throw new Pending();
};
/**
* Return the full title generated by recursively
* concatenating the parent's full title.
*
* @return {String}
* @api public
*/
Runnable.prototype.fullTitle = function(){
return this.parent.fullTitle() + ' ' + this.title;
};
/**
* Clear the timeout.
*
* @api private
*/
Runnable.prototype.clearTimeout = function(){
clearTimeout(this.timer);
};
/**
* Inspect the runnable void of private properties.
*
* @return {String}
* @api private
*/
Runnable.prototype.inspect = function(){
return JSON.stringify(this, function(key, val){
if ('_' == key[0]) return;
if ('parent' == key) return '#<Suite>';
if ('ctx' == key) return '#<Context>';
return val;
}, 2);
};
/**
* Reset the timeout.
*
* @api private
*/
Runnable.prototype.resetTimeout = function(){
var self = this;
var ms = this.timeout() || 1e9;
if (!this._enableTimeouts) return;
this.clearTimeout();
this.timer = setTimeout(function(){
if (!self._enableTimeouts) return;
self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
self.timedOut = true;
}, ms);
};
/**
* Whitelist these globals for this test run
*
* @api private
*/
Runnable.prototype.globals = function(arr){
var self = this;
this._allowedGlobals = arr;
};
/**
* Run the test and invoke `fn(err)`.
*
* @param {Function} fn
* @api private
*/
Runnable.prototype.run = function(fn){
var self = this
, start = new Date
, ctx = this.ctx
, finished
, emitted;
// Some times the ctx exists but it is not runnable
if (ctx && ctx.runnable) ctx.runnable(this);
// called multiple times
function multiple(err) {
if (emitted) return;
emitted = true;
self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
}
// finished
function done(err) {
var ms = self.timeout();
if (self.timedOut) return;
if (finished) return multiple(err || self._trace);
// Discard the resolution if this test has already failed asynchronously
if (self.state) return;
self.clearTimeout();
self.duration = new Date - start;
finished = true;
if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
fn(err);
}
// for .resetTimeout()
this.callback = done;
// explicit async with `done` argument
if (this.async) {
this.resetTimeout();
try {
this.fn.call(ctx, function(err){
if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
if (null != err) {
if (Object.prototype.toString.call(err) === '[object Object]') {
return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
} else {
return done(new Error('done() invoked with non-Error: ' + err));
}
}
done();
});
} catch (err) {
done(utils.getError(err));
}
return;
}
if (this.asyncOnly) {
return done(new Error('--async-only option in use without declaring `done()`'));
}
// sync or promise-returning
try {
if (this.pending) {
done();
} else {
callFn(this.fn);
}
} catch (err) {
done(utils.getError(err));
}
function callFn(fn) {
var result = fn.call(ctx);
if (result && typeof result.then === 'function') {
self.resetTimeout();
result
.then(function() {
done()
},
function(reason) {
done(reason || new Error('Promise rejected with no or falsy reason'))
});
} else {
done();
}
}
};