runnable.js 4.2 KB

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