You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
2.9 KiB
163 lines
2.9 KiB
|
|
/** |
|
* Module dependencies. |
|
*/ |
|
|
|
var EventEmitter = require('events').EventEmitter |
|
, debug = require('debug')('runnable'); |
|
|
|
/** |
|
* 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.timedOut = false; |
|
this.context = this; |
|
} |
|
|
|
/** |
|
* Inherit from `EventEmitter.prototype`. |
|
*/ |
|
|
|
Runnable.prototype.__proto__ = EventEmitter.prototype; |
|
|
|
/** |
|
* Set & get timeout `ms`. |
|
* |
|
* @param {Number} ms |
|
* @return {Runnable|Number} ms or self |
|
* @api private |
|
*/ |
|
|
|
Runnable.prototype.timeout = function(ms){ |
|
if (0 == arguments.length) return this._timeout; |
|
debug('timeout %d', ms); |
|
this._timeout = ms; |
|
if (this.timer) this.resetTimeout(); |
|
return this; |
|
}; |
|
|
|
/** |
|
* 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); |
|
}; |
|
|
|
/** |
|
* Reset the timeout. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Runnable.prototype.resetTimeout = function(){ |
|
var self = this |
|
, ms = this.timeout(); |
|
|
|
this.clearTimeout(); |
|
if (ms) { |
|
this.timer = setTimeout(function(){ |
|
self.callback(new Error('timeout of ' + ms + 'ms exceeded')); |
|
self.timedOut = true; |
|
}, ms); |
|
} |
|
}; |
|
|
|
/** |
|
* Run the test and invoke `fn(err)`. |
|
* |
|
* @param {Function} fn |
|
* @api private |
|
*/ |
|
|
|
Runnable.prototype.run = function(fn){ |
|
var self = this |
|
, ms = this.timeout() |
|
, start = new Date |
|
, ctx = this.context |
|
, finished |
|
, emitted; |
|
|
|
// timeout |
|
if (this.async) { |
|
if (ms) { |
|
this.timer = setTimeout(function(){ |
|
done(new Error('timeout of ' + ms + 'ms exceeded')); |
|
self.timedOut = true; |
|
}, ms); |
|
} |
|
} |
|
|
|
// called multiple times |
|
function multiple() { |
|
if (emitted) return; |
|
emitted = true; |
|
self.emit('error', new Error('done() called multiple times')); |
|
} |
|
|
|
// finished |
|
function done(err) { |
|
if (self.timedOut) return; |
|
if (finished) return multiple(); |
|
self.clearTimeout(); |
|
self.duration = new Date - start; |
|
finished = true; |
|
fn(err); |
|
} |
|
|
|
// for .resetTimeout() |
|
this.callback = done; |
|
|
|
// async |
|
if (this.async) { |
|
try { |
|
this.fn.call(ctx, function(err){ |
|
if (err instanceof Error) return done(err); |
|
if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); |
|
done(); |
|
}); |
|
} catch (err) { |
|
done(err); |
|
} |
|
return; |
|
} |
|
|
|
// sync |
|
try { |
|
if (!this.pending) this.fn.call(ctx); |
|
this.duration = new Date - start; |
|
fn(); |
|
} catch (err) { |
|
fn(err); |
|
} |
|
};
|
|
|