node_modules/mocha/lib/runner.js   F
last analyzed

Complexity

Total Complexity 153
Complexity/F 2.64

Size

Lines of Code 979
Function Count 58

Duplication

Duplicated Lines 11
Ratio 1.12 %

Importance

Changes 0
Metric Value
cc 0
eloc 431
nc 442368
dl 11
loc 979
rs 2
c 0
b 0
f 0
wmc 153
mnd 4
bc 154
fnc 58
bpm 2.6551
cpm 2.6379
noi 22

25 Functions

Rating   Name   Duplication   Size   Complexity  
A Runner.fail 1 22 5
A Runner.grepTotal 0 16 1
A Runner.hooks 0 25 1
B Runner.runTests 1 146 1
A Runner.hookUp 0 4 1
B Runner.runSuite 1 78 4
A runner.js ➔ filterOnly 1 23 2
A runner.js ➔ extraGlobals 1 16 4
B Runner.runTest 1 27 6
A Runner.run 0 50 3
A runner.js ➔ alwaysFalse 0 3 1
A runner.js ➔ Runner 0 19 1
A runner.js ➔ filterLeaks 1 34 1
A Runner.failHook 0 11 4
A Runner.parents 0 9 2
A runner.js ➔ hasOnly 0 3 1
D Runner.uncaught 1 62 12
A Runner.grep 0 7 1
A Runner.globalProps 0 13 3
A Runner.hookDown 0 4 1
A Runner.hook 1 55 1
A Runner.abort 0 6 1
A Runner.globals 0 8 2
B runner.js ➔ cleanSuiteReferences 1 27 6
B Runner.checkGlobals 1 27 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

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:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
330
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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());
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
367
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
421
  var self = this;
422
  var test = this.test;
423
424
  if (!test) {
425
    return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
426
  }
427
  if (this.forbidOnly && hasOnly(this.parents().reverse()[0] || this.suite)) {
428
    fn(new Error('`.only` forbidden'));
429
    return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
443
  } catch (err) {
444
    fn(err);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
593
      });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
594
    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
      });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
656
    } else {
657
      self.runSuite(curr, next);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
689
  });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
725
  }
726
727
  runnable.clearTimeout();
728
729
  // Ignore errors if complete or pending
730
  if (runnable.state || runnable.isPending()) {
731
    return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
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');
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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 () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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