/** * 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); } };