Total Complexity | 153 |
Complexity/F | 2.64 |
Lines of Code | 979 |
Function Count | 58 |
Duplicated Lines | 11 |
Ratio | 1.12 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like node_modules/mocha/lib/runner.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | 'use strict'; |
||
2 | |||
3 | /** |
||
4 | * Module dependencies. |
||
5 | */ |
||
6 | |||
7 | var EventEmitter = require('events').EventEmitter; |
||
8 | var Pending = require('./pending'); |
||
9 | var utils = require('./utils'); |
||
10 | var inherits = utils.inherits; |
||
11 | var debug = require('debug')('mocha:runner'); |
||
12 | var Runnable = require('./runnable'); |
||
13 | var stackFilter = utils.stackTraceFilter(); |
||
14 | var stringify = utils.stringify; |
||
15 | var type = utils.type; |
||
16 | var undefinedError = utils.undefinedError; |
||
17 | |||
18 | /** |
||
19 | * Non-enumerable globals. |
||
20 | */ |
||
21 | |||
22 | var globals = [ |
||
23 | 'setTimeout', |
||
24 | 'clearTimeout', |
||
25 | 'setInterval', |
||
26 | 'clearInterval', |
||
27 | 'XMLHttpRequest', |
||
28 | 'Date', |
||
29 | 'setImmediate', |
||
30 | 'clearImmediate' |
||
31 | ]; |
||
32 | |||
33 | /** |
||
34 | * Expose `Runner`. |
||
35 | */ |
||
36 | |||
37 | module.exports = Runner; |
||
38 | |||
39 | /** |
||
40 | * Initialize a `Runner` for the given `suite`. |
||
41 | * |
||
42 | * Events: |
||
43 | * |
||
44 | * - `start` execution started |
||
45 | * - `end` execution complete |
||
46 | * - `suite` (suite) test suite execution started |
||
47 | * - `suite end` (suite) all tests (and sub-suites) have finished |
||
48 | * - `test` (test) test execution started |
||
49 | * - `test end` (test) test completed |
||
50 | * - `hook` (hook) hook execution started |
||
51 | * - `hook end` (hook) hook complete |
||
52 | * - `pass` (test) test passed |
||
53 | * - `fail` (test, err) test failed |
||
54 | * - `pending` (test) test pending |
||
55 | * |
||
56 | * @api public |
||
57 | * @param {Suite} suite Root suite |
||
58 | * @param {boolean} [delay] Whether or not to delay execution of root suite |
||
59 | * until ready. |
||
60 | */ |
||
61 | function Runner (suite, delay) { |
||
62 | var self = this; |
||
63 | this._globals = []; |
||
64 | this._abort = false; |
||
65 | this._delay = delay; |
||
66 | this.suite = suite; |
||
67 | this.started = false; |
||
68 | this.total = suite.total(); |
||
69 | this.failures = 0; |
||
70 | this.on('test end', function (test) { |
||
71 | self.checkGlobals(test); |
||
72 | }); |
||
73 | this.on('hook end', function (hook) { |
||
74 | self.checkGlobals(hook); |
||
75 | }); |
||
76 | this._defaultGrep = /.*/; |
||
77 | this.grep(this._defaultGrep); |
||
78 | this.globals(this.globalProps().concat(extraGlobals())); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Wrapper for setImmediate, process.nextTick, or browser polyfill. |
||
83 | * |
||
84 | * @param {Function} fn |
||
85 | * @api private |
||
86 | */ |
||
87 | Runner.immediately = global.setImmediate || process.nextTick; |
||
88 | |||
89 | /** |
||
90 | * Inherit from `EventEmitter.prototype`. |
||
91 | */ |
||
92 | inherits(Runner, EventEmitter); |
||
93 | |||
94 | /** |
||
95 | * Run tests with full titles matching `re`. Updates runner.total |
||
96 | * with number of tests matched. |
||
97 | * |
||
98 | * @param {RegExp} re |
||
99 | * @param {Boolean} invert |
||
100 | * @return {Runner} for chaining |
||
101 | * @api public |
||
102 | * @param {RegExp} re |
||
103 | * @param {boolean} invert |
||
104 | * @return {Runner} Runner instance. |
||
105 | */ |
||
106 | Runner.prototype.grep = function (re, invert) { |
||
107 | debug('grep %s', re); |
||
108 | this._grep = re; |
||
109 | this._invert = invert; |
||
110 | this.total = this.grepTotal(this.suite); |
||
111 | return this; |
||
112 | }; |
||
113 | |||
114 | /** |
||
115 | * Returns the number of tests matching the grep search for the |
||
116 | * given suite. |
||
117 | * |
||
118 | * @param {Suite} suite |
||
119 | * @return {Number} |
||
120 | * @api public |
||
121 | * @param {Suite} suite |
||
122 | * @return {number} |
||
123 | */ |
||
124 | Runner.prototype.grepTotal = function (suite) { |
||
125 | var self = this; |
||
126 | var total = 0; |
||
127 | |||
128 | suite.eachTest(function (test) { |
||
129 | var match = self._grep.test(test.fullTitle()); |
||
130 | if (self._invert) { |
||
131 | match = !match; |
||
132 | } |
||
133 | if (match) { |
||
134 | total++; |
||
135 | } |
||
136 | }); |
||
137 | |||
138 | return total; |
||
139 | }; |
||
140 | |||
141 | /** |
||
142 | * Return a list of global properties. |
||
143 | * |
||
144 | * @return {Array} |
||
145 | * @api private |
||
146 | */ |
||
147 | Runner.prototype.globalProps = function () { |
||
148 | var props = Object.keys(global); |
||
149 | |||
150 | // non-enumerables |
||
151 | for (var i = 0; i < globals.length; ++i) { |
||
152 | if (~props.indexOf(globals[i])) { |
||
153 | continue; |
||
154 | } |
||
155 | props.push(globals[i]); |
||
156 | } |
||
157 | |||
158 | return props; |
||
159 | }; |
||
160 | |||
161 | /** |
||
162 | * Allow the given `arr` of globals. |
||
163 | * |
||
164 | * @param {Array} arr |
||
165 | * @return {Runner} for chaining |
||
166 | * @api public |
||
167 | * @param {Array} arr |
||
168 | * @return {Runner} Runner instance. |
||
169 | */ |
||
170 | Runner.prototype.globals = function (arr) { |
||
171 | if (!arguments.length) { |
||
172 | return this._globals; |
||
173 | } |
||
174 | debug('globals %j', arr); |
||
175 | this._globals = this._globals.concat(arr); |
||
176 | return this; |
||
177 | }; |
||
178 | |||
179 | /** |
||
180 | * Check for global variable leaks. |
||
181 | * |
||
182 | * @api private |
||
183 | */ |
||
184 | View Code Duplication | Runner.prototype.checkGlobals = function (test) { |
|
|
|||
185 | if (this.ignoreLeaks) { |
||
186 | return; |
||
187 | } |
||
188 | var ok = this._globals; |
||
189 | |||
190 | var globals = this.globalProps(); |
||
191 | var leaks; |
||
192 | |||
193 | if (test) { |
||
194 | ok = ok.concat(test._allowedGlobals || []); |
||
195 | } |
||
196 | |||
197 | if (this.prevGlobalsLength === globals.length) { |
||
198 | return; |
||
199 | } |
||
200 | this.prevGlobalsLength = globals.length; |
||
201 | |||
202 | leaks = filterLeaks(ok, globals); |
||
203 | this._globals = this._globals.concat(leaks); |
||
204 | |||
205 | if (leaks.length > 1) { |
||
206 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); |
||
207 | } else if (leaks.length) { |
||
208 | this.fail(test, new Error('global leak detected: ' + leaks[0])); |
||
209 | } |
||
210 | }; |
||
211 | |||
212 | /** |
||
213 | * Fail the given `test`. |
||
214 | * |
||
215 | * @api private |
||
216 | * @param {Test} test |
||
217 | * @param {Error} err |
||
218 | */ |
||
219 | View Code Duplication | Runner.prototype.fail = function (test, err) { |
|
220 | if (test.isPending()) { |
||
221 | return; |
||
222 | } |
||
223 | |||
224 | ++this.failures; |
||
225 | test.state = 'failed'; |
||
226 | |||
227 | if (!(err instanceof Error || (err && typeof err.message === 'string'))) { |
||
228 | err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'); |
||
229 | } |
||
230 | |||
231 | try { |
||
232 | err.stack = (this.fullStackTrace || !err.stack) |
||
233 | ? err.stack |
||
234 | : stackFilter(err.stack); |
||
235 | } catch (ignored) { |
||
236 | // some environments do not take kindly to monkeying with the stack |
||
237 | } |
||
238 | |||
239 | this.emit('fail', test, err); |
||
240 | }; |
||
241 | |||
242 | /** |
||
243 | * Fail the given `hook` with `err`. |
||
244 | * |
||
245 | * Hook failures work in the following pattern: |
||
246 | * - If bail, then exit |
||
247 | * - Failed `before` hook skips all tests in a suite and subsuites, |
||
248 | * but jumps to corresponding `after` hook |
||
249 | * - Failed `before each` hook skips remaining tests in a |
||
250 | * suite and jumps to corresponding `after each` hook, |
||
251 | * which is run only once |
||
252 | * - Failed `after` hook does not alter |
||
253 | * execution order |
||
254 | * - Failed `after each` hook skips remaining tests in a |
||
255 | * suite and subsuites, but executes other `after each` |
||
256 | * hooks |
||
257 | * |
||
258 | * @api private |
||
259 | * @param {Hook} hook |
||
260 | * @param {Error} err |
||
261 | */ |
||
262 | Runner.prototype.failHook = function (hook, err) { |
||
263 | if (hook.ctx && hook.ctx.currentTest) { |
||
264 | hook.originalTitle = hook.originalTitle || hook.title; |
||
265 | hook.title = hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"'; |
||
266 | } |
||
267 | |||
268 | this.fail(hook, err); |
||
269 | if (this.suite.bail()) { |
||
270 | this.emit('end'); |
||
271 | } |
||
272 | }; |
||
273 | |||
274 | /** |
||
275 | * Run hook `name` callbacks and then invoke `fn()`. |
||
276 | * |
||
277 | * @api private |
||
278 | * @param {string} name |
||
279 | * @param {Function} fn |
||
280 | */ |
||
281 | |||
282 | View Code Duplication | Runner.prototype.hook = function (name, fn) { |
|
283 | var suite = this.suite; |
||
284 | var hooks = suite['_' + name]; |
||
285 | var self = this; |
||
286 | |||
287 | function next (i) { |
||
288 | var hook = hooks[i]; |
||
289 | if (!hook) { |
||
290 | return fn(); |
||
291 | } |
||
292 | self.currentRunnable = hook; |
||
293 | |||
294 | hook.ctx.currentTest = self.test; |
||
295 | |||
296 | self.emit('hook', hook); |
||
297 | |||
298 | if (!hook.listeners('error').length) { |
||
299 | hook.on('error', function (err) { |
||
300 | self.failHook(hook, err); |
||
301 | }); |
||
302 | } |
||
303 | |||
304 | hook.run(function (err) { |
||
305 | var testError = hook.error(); |
||
306 | if (testError) { |
||
307 | self.fail(self.test, testError); |
||
308 | } |
||
309 | if (err) { |
||
310 | if (err instanceof Pending) { |
||
311 | if (name === 'beforeEach' || name === 'afterEach') { |
||
312 | self.test.pending = true; |
||
313 | } else { |
||
314 | suite.tests.forEach(function (test) { |
||
315 | test.pending = true; |
||
316 | }); |
||
317 | // a pending hook won't be executed twice. |
||
318 | hook.pending = true; |
||
319 | } |
||
320 | } else { |
||
321 | self.failHook(hook, err); |
||
322 | |||
323 | // stop executing hooks, notify callee of hook err |
||
324 | return fn(err); |
||
325 | } |
||
326 | } |
||
327 | self.emit('hook end', hook); |
||
328 | delete hook.ctx.currentTest; |
||
329 | next(++i); |
||
330 | }); |
||
331 | } |
||
332 | |||
333 | Runner.immediately(function () { |
||
334 | next(0); |
||
335 | }); |
||
336 | }; |
||
337 | |||
338 | /** |
||
339 | * Run hook `name` for the given array of `suites` |
||
340 | * in order, and callback `fn(err, errSuite)`. |
||
341 | * |
||
342 | * @api private |
||
343 | * @param {string} name |
||
344 | * @param {Array} suites |
||
345 | * @param {Function} fn |
||
346 | */ |
||
347 | Runner.prototype.hooks = function (name, suites, fn) { |
||
348 | var self = this; |
||
349 | var orig = this.suite; |
||
350 | |||
351 | function next (suite) { |
||
352 | self.suite = suite; |
||
353 | |||
354 | if (!suite) { |
||
355 | self.suite = orig; |
||
356 | return fn(); |
||
357 | } |
||
358 | |||
359 | self.hook(name, function (err) { |
||
360 | if (err) { |
||
361 | var errSuite = self.suite; |
||
362 | self.suite = orig; |
||
363 | return fn(err, errSuite); |
||
364 | } |
||
365 | |||
366 | next(suites.pop()); |
||
367 | }); |
||
368 | } |
||
369 | |||
370 | next(suites.pop()); |
||
371 | }; |
||
372 | |||
373 | /** |
||
374 | * Run hooks from the top level down. |
||
375 | * |
||
376 | * @param {String} name |
||
377 | * @param {Function} fn |
||
378 | * @api private |
||
379 | */ |
||
380 | Runner.prototype.hookUp = function (name, fn) { |
||
381 | var suites = [this.suite].concat(this.parents()).reverse(); |
||
382 | this.hooks(name, suites, fn); |
||
383 | }; |
||
384 | |||
385 | /** |
||
386 | * Run hooks from the bottom up. |
||
387 | * |
||
388 | * @param {String} name |
||
389 | * @param {Function} fn |
||
390 | * @api private |
||
391 | */ |
||
392 | Runner.prototype.hookDown = function (name, fn) { |
||
393 | var suites = [this.suite].concat(this.parents()); |
||
394 | this.hooks(name, suites, fn); |
||
395 | }; |
||
396 | |||
397 | /** |
||
398 | * Return an array of parent Suites from |
||
399 | * closest to furthest. |
||
400 | * |
||
401 | * @return {Array} |
||
402 | * @api private |
||
403 | */ |
||
404 | Runner.prototype.parents = function () { |
||
405 | var suite = this.suite; |
||
406 | var suites = []; |
||
407 | while (suite.parent) { |
||
408 | suite = suite.parent; |
||
409 | suites.push(suite); |
||
410 | } |
||
411 | return suites; |
||
412 | }; |
||
413 | |||
414 | /** |
||
415 | * Run the current test and callback `fn(err)`. |
||
416 | * |
||
417 | * @param {Function} fn |
||
418 | * @api private |
||
419 | */ |
||
420 | View Code Duplication | Runner.prototype.runTest = function (fn) { |
|
421 | var self = this; |
||
422 | var test = this.test; |
||
423 | |||
424 | if (!test) { |
||
425 | return; |
||
426 | } |
||
427 | if (this.forbidOnly && hasOnly(this.parents().reverse()[0] || this.suite)) { |
||
428 | fn(new Error('`.only` forbidden')); |
||
429 | return; |
||
430 | } |
||
431 | if (this.asyncOnly) { |
||
432 | test.asyncOnly = true; |
||
433 | } |
||
434 | test.on('error', function (err) { |
||
435 | self.fail(test, err); |
||
436 | }); |
||
437 | if (this.allowUncaught) { |
||
438 | test.allowUncaught = true; |
||
439 | return test.run(fn); |
||
440 | } |
||
441 | try { |
||
442 | test.run(fn); |
||
443 | } catch (err) { |
||
444 | fn(err); |
||
445 | } |
||
446 | }; |
||
447 | |||
448 | /** |
||
449 | * Run tests in the given `suite` and invoke the callback `fn()` when complete. |
||
450 | * |
||
451 | * @api private |
||
452 | * @param {Suite} suite |
||
453 | * @param {Function} fn |
||
454 | */ |
||
455 | View Code Duplication | Runner.prototype.runTests = function (suite, fn) { |
|
456 | var self = this; |
||
457 | var tests = suite.tests.slice(); |
||
458 | var test; |
||
459 | |||
460 | function hookErr (_, errSuite, after) { |
||
461 | // before/after Each hook for errSuite failed: |
||
462 | var orig = self.suite; |
||
463 | |||
464 | // for failed 'after each' hook start from errSuite parent, |
||
465 | // otherwise start from errSuite itself |
||
466 | self.suite = after ? errSuite.parent : errSuite; |
||
467 | |||
468 | if (self.suite) { |
||
469 | // call hookUp afterEach |
||
470 | self.hookUp('afterEach', function (err2, errSuite2) { |
||
471 | self.suite = orig; |
||
472 | // some hooks may fail even now |
||
473 | if (err2) { |
||
474 | return hookErr(err2, errSuite2, true); |
||
475 | } |
||
476 | // report error suite |
||
477 | fn(errSuite); |
||
478 | }); |
||
479 | } else { |
||
480 | // there is no need calling other 'after each' hooks |
||
481 | self.suite = orig; |
||
482 | fn(errSuite); |
||
483 | } |
||
484 | } |
||
485 | |||
486 | function next (err, errSuite) { |
||
487 | // if we bail after first err |
||
488 | if (self.failures && suite._bail) { |
||
489 | return fn(); |
||
490 | } |
||
491 | |||
492 | if (self._abort) { |
||
493 | return fn(); |
||
494 | } |
||
495 | |||
496 | if (err) { |
||
497 | return hookErr(err, errSuite, true); |
||
498 | } |
||
499 | |||
500 | // next test |
||
501 | test = tests.shift(); |
||
502 | |||
503 | // all done |
||
504 | if (!test) { |
||
505 | return fn(); |
||
506 | } |
||
507 | |||
508 | // grep |
||
509 | var match = self._grep.test(test.fullTitle()); |
||
510 | if (self._invert) { |
||
511 | match = !match; |
||
512 | } |
||
513 | if (!match) { |
||
514 | // Run immediately only if we have defined a grep. When we |
||
515 | // define a grep — It can cause maximum callstack error if |
||
516 | // the grep is doing a large recursive loop by neglecting |
||
517 | // all tests. The run immediately function also comes with |
||
518 | // a performance cost. So we don't want to run immediately |
||
519 | // if we run the whole test suite, because running the whole |
||
520 | // test suite don't do any immediate recursive loops. Thus, |
||
521 | // allowing a JS runtime to breathe. |
||
522 | if (self._grep !== self._defaultGrep) { |
||
523 | Runner.immediately(next); |
||
524 | } else { |
||
525 | next(); |
||
526 | } |
||
527 | return; |
||
528 | } |
||
529 | |||
530 | if (test.isPending()) { |
||
531 | if (self.forbidPending) { |
||
532 | test.isPending = alwaysFalse; |
||
533 | self.fail(test, new Error('Pending test forbidden')); |
||
534 | delete test.isPending; |
||
535 | } else { |
||
536 | self.emit('pending', test); |
||
537 | } |
||
538 | self.emit('test end', test); |
||
539 | return next(); |
||
540 | } |
||
541 | |||
542 | // execute test and hook(s) |
||
543 | self.emit('test', self.test = test); |
||
544 | self.hookDown('beforeEach', function (err, errSuite) { |
||
545 | if (test.isPending()) { |
||
546 | if (self.forbidPending) { |
||
547 | test.isPending = alwaysFalse; |
||
548 | self.fail(test, new Error('Pending test forbidden')); |
||
549 | delete test.isPending; |
||
550 | } else { |
||
551 | self.emit('pending', test); |
||
552 | } |
||
553 | self.emit('test end', test); |
||
554 | return next(); |
||
555 | } |
||
556 | if (err) { |
||
557 | return hookErr(err, errSuite, false); |
||
558 | } |
||
559 | self.currentRunnable = self.test; |
||
560 | self.runTest(function (err) { |
||
561 | test = self.test; |
||
562 | if (err) { |
||
563 | var retry = test.currentRetry(); |
||
564 | if (err instanceof Pending && self.forbidPending) { |
||
565 | self.fail(test, new Error('Pending test forbidden')); |
||
566 | } else if (err instanceof Pending) { |
||
567 | test.pending = true; |
||
568 | self.emit('pending', test); |
||
569 | } else if (retry < test.retries()) { |
||
570 | var clonedTest = test.clone(); |
||
571 | clonedTest.currentRetry(retry + 1); |
||
572 | tests.unshift(clonedTest); |
||
573 | |||
574 | // Early return + hook trigger so that it doesn't |
||
575 | // increment the count wrong |
||
576 | return self.hookUp('afterEach', next); |
||
577 | } else { |
||
578 | self.fail(test, err); |
||
579 | } |
||
580 | self.emit('test end', test); |
||
581 | |||
582 | if (err instanceof Pending) { |
||
583 | return next(); |
||
584 | } |
||
585 | |||
586 | return self.hookUp('afterEach', next); |
||
587 | } |
||
588 | |||
589 | test.state = 'passed'; |
||
590 | self.emit('pass', test); |
||
591 | self.emit('test end', test); |
||
592 | self.hookUp('afterEach', next); |
||
593 | }); |
||
594 | }); |
||
595 | } |
||
596 | |||
597 | this.next = next; |
||
598 | this.hookErr = hookErr; |
||
599 | next(); |
||
600 | }; |
||
601 | |||
602 | function alwaysFalse () { |
||
603 | return false; |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Run the given `suite` and invoke the callback `fn()` when complete. |
||
608 | * |
||
609 | * @api private |
||
610 | * @param {Suite} suite |
||
611 | * @param {Function} fn |
||
612 | */ |
||
613 | View Code Duplication | Runner.prototype.runSuite = function (suite, fn) { |
|
614 | var i = 0; |
||
615 | var self = this; |
||
616 | var total = this.grepTotal(suite); |
||
617 | var afterAllHookCalled = false; |
||
618 | |||
619 | debug('run suite %s', suite.fullTitle()); |
||
620 | |||
621 | if (!total || (self.failures && suite._bail)) { |
||
622 | return fn(); |
||
623 | } |
||
624 | |||
625 | this.emit('suite', this.suite = suite); |
||
626 | |||
627 | function next (errSuite) { |
||
628 | if (errSuite) { |
||
629 | // current suite failed on a hook from errSuite |
||
630 | if (errSuite === suite) { |
||
631 | // if errSuite is current suite |
||
632 | // continue to the next sibling suite |
||
633 | return done(); |
||
634 | } |
||
635 | // errSuite is among the parents of current suite |
||
636 | // stop execution of errSuite and all sub-suites |
||
637 | return done(errSuite); |
||
638 | } |
||
639 | |||
640 | if (self._abort) { |
||
641 | return done(); |
||
642 | } |
||
643 | |||
644 | var curr = suite.suites[i++]; |
||
645 | if (!curr) { |
||
646 | return done(); |
||
647 | } |
||
648 | |||
649 | // Avoid grep neglecting large number of tests causing a |
||
650 | // huge recursive loop and thus a maximum call stack error. |
||
651 | // See comment in `this.runTests()` for more information. |
||
652 | if (self._grep !== self._defaultGrep) { |
||
653 | Runner.immediately(function () { |
||
654 | self.runSuite(curr, next); |
||
655 | }); |
||
656 | } else { |
||
657 | self.runSuite(curr, next); |
||
658 | } |
||
659 | } |
||
660 | |||
661 | function done (errSuite) { |
||
662 | self.suite = suite; |
||
663 | self.nextSuite = next; |
||
664 | |||
665 | if (afterAllHookCalled) { |
||
666 | fn(errSuite); |
||
667 | } else { |
||
668 | // mark that the afterAll block has been called once |
||
669 | // and so can be skipped if there is an error in it. |
||
670 | afterAllHookCalled = true; |
||
671 | |||
672 | // remove reference to test |
||
673 | delete self.test; |
||
674 | |||
675 | self.hook('afterAll', function () { |
||
676 | self.emit('suite end', suite); |
||
677 | fn(errSuite); |
||
678 | }); |
||
679 | } |
||
680 | } |
||
681 | |||
682 | this.nextSuite = next; |
||
683 | |||
684 | this.hook('beforeAll', function (err) { |
||
685 | if (err) { |
||
686 | return done(); |
||
687 | } |
||
688 | self.runTests(suite, next); |
||
689 | }); |
||
690 | }; |
||
691 | |||
692 | /** |
||
693 | * Handle uncaught exceptions. |
||
694 | * |
||
695 | * @param {Error} err |
||
696 | * @api private |
||
697 | */ |
||
698 | View Code Duplication | Runner.prototype.uncaught = function (err) { |
|
699 | if (err) { |
||
700 | debug('uncaught exception %s', err === (function () { |
||
701 | return this; |
||
702 | }.call(err)) ? (err.message || err) : err); |
||
703 | } else { |
||
704 | debug('uncaught undefined exception'); |
||
705 | err = undefinedError(); |
||
706 | } |
||
707 | err.uncaught = true; |
||
708 | |||
709 | var runnable = this.currentRunnable; |
||
710 | |||
711 | if (!runnable) { |
||
712 | runnable = new Runnable('Uncaught error outside test suite'); |
||
713 | runnable.parent = this.suite; |
||
714 | |||
715 | if (this.started) { |
||
716 | this.fail(runnable, err); |
||
717 | } else { |
||
718 | // Can't recover from this failure |
||
719 | this.emit('start'); |
||
720 | this.fail(runnable, err); |
||
721 | this.emit('end'); |
||
722 | } |
||
723 | |||
724 | return; |
||
725 | } |
||
726 | |||
727 | runnable.clearTimeout(); |
||
728 | |||
729 | // Ignore errors if complete or pending |
||
730 | if (runnable.state || runnable.isPending()) { |
||
731 | return; |
||
732 | } |
||
733 | this.fail(runnable, err); |
||
734 | |||
735 | // recover from test |
||
736 | if (runnable.type === 'test') { |
||
737 | this.emit('test end', runnable); |
||
738 | this.hookUp('afterEach', this.next); |
||
739 | return; |
||
740 | } |
||
741 | |||
742 | // recover from hooks |
||
743 | if (runnable.type === 'hook') { |
||
744 | var errSuite = this.suite; |
||
745 | // if hook failure is in afterEach block |
||
746 | if (runnable.fullTitle().indexOf('after each') > -1) { |
||
747 | return this.hookErr(err, errSuite, true); |
||
748 | } |
||
749 | // if hook failure is in beforeEach block |
||
750 | if (runnable.fullTitle().indexOf('before each') > -1) { |
||
751 | return this.hookErr(err, errSuite, false); |
||
752 | } |
||
753 | // if hook failure is in after or before blocks |
||
754 | return this.nextSuite(errSuite); |
||
755 | } |
||
756 | |||
757 | // bail |
||
758 | this.emit('end'); |
||
759 | }; |
||
760 | |||
761 | /** |
||
762 | * Cleans up the references to all the deferred functions |
||
763 | * (before/after/beforeEach/afterEach) and tests of a Suite. |
||
764 | * These must be deleted otherwise a memory leak can happen, |
||
765 | * as those functions may reference variables from closures, |
||
766 | * thus those variables can never be garbage collected as long |
||
767 | * as the deferred functions exist. |
||
768 | * |
||
769 | * @param {Suite} suite |
||
770 | */ |
||
771 | View Code Duplication | function cleanSuiteReferences (suite) { |
|
772 | function cleanArrReferences (arr) { |
||
773 | for (var i = 0; i < arr.length; i++) { |
||
774 | delete arr[i].fn; |
||
775 | } |
||
776 | } |
||
777 | |||
778 | if (Array.isArray(suite._beforeAll)) { |
||
779 | cleanArrReferences(suite._beforeAll); |
||
780 | } |
||
781 | |||
782 | if (Array.isArray(suite._beforeEach)) { |
||
783 | cleanArrReferences(suite._beforeEach); |
||
784 | } |
||
785 | |||
786 | if (Array.isArray(suite._afterAll)) { |
||
787 | cleanArrReferences(suite._afterAll); |
||
788 | } |
||
789 | |||
790 | if (Array.isArray(suite._afterEach)) { |
||
791 | cleanArrReferences(suite._afterEach); |
||
792 | } |
||
793 | |||
794 | for (var i = 0; i < suite.tests.length; i++) { |
||
795 | delete suite.tests[i].fn; |
||
796 | } |
||
797 | } |
||
798 | |||
799 | /** |
||
800 | * Run the root suite and invoke `fn(failures)` |
||
801 | * on completion. |
||
802 | * |
||
803 | * @param {Function} fn |
||
804 | * @return {Runner} for chaining |
||
805 | * @api public |
||
806 | * @param {Function} fn |
||
807 | * @return {Runner} Runner instance. |
||
808 | */ |
||
809 | Runner.prototype.run = function (fn) { |
||
810 | var self = this; |
||
811 | var rootSuite = this.suite; |
||
812 | |||
813 | // If there is an `only` filter |
||
814 | if (hasOnly(rootSuite)) { |
||
815 | filterOnly(rootSuite); |
||
816 | } |
||
817 | |||
818 | fn = fn || function () {}; |
||
819 | |||
820 | function uncaught (err) { |
||
821 | self.uncaught(err); |
||
822 | } |
||
823 | |||
824 | function start () { |
||
825 | self.started = true; |
||
826 | self.emit('start'); |
||
827 | self.runSuite(rootSuite, function () { |
||
828 | debug('finished running'); |
||
829 | self.emit('end'); |
||
830 | }); |
||
831 | } |
||
832 | |||
833 | debug('start'); |
||
834 | |||
835 | // references cleanup to avoid memory leaks |
||
836 | this.on('suite end', cleanSuiteReferences); |
||
837 | |||
838 | // callback |
||
839 | this.on('end', function () { |
||
840 | debug('end'); |
||
841 | process.removeListener('uncaughtException', uncaught); |
||
842 | fn(self.failures); |
||
843 | }); |
||
844 | |||
845 | // uncaught exception |
||
846 | process.on('uncaughtException', uncaught); |
||
847 | |||
848 | if (this._delay) { |
||
849 | // for reporters, I guess. |
||
850 | // might be nice to debounce some dots while we wait. |
||
851 | this.emit('waiting', rootSuite); |
||
852 | rootSuite.once('run', start); |
||
853 | } else { |
||
854 | start(); |
||
855 | } |
||
856 | |||
857 | return this; |
||
858 | }; |
||
859 | |||
860 | /** |
||
861 | * Cleanly abort execution. |
||
862 | * |
||
863 | * @api public |
||
864 | * @return {Runner} Runner instance. |
||
865 | */ |
||
866 | Runner.prototype.abort = function () { |
||
867 | debug('aborting'); |
||
868 | this._abort = true; |
||
869 | |||
870 | return this; |
||
871 | }; |
||
872 | |||
873 | /** |
||
874 | * Filter suites based on `isOnly` logic. |
||
875 | * |
||
876 | * @param {Array} suite |
||
877 | * @returns {Boolean} |
||
878 | * @api private |
||
879 | */ |
||
880 | View Code Duplication | function filterOnly (suite) { |
|
881 | if (suite._onlyTests.length) { |
||
882 | // If the suite contains `only` tests, run those and ignore any nested suites. |
||
883 | suite.tests = suite._onlyTests; |
||
884 | suite.suites = []; |
||
885 | } else { |
||
886 | // Otherwise, do not run any of the tests in this suite. |
||
887 | suite.tests = []; |
||
888 | suite._onlySuites.forEach(function (onlySuite) { |
||
889 | // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite. |
||
890 | // Otherwise, all of the tests on this `only` suite should be run, so don't filter it. |
||
891 | if (hasOnly(onlySuite)) { |
||
892 | filterOnly(onlySuite); |
||
893 | } |
||
894 | }); |
||
895 | // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants. |
||
896 | suite.suites = suite.suites.filter(function (childSuite) { |
||
897 | return suite._onlySuites.indexOf(childSuite) !== -1 || filterOnly(childSuite); |
||
898 | }); |
||
899 | } |
||
900 | // Keep the suite only if there is something to run |
||
901 | return suite.tests.length || suite.suites.length; |
||
902 | } |
||
903 | |||
904 | /** |
||
905 | * Determines whether a suite has an `only` test or suite as a descendant. |
||
906 | * |
||
907 | * @param {Array} suite |
||
908 | * @returns {Boolean} |
||
909 | * @api private |
||
910 | */ |
||
911 | function hasOnly (suite) { |
||
912 | return suite._onlyTests.length || suite._onlySuites.length || suite.suites.some(hasOnly); |
||
913 | } |
||
914 | |||
915 | /** |
||
916 | * Filter leaks with the given globals flagged as `ok`. |
||
917 | * |
||
918 | * @api private |
||
919 | * @param {Array} ok |
||
920 | * @param {Array} globals |
||
921 | * @return {Array} |
||
922 | */ |
||
923 | View Code Duplication | function filterLeaks (ok, globals) { |
|
924 | return globals.filter(function (key) { |
||
925 | // Firefox and Chrome exposes iframes as index inside the window object |
||
926 | if (/^\d+/.test(key)) { |
||
927 | return false; |
||
928 | } |
||
929 | |||
930 | // in firefox |
||
931 | // if runner runs in an iframe, this iframe's window.getInterface method |
||
932 | // not init at first it is assigned in some seconds |
||
933 | if (global.navigator && (/^getInterface/).test(key)) { |
||
934 | return false; |
||
935 | } |
||
936 | |||
937 | // an iframe could be approached by window[iframeIndex] |
||
938 | // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak |
||
939 | if (global.navigator && (/^\d+/).test(key)) { |
||
940 | return false; |
||
941 | } |
||
942 | |||
943 | // Opera and IE expose global variables for HTML element IDs (issue #243) |
||
944 | if (/^mocha-/.test(key)) { |
||
945 | return false; |
||
946 | } |
||
947 | |||
948 | var matched = ok.filter(function (ok) { |
||
949 | if (~ok.indexOf('*')) { |
||
950 | return key.indexOf(ok.split('*')[0]) === 0; |
||
951 | } |
||
952 | return key === ok; |
||
953 | }); |
||
954 | return !matched.length && (!global.navigator || key !== 'onerror'); |
||
955 | }); |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * Array of globals dependent on the environment. |
||
960 | * |
||
961 | * @return {Array} |
||
962 | * @api private |
||
963 | */ |
||
964 | View Code Duplication | function extraGlobals () { |
|
965 | if (typeof process === 'object' && typeof process.version === 'string') { |
||
966 | var parts = process.version.split('.'); |
||
967 | var nodeVersion = parts.reduce(function (a, v) { |
||
968 | return a << 8 | v; |
||
969 | }); |
||
970 | |||
971 | // 'errno' was renamed to process._errno in v0.9.11. |
||
972 | |||
973 | if (nodeVersion < 0x00090B) { |
||
974 | return ['errno']; |
||
975 | } |
||
976 | } |
||
977 | |||
978 | return []; |
||
979 | } |
||
980 |