suite.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /**
  2. * Module dependencies.
  3. */
  4. var EventEmitter = require('events').EventEmitter;
  5. var Hook = require('./hook');
  6. var utils = require('./utils');
  7. var inherits = utils.inherits;
  8. var debug = require('debug')('mocha:suite');
  9. var milliseconds = require('./ms');
  10. /**
  11. * Expose `Suite`.
  12. */
  13. exports = module.exports = Suite;
  14. /**
  15. * Create a new `Suite` with the given `title` and parent `Suite`. When a suite
  16. * with the same title is already present, that suite is returned to provide
  17. * nicer reporter and more flexible meta-testing.
  18. *
  19. * @api public
  20. * @param {Suite} parent
  21. * @param {string} title
  22. * @return {Suite}
  23. */
  24. exports.create = function(parent, title) {
  25. var suite = new Suite(title, parent.ctx);
  26. suite.parent = parent;
  27. title = suite.fullTitle();
  28. parent.addSuite(suite);
  29. return suite;
  30. };
  31. /**
  32. * Initialize a new `Suite` with the given `title` and `ctx`.
  33. *
  34. * @api private
  35. * @param {string} title
  36. * @param {Context} parentContext
  37. */
  38. function Suite(title, parentContext) {
  39. this.title = title;
  40. function Context() {}
  41. Context.prototype = parentContext;
  42. this.ctx = new Context();
  43. this.suites = [];
  44. this.tests = [];
  45. this.pending = false;
  46. this._beforeEach = [];
  47. this._beforeAll = [];
  48. this._afterEach = [];
  49. this._afterAll = [];
  50. this.root = !title;
  51. this._timeout = 2000;
  52. this._enableTimeouts = true;
  53. this._slow = 75;
  54. this._bail = false;
  55. this._retries = -1;
  56. this.delayed = false;
  57. }
  58. /**
  59. * Inherit from `EventEmitter.prototype`.
  60. */
  61. inherits(Suite, EventEmitter);
  62. /**
  63. * Return a clone of this `Suite`.
  64. *
  65. * @api private
  66. * @return {Suite}
  67. */
  68. Suite.prototype.clone = function() {
  69. var suite = new Suite(this.title);
  70. debug('clone');
  71. suite.ctx = this.ctx;
  72. suite.timeout(this.timeout());
  73. suite.retries(this.retries());
  74. suite.enableTimeouts(this.enableTimeouts());
  75. suite.slow(this.slow());
  76. suite.bail(this.bail());
  77. return suite;
  78. };
  79. /**
  80. * Set timeout `ms` or short-hand such as "2s".
  81. *
  82. * @api private
  83. * @param {number|string} ms
  84. * @return {Suite|number} for chaining
  85. */
  86. Suite.prototype.timeout = function(ms) {
  87. if (!arguments.length) {
  88. return this._timeout;
  89. }
  90. if (ms.toString() === '0') {
  91. this._enableTimeouts = false;
  92. }
  93. if (typeof ms === 'string') {
  94. ms = milliseconds(ms);
  95. }
  96. debug('timeout %d', ms);
  97. this._timeout = parseInt(ms, 10);
  98. return this;
  99. };
  100. /**
  101. * Set number of times to retry a failed test.
  102. *
  103. * @api private
  104. * @param {number|string} n
  105. * @return {Suite|number} for chaining
  106. */
  107. Suite.prototype.retries = function(n) {
  108. if (!arguments.length) {
  109. return this._retries;
  110. }
  111. debug('retries %d', n);
  112. this._retries = parseInt(n, 10) || 0;
  113. return this;
  114. };
  115. /**
  116. * Set timeout to `enabled`.
  117. *
  118. * @api private
  119. * @param {boolean} enabled
  120. * @return {Suite|boolean} self or enabled
  121. */
  122. Suite.prototype.enableTimeouts = function(enabled) {
  123. if (!arguments.length) {
  124. return this._enableTimeouts;
  125. }
  126. debug('enableTimeouts %s', enabled);
  127. this._enableTimeouts = enabled;
  128. return this;
  129. };
  130. /**
  131. * Set slow `ms` or short-hand such as "2s".
  132. *
  133. * @api private
  134. * @param {number|string} ms
  135. * @return {Suite|number} for chaining
  136. */
  137. Suite.prototype.slow = function(ms) {
  138. if (!arguments.length) {
  139. return this._slow;
  140. }
  141. if (typeof ms === 'string') {
  142. ms = milliseconds(ms);
  143. }
  144. debug('slow %d', ms);
  145. this._slow = ms;
  146. return this;
  147. };
  148. /**
  149. * Sets whether to bail after first error.
  150. *
  151. * @api private
  152. * @param {boolean} bail
  153. * @return {Suite|number} for chaining
  154. */
  155. Suite.prototype.bail = function(bail) {
  156. if (!arguments.length) {
  157. return this._bail;
  158. }
  159. debug('bail %s', bail);
  160. this._bail = bail;
  161. return this;
  162. };
  163. /**
  164. * Check if this suite or its parent suite is marked as pending.
  165. *
  166. * @api private
  167. */
  168. Suite.prototype.isPending = function() {
  169. return this.pending || (this.parent && this.parent.isPending());
  170. };
  171. /**
  172. * Run `fn(test[, done])` before running tests.
  173. *
  174. * @api private
  175. * @param {string} title
  176. * @param {Function} fn
  177. * @return {Suite} for chaining
  178. */
  179. Suite.prototype.beforeAll = function(title, fn) {
  180. if (this.isPending()) {
  181. return this;
  182. }
  183. if (typeof title === 'function') {
  184. fn = title;
  185. title = fn.name;
  186. }
  187. title = '"before all" hook' + (title ? ': ' + title : '');
  188. var hook = new Hook(title, fn);
  189. hook.parent = this;
  190. hook.timeout(this.timeout());
  191. hook.retries(this.retries());
  192. hook.enableTimeouts(this.enableTimeouts());
  193. hook.slow(this.slow());
  194. hook.ctx = this.ctx;
  195. this._beforeAll.push(hook);
  196. this.emit('beforeAll', hook);
  197. return this;
  198. };
  199. /**
  200. * Run `fn(test[, done])` after running tests.
  201. *
  202. * @api private
  203. * @param {string} title
  204. * @param {Function} fn
  205. * @return {Suite} for chaining
  206. */
  207. Suite.prototype.afterAll = function(title, fn) {
  208. if (this.isPending()) {
  209. return this;
  210. }
  211. if (typeof title === 'function') {
  212. fn = title;
  213. title = fn.name;
  214. }
  215. title = '"after all" hook' + (title ? ': ' + title : '');
  216. var hook = new Hook(title, fn);
  217. hook.parent = this;
  218. hook.timeout(this.timeout());
  219. hook.retries(this.retries());
  220. hook.enableTimeouts(this.enableTimeouts());
  221. hook.slow(this.slow());
  222. hook.ctx = this.ctx;
  223. this._afterAll.push(hook);
  224. this.emit('afterAll', hook);
  225. return this;
  226. };
  227. /**
  228. * Run `fn(test[, done])` before each test case.
  229. *
  230. * @api private
  231. * @param {string} title
  232. * @param {Function} fn
  233. * @return {Suite} for chaining
  234. */
  235. Suite.prototype.beforeEach = function(title, fn) {
  236. if (this.isPending()) {
  237. return this;
  238. }
  239. if (typeof title === 'function') {
  240. fn = title;
  241. title = fn.name;
  242. }
  243. title = '"before each" hook' + (title ? ': ' + title : '');
  244. var hook = new Hook(title, fn);
  245. hook.parent = this;
  246. hook.timeout(this.timeout());
  247. hook.retries(this.retries());
  248. hook.enableTimeouts(this.enableTimeouts());
  249. hook.slow(this.slow());
  250. hook.ctx = this.ctx;
  251. this._beforeEach.push(hook);
  252. this.emit('beforeEach', hook);
  253. return this;
  254. };
  255. /**
  256. * Run `fn(test[, done])` after each test case.
  257. *
  258. * @api private
  259. * @param {string} title
  260. * @param {Function} fn
  261. * @return {Suite} for chaining
  262. */
  263. Suite.prototype.afterEach = function(title, fn) {
  264. if (this.isPending()) {
  265. return this;
  266. }
  267. if (typeof title === 'function') {
  268. fn = title;
  269. title = fn.name;
  270. }
  271. title = '"after each" hook' + (title ? ': ' + title : '');
  272. var hook = new Hook(title, fn);
  273. hook.parent = this;
  274. hook.timeout(this.timeout());
  275. hook.retries(this.retries());
  276. hook.enableTimeouts(this.enableTimeouts());
  277. hook.slow(this.slow());
  278. hook.ctx = this.ctx;
  279. this._afterEach.push(hook);
  280. this.emit('afterEach', hook);
  281. return this;
  282. };
  283. /**
  284. * Add a test `suite`.
  285. *
  286. * @api private
  287. * @param {Suite} suite
  288. * @return {Suite} for chaining
  289. */
  290. Suite.prototype.addSuite = function(suite) {
  291. suite.parent = this;
  292. suite.timeout(this.timeout());
  293. suite.retries(this.retries());
  294. suite.enableTimeouts(this.enableTimeouts());
  295. suite.slow(this.slow());
  296. suite.bail(this.bail());
  297. this.suites.push(suite);
  298. this.emit('suite', suite);
  299. return this;
  300. };
  301. /**
  302. * Add a `test` to this suite.
  303. *
  304. * @api private
  305. * @param {Test} test
  306. * @return {Suite} for chaining
  307. */
  308. Suite.prototype.addTest = function(test) {
  309. test.parent = this;
  310. test.timeout(this.timeout());
  311. test.retries(this.retries());
  312. test.enableTimeouts(this.enableTimeouts());
  313. test.slow(this.slow());
  314. test.ctx = this.ctx;
  315. this.tests.push(test);
  316. this.emit('test', test);
  317. return this;
  318. };
  319. /**
  320. * Return the full title generated by recursively concatenating the parent's
  321. * full title.
  322. *
  323. * @api public
  324. * @return {string}
  325. */
  326. Suite.prototype.fullTitle = function() {
  327. if (this.parent) {
  328. var full = this.parent.fullTitle();
  329. if (full) {
  330. return full + ' ' + this.title;
  331. }
  332. }
  333. return this.title;
  334. };
  335. /**
  336. * Return the total number of tests.
  337. *
  338. * @api public
  339. * @return {number}
  340. */
  341. Suite.prototype.total = function() {
  342. return utils.reduce(this.suites, function(sum, suite) {
  343. return sum + suite.total();
  344. }, 0) + this.tests.length;
  345. };
  346. /**
  347. * Iterates through each suite recursively to find all tests. Applies a
  348. * function in the format `fn(test)`.
  349. *
  350. * @api private
  351. * @param {Function} fn
  352. * @return {Suite}
  353. */
  354. Suite.prototype.eachTest = function(fn) {
  355. utils.forEach(this.tests, fn);
  356. utils.forEach(this.suites, function(suite) {
  357. suite.eachTest(fn);
  358. });
  359. return this;
  360. };
  361. /**
  362. * This will run the root suite if we happen to be running in delayed mode.
  363. */
  364. Suite.prototype.run = function run() {
  365. if (this.root) {
  366. this.emit('run');
  367. }
  368. };