[484] | 1 | |
---|
| 2 | /** |
---|
| 3 | * Module dependencies. |
---|
| 4 | */ |
---|
| 5 | |
---|
| 6 | var EventEmitter = require('events').EventEmitter |
---|
| 7 | , debug = require('debug')('mocha:runnable') |
---|
| 8 | , milliseconds = require('./ms'); |
---|
| 9 | |
---|
| 10 | /** |
---|
| 11 | * Save timer references to avoid Sinon interfering (see GH-237). |
---|
| 12 | */ |
---|
| 13 | |
---|
| 14 | var Date = global.Date |
---|
| 15 | , setTimeout = global.setTimeout |
---|
| 16 | , setInterval = global.setInterval |
---|
| 17 | , clearTimeout = global.clearTimeout |
---|
| 18 | , clearInterval = global.clearInterval; |
---|
| 19 | |
---|
| 20 | /** |
---|
| 21 | * Object#toString(). |
---|
| 22 | */ |
---|
| 23 | |
---|
| 24 | var toString = Object.prototype.toString; |
---|
| 25 | |
---|
| 26 | /** |
---|
| 27 | * Expose `Runnable`. |
---|
| 28 | */ |
---|
| 29 | |
---|
| 30 | module.exports = Runnable; |
---|
| 31 | |
---|
| 32 | /** |
---|
| 33 | * Initialize a new `Runnable` with the given `title` and callback `fn`. |
---|
| 34 | * |
---|
| 35 | * @param {String} title |
---|
| 36 | * @param {Function} fn |
---|
| 37 | * @api private |
---|
| 38 | */ |
---|
| 39 | |
---|
| 40 | function Runnable(title, fn) { |
---|
| 41 | this.title = title; |
---|
| 42 | this.fn = fn; |
---|
| 43 | this.async = fn && fn.length; |
---|
| 44 | this.sync = ! this.async; |
---|
| 45 | this._timeout = 2000; |
---|
| 46 | this._slow = 75; |
---|
| 47 | this.timedOut = false; |
---|
| 48 | } |
---|
| 49 | |
---|
| 50 | /** |
---|
| 51 | * Inherit from `EventEmitter.prototype`. |
---|
| 52 | */ |
---|
| 53 | |
---|
| 54 | Runnable.prototype.__proto__ = EventEmitter.prototype; |
---|
| 55 | |
---|
| 56 | /** |
---|
| 57 | * Set & get timeout `ms`. |
---|
| 58 | * |
---|
| 59 | * @param {Number|String} ms |
---|
| 60 | * @return {Runnable|Number} ms or self |
---|
| 61 | * @api private |
---|
| 62 | */ |
---|
| 63 | |
---|
| 64 | Runnable.prototype.timeout = function(ms){ |
---|
| 65 | if (0 == arguments.length) return this._timeout; |
---|
| 66 | if ('string' == typeof ms) ms = milliseconds(ms); |
---|
| 67 | debug('timeout %d', ms); |
---|
| 68 | this._timeout = ms; |
---|
| 69 | if (this.timer) this.resetTimeout(); |
---|
| 70 | return this; |
---|
| 71 | }; |
---|
| 72 | |
---|
| 73 | /** |
---|
| 74 | * Set & get slow `ms`. |
---|
| 75 | * |
---|
| 76 | * @param {Number|String} ms |
---|
| 77 | * @return {Runnable|Number} ms or self |
---|
| 78 | * @api private |
---|
| 79 | */ |
---|
| 80 | |
---|
| 81 | Runnable.prototype.slow = function(ms){ |
---|
| 82 | if (0 === arguments.length) return this._slow; |
---|
| 83 | if ('string' == typeof ms) ms = milliseconds(ms); |
---|
| 84 | debug('timeout %d', ms); |
---|
| 85 | this._slow = ms; |
---|
| 86 | return this; |
---|
| 87 | }; |
---|
| 88 | |
---|
| 89 | /** |
---|
| 90 | * Return the full title generated by recursively |
---|
| 91 | * concatenating the parent's full title. |
---|
| 92 | * |
---|
| 93 | * @return {String} |
---|
| 94 | * @api public |
---|
| 95 | */ |
---|
| 96 | |
---|
| 97 | Runnable.prototype.fullTitle = function(){ |
---|
| 98 | return this.parent.fullTitle() + ' ' + this.title; |
---|
| 99 | }; |
---|
| 100 | |
---|
| 101 | /** |
---|
| 102 | * Clear the timeout. |
---|
| 103 | * |
---|
| 104 | * @api private |
---|
| 105 | */ |
---|
| 106 | |
---|
| 107 | Runnable.prototype.clearTimeout = function(){ |
---|
| 108 | clearTimeout(this.timer); |
---|
| 109 | }; |
---|
| 110 | |
---|
| 111 | /** |
---|
| 112 | * Inspect the runnable void of private properties. |
---|
| 113 | * |
---|
| 114 | * @return {String} |
---|
| 115 | * @api private |
---|
| 116 | */ |
---|
| 117 | |
---|
| 118 | Runnable.prototype.inspect = function(){ |
---|
| 119 | return JSON.stringify(this, function(key, val){ |
---|
| 120 | if ('_' == key[0]) return; |
---|
| 121 | if ('parent' == key) return '#<Suite>'; |
---|
| 122 | if ('ctx' == key) return '#<Context>'; |
---|
| 123 | return val; |
---|
| 124 | }, 2); |
---|
| 125 | }; |
---|
| 126 | |
---|
| 127 | /** |
---|
| 128 | * Reset the timeout. |
---|
| 129 | * |
---|
| 130 | * @api private |
---|
| 131 | */ |
---|
| 132 | |
---|
| 133 | Runnable.prototype.resetTimeout = function(){ |
---|
| 134 | var self = this; |
---|
| 135 | var ms = this.timeout() || 1e9; |
---|
| 136 | |
---|
| 137 | this.clearTimeout(); |
---|
| 138 | this.timer = setTimeout(function(){ |
---|
| 139 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); |
---|
| 140 | self.timedOut = true; |
---|
| 141 | }, ms); |
---|
| 142 | }; |
---|
| 143 | |
---|
| 144 | /** |
---|
| 145 | * Run the test and invoke `fn(err)`. |
---|
| 146 | * |
---|
| 147 | * @param {Function} fn |
---|
| 148 | * @api private |
---|
| 149 | */ |
---|
| 150 | |
---|
| 151 | Runnable.prototype.run = function(fn){ |
---|
| 152 | var self = this |
---|
| 153 | , ms = this.timeout() |
---|
| 154 | , start = new Date |
---|
| 155 | , ctx = this.ctx |
---|
| 156 | , finished |
---|
| 157 | , emitted; |
---|
| 158 | |
---|
| 159 | if (ctx) ctx.runnable(this); |
---|
| 160 | |
---|
| 161 | // timeout |
---|
| 162 | if (this.async) { |
---|
| 163 | if (ms) { |
---|
| 164 | this.timer = setTimeout(function(){ |
---|
| 165 | done(new Error('timeout of ' + ms + 'ms exceeded')); |
---|
| 166 | self.timedOut = true; |
---|
| 167 | }, ms); |
---|
| 168 | } |
---|
| 169 | } |
---|
| 170 | |
---|
| 171 | // called multiple times |
---|
| 172 | function multiple(err) { |
---|
| 173 | if (emitted) return; |
---|
| 174 | emitted = true; |
---|
| 175 | self.emit('error', err || new Error('done() called multiple times')); |
---|
| 176 | } |
---|
| 177 | |
---|
| 178 | // finished |
---|
| 179 | function done(err) { |
---|
| 180 | if (self.timedOut) return; |
---|
| 181 | if (finished) return multiple(err); |
---|
| 182 | self.clearTimeout(); |
---|
| 183 | self.duration = new Date - start; |
---|
| 184 | finished = true; |
---|
| 185 | fn(err); |
---|
| 186 | } |
---|
| 187 | |
---|
| 188 | // for .resetTimeout() |
---|
| 189 | this.callback = done; |
---|
| 190 | |
---|
| 191 | // async |
---|
| 192 | if (this.async) { |
---|
| 193 | try { |
---|
| 194 | this.fn.call(ctx, function(err){ |
---|
| 195 | if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); |
---|
| 196 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); |
---|
| 197 | done(); |
---|
| 198 | }); |
---|
| 199 | } catch (err) { |
---|
| 200 | done(err); |
---|
| 201 | } |
---|
| 202 | return; |
---|
| 203 | } |
---|
| 204 | |
---|
| 205 | if (this.asyncOnly) { |
---|
| 206 | return done(new Error('--async-only option in use without declaring `done()`')); |
---|
| 207 | } |
---|
| 208 | |
---|
| 209 | // sync |
---|
| 210 | try { |
---|
| 211 | if (!this.pending) this.fn.call(ctx); |
---|
| 212 | this.duration = new Date - start; |
---|
| 213 | fn(); |
---|
| 214 | } catch (err) { |
---|
| 215 | fn(err); |
---|
| 216 | } |
---|
| 217 | }; |
---|