| 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 |