runnable.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * Module dependencies.
  3. */
  4. var EventEmitter = require('events').EventEmitter;
  5. var Pending = require('./pending');
  6. var debug = require('debug')('mocha:runnable');
  7. var milliseconds = require('./ms');
  8. var utils = require('./utils');
  9. var inherits = utils.inherits;
  10. /**
  11. * Save timer references to avoid Sinon interfering (see GH-237).
  12. */
  13. /* eslint-disable no-unused-vars, no-native-reassign */
  14. var Date = global.Date;
  15. var setTimeout = global.setTimeout;
  16. var setInterval = global.setInterval;
  17. var clearTimeout = global.clearTimeout;
  18. var clearInterval = global.clearInterval;
  19. /* eslint-enable no-unused-vars, no-native-reassign */
  20. /**
  21. * Object#toString().
  22. */
  23. var toString = Object.prototype.toString;
  24. /**
  25. * Expose `Runnable`.
  26. */
  27. module.exports = Runnable;
  28. /**
  29. * Initialize a new `Runnable` with the given `title` and callback `fn`.
  30. *
  31. * @param {String} title
  32. * @param {Function} fn
  33. * @api private
  34. * @param {string} title
  35. * @param {Function} fn
  36. */
  37. function Runnable(title, fn) {
  38. this.title = title;
  39. this.fn = fn;
  40. this.body = (fn || '').toString();
  41. this.async = fn && fn.length;
  42. this.sync = !this.async;
  43. this._timeout = 2000;
  44. this._slow = 75;
  45. this._enableTimeouts = true;
  46. this.timedOut = false;
  47. this._trace = new Error('done() called multiple times');
  48. this._retries = -1;
  49. this._currentRetry = 0;
  50. this.pending = false;
  51. }
  52. /**
  53. * Inherit from `EventEmitter.prototype`.
  54. */
  55. inherits(Runnable, EventEmitter);
  56. /**
  57. * Set & get timeout `ms`.
  58. *
  59. * @api private
  60. * @param {number|string} ms
  61. * @return {Runnable|number} ms or Runnable instance.
  62. */
  63. Runnable.prototype.timeout = function(ms) {
  64. if (!arguments.length) {
  65. return this._timeout;
  66. }
  67. if (ms === 0) {
  68. this._enableTimeouts = false;
  69. }
  70. if (typeof ms === 'string') {
  71. ms = milliseconds(ms);
  72. }
  73. debug('timeout %d', ms);
  74. this._timeout = ms;
  75. if (this.timer) {
  76. this.resetTimeout();
  77. }
  78. return this;
  79. };
  80. /**
  81. * Set & get slow `ms`.
  82. *
  83. * @api private
  84. * @param {number|string} ms
  85. * @return {Runnable|number} ms or Runnable instance.
  86. */
  87. Runnable.prototype.slow = function(ms) {
  88. if (!arguments.length) {
  89. return this._slow;
  90. }
  91. if (typeof ms === 'string') {
  92. ms = milliseconds(ms);
  93. }
  94. debug('timeout %d', ms);
  95. this._slow = ms;
  96. return this;
  97. };
  98. /**
  99. * Set and get whether timeout is `enabled`.
  100. *
  101. * @api private
  102. * @param {boolean} enabled
  103. * @return {Runnable|boolean} enabled or Runnable instance.
  104. */
  105. Runnable.prototype.enableTimeouts = function(enabled) {
  106. if (!arguments.length) {
  107. return this._enableTimeouts;
  108. }
  109. debug('enableTimeouts %s', enabled);
  110. this._enableTimeouts = enabled;
  111. return this;
  112. };
  113. /**
  114. * Halt and mark as pending.
  115. *
  116. * @api public
  117. */
  118. Runnable.prototype.skip = function() {
  119. throw new Pending();
  120. };
  121. /**
  122. * Check if this runnable or its parent suite is marked as pending.
  123. *
  124. * @api private
  125. */
  126. Runnable.prototype.isPending = function() {
  127. return this.pending || (this.parent && this.parent.isPending());
  128. };
  129. /**
  130. * Set number of retries.
  131. *
  132. * @api private
  133. */
  134. Runnable.prototype.retries = function(n) {
  135. if (!arguments.length) {
  136. return this._retries;
  137. }
  138. this._retries = n;
  139. };
  140. /**
  141. * Get current retry
  142. *
  143. * @api private
  144. */
  145. Runnable.prototype.currentRetry = function(n) {
  146. if (!arguments.length) {
  147. return this._currentRetry;
  148. }
  149. this._currentRetry = n;
  150. };
  151. /**
  152. * Return the full title generated by recursively concatenating the parent's
  153. * full title.
  154. *
  155. * @api public
  156. * @return {string}
  157. */
  158. Runnable.prototype.fullTitle = function() {
  159. return this.parent.fullTitle() + ' ' + this.title;
  160. };
  161. /**
  162. * Clear the timeout.
  163. *
  164. * @api private
  165. */
  166. Runnable.prototype.clearTimeout = function() {
  167. clearTimeout(this.timer);
  168. };
  169. /**
  170. * Inspect the runnable void of private properties.
  171. *
  172. * @api private
  173. * @return {string}
  174. */
  175. Runnable.prototype.inspect = function() {
  176. return JSON.stringify(this, function(key, val) {
  177. if (key[0] === '_') {
  178. return;
  179. }
  180. if (key === 'parent') {
  181. return '#<Suite>';
  182. }
  183. if (key === 'ctx') {
  184. return '#<Context>';
  185. }
  186. return val;
  187. }, 2);
  188. };
  189. /**
  190. * Reset the timeout.
  191. *
  192. * @api private
  193. */
  194. Runnable.prototype.resetTimeout = function() {
  195. var self = this;
  196. var ms = this.timeout() || 1e9;
  197. if (!this._enableTimeouts) {
  198. return;
  199. }
  200. this.clearTimeout();
  201. this.timer = setTimeout(function() {
  202. if (!self._enableTimeouts) {
  203. return;
  204. }
  205. self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
  206. self.timedOut = true;
  207. }, ms);
  208. };
  209. /**
  210. * Whitelist a list of globals for this test run.
  211. *
  212. * @api private
  213. * @param {string[]} globals
  214. */
  215. Runnable.prototype.globals = function(globals) {
  216. if (!arguments.length) {
  217. return this._allowedGlobals;
  218. }
  219. this._allowedGlobals = globals;
  220. };
  221. /**
  222. * Run the test and invoke `fn(err)`.
  223. *
  224. * @param {Function} fn
  225. * @api private
  226. */
  227. Runnable.prototype.run = function(fn) {
  228. var self = this;
  229. var start = new Date();
  230. var ctx = this.ctx;
  231. var finished;
  232. var emitted;
  233. // Sometimes the ctx exists, but it is not runnable
  234. if (ctx && ctx.runnable) {
  235. ctx.runnable(this);
  236. }
  237. // called multiple times
  238. function multiple(err) {
  239. if (emitted) {
  240. return;
  241. }
  242. emitted = true;
  243. self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
  244. }
  245. // finished
  246. function done(err) {
  247. var ms = self.timeout();
  248. if (self.timedOut) {
  249. return;
  250. }
  251. if (finished) {
  252. return multiple(err || self._trace);
  253. }
  254. self.clearTimeout();
  255. self.duration = new Date() - start;
  256. finished = true;
  257. if (!err && self.duration > ms && self._enableTimeouts) {
  258. err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
  259. }
  260. fn(err);
  261. }
  262. // for .resetTimeout()
  263. this.callback = done;
  264. // explicit async with `done` argument
  265. if (this.async) {
  266. this.resetTimeout();
  267. if (this.allowUncaught) {
  268. return callFnAsync(this.fn);
  269. }
  270. try {
  271. callFnAsync(this.fn);
  272. } catch (err) {
  273. done(utils.getError(err));
  274. }
  275. return;
  276. }
  277. if (this.allowUncaught) {
  278. callFn(this.fn);
  279. done();
  280. return;
  281. }
  282. // sync or promise-returning
  283. try {
  284. if (this.isPending()) {
  285. done();
  286. } else {
  287. callFn(this.fn);
  288. }
  289. } catch (err) {
  290. done(utils.getError(err));
  291. }
  292. function callFn(fn) {
  293. var result = fn.call(ctx);
  294. if (result && typeof result.then === 'function') {
  295. self.resetTimeout();
  296. result
  297. .then(function() {
  298. done();
  299. // Return null so libraries like bluebird do not warn about
  300. // subsequently constructed Promises.
  301. return null;
  302. },
  303. function(reason) {
  304. done(reason || new Error('Promise rejected with no or falsy reason'));
  305. });
  306. } else {
  307. if (self.asyncOnly) {
  308. return done(new Error('--async-only option in use without declaring `done()` or returning a promise'));
  309. }
  310. done();
  311. }
  312. }
  313. function callFnAsync(fn) {
  314. fn.call(ctx, function(err) {
  315. if (err instanceof Error || toString.call(err) === '[object Error]') {
  316. return done(err);
  317. }
  318. if (err) {
  319. if (Object.prototype.toString.call(err) === '[object Object]') {
  320. return done(new Error('done() invoked with non-Error: '
  321. + JSON.stringify(err)));
  322. }
  323. return done(new Error('done() invoked with non-Error: ' + err));
  324. }
  325. done();
  326. });
  327. }
  328. };