├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── contextProxy.js └── parallel.js ├── package-lock.json ├── package.json └── spec ├── fixtures ├── assertionFailure.js ├── contextProxy.js ├── contextSkip.js ├── contextTimeout.js ├── defaultTimeout.js ├── delay.js ├── disable.js ├── failure.js ├── hooks.js ├── hooksExample.js ├── limit.js ├── multiple.js ├── only.js ├── parallelOnly.js ├── parallelSkip.js ├── parentHooks.js ├── promiseRejection.js ├── rootHooks.js ├── skip.js ├── sync.js ├── syncTime.js └── uncaughtException.js └── spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | node_js: 6 | - "8" 7 | - "6" 8 | - "4" 9 | - "0.10" 10 | 11 | env: 12 | matrix: 13 | - mocha_version=2 14 | - mocha_version=3 15 | - mocha_version=4 16 | - mocha_version=5 17 | 18 | matrix: 19 | exclude: 20 | - node_js: "0.10" 21 | env: mocha_version=4 22 | - node_js: "0.10" 23 | env: mocha_version=5 24 | - node_js: "4" 25 | env: mocha_version=5 26 | 27 | before_script: 28 | - npm install "mocha@$mocha_version" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel St. Jules 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mocha.parallel 2 | 3 | Speed up your IO bound async specs by running them at the same time. Compatible 4 | with node 0.10+, and Mocha 2.3.5 - 5.2.x. 5 | 6 | [![Build Status](https://travis-ci.org/danielstjules/mocha.parallel.svg?branch=master)](https://travis-ci.org/danielstjules/mocha.parallel) 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm install --save-dev mocha.parallel 12 | ``` 13 | 14 | ## Overview 15 | 16 | ``` javascript 17 | /** 18 | * Generates a suite for parallel execution of individual specs. While each 19 | * spec is ran in parallel, specs resolve in series, leading to deterministic 20 | * output. Compatible with both callbacks and promises. Supports hooks, pending 21 | * or skipped specs/suites via parallel.skip() and it.skip(), but not nested 22 | * suites. parallel.only() and it.only() may be used to only wait on the 23 | * specified specs and suites. Runnable contexts are bound, so this.skip() 24 | * and this.timeout() may be used from within a spec. parallel.disable() 25 | * may be invoked to use mocha's default test behavior, and parallel.enable() 26 | * will re-enable the module. parallel.limit(n) can be used to limit the number 27 | * of specs running simultaneously. 28 | * 29 | * @example 30 | * parallel('setTimeout', function() { 31 | * it('test1', function(done) { 32 | * setTimeout(done, 500); 33 | * }); 34 | * it('test2', function(done) { 35 | * setTimeout(done, 500); 36 | * }); 37 | * }); 38 | * 39 | * @param {string} name Name of the function 40 | * @param {function} fn The test suite's body 41 | */ 42 | ``` 43 | 44 | ## Examples 45 | 46 | In the examples below, imagine that `setTimeout` is a function that performs 47 | some async IO with the specified delay. This could include requests to your 48 | http server using a module like `supertest` or `request`. Or maybe a headless 49 | browser using `zombie` or `nightmare`. 50 | 51 | #### Simple 52 | 53 | Rather than taking 1.5s, the specs below run in parallel, completing in just 54 | over 500ms. 55 | 56 | ``` javascript 57 | var parallel = require('mocha.parallel'); 58 | var Promise = require('bluebird'); 59 | 60 | parallel('delays', function() { 61 | it('test1', function(done) { 62 | setTimeout(done, 500); 63 | }); 64 | 65 | it('test2', function(done) { 66 | setTimeout(done, 500); 67 | }); 68 | 69 | it('test3', function() { 70 | return Promise.delay(500); 71 | }); 72 | }); 73 | ``` 74 | 75 | ``` 76 | delays 77 | ✓ test1 (500ms) 78 | ✓ test2 79 | ✓ test3 80 | 81 | 82 | 3 passing (512ms) 83 | ``` 84 | 85 | #### Isolation 86 | 87 | Individual parallel suites run in series and in isolation from each other. 88 | In the example below, the two specs in suite1 run in parallel, followed by 89 | those in suite2. 90 | 91 | ``` javascript 92 | var parallel = require('mocha.parallel'); 93 | 94 | parallel('suite1', function() { 95 | it('test1', function(done) { 96 | setTimeout(done, 500); 97 | }); 98 | 99 | it('test2', function(done) { 100 | setTimeout(done, 500); 101 | }); 102 | }); 103 | 104 | parallel('suite2', function() { 105 | it('test1', function(done) { 106 | setTimeout(done, 500); 107 | }); 108 | 109 | it('test2', function(done) { 110 | setTimeout(done, 500); 111 | }); 112 | }); 113 | ``` 114 | 115 | ``` 116 | suite1 117 | ✓ test1 (503ms) 118 | ✓ test2 119 | 120 | suite2 121 | ✓ test1 (505ms) 122 | ✓ test2 123 | 124 | 125 | 4 passing (1s) 126 | ``` 127 | 128 | #### Error handling 129 | 130 | Uncaught exceptions are associated with the spec that threw them, despite them 131 | all running at the same time. So debugging doesn't need to be too difficult! 132 | 133 | ``` javascript 134 | var parallel = require('mocha.parallel'); 135 | 136 | parallel('uncaught', function() { 137 | it('test1', function(done) { 138 | setTimeout(done, 500); 139 | }); 140 | 141 | it('test2', function(done) { 142 | setTimeout(function() { 143 | // Thrown while test1 is executing 144 | throw new Error('test'); 145 | }, 100); 146 | }); 147 | 148 | it('test3', function(done) { 149 | setTimeout(done, 500); 150 | }); 151 | }); 152 | ``` 153 | 154 | ``` 155 | uncaught 156 | ✓ test1 (501ms) 157 | 1) test2 158 | ✓ test3 159 | 160 | 161 | 2 passing (519ms) 162 | 1 failing 163 | 164 | 1) uncaught test2: 165 | Error: test 166 | at null._onTimeout (fixtures/uncaughtException.js:11:13) 167 | ``` 168 | 169 | #### Hooks 170 | 171 | Hook behavior may not be as intuitive when ran using this library. 172 | 173 | ``` javascript 174 | var parallel = require('mocha.parallel'); 175 | var assert = require('assert'); 176 | 177 | describe('suite', function() { 178 | var i = 0; 179 | 180 | beforeEach(function(done) { 181 | // Invoked twice, before either spec starts 182 | i++; 183 | done(); 184 | }); 185 | 186 | parallel('hooks', function() { 187 | beforeEach(function(done) { 188 | // Invoked twice, before either spec starts 189 | i++; 190 | done(); 191 | }); 192 | 193 | it('test1', function(done) { 194 | // Incremented by 4x beforeEach 195 | setTimeout(function() { 196 | assert.equal(i, 4); 197 | done(); 198 | }, 1000); 199 | }); 200 | 201 | it('test2', function(done) { 202 | // Incremented by 4x beforeEach 203 | setTimeout(function() { 204 | assert.equal(i, 4); 205 | done(); 206 | }, 1000); 207 | }); 208 | }); 209 | }); 210 | ``` 211 | 212 | ## Notes 213 | 214 | Debugging parallel execution can be more difficult as exceptions may be thrown 215 | from any of the running specs. Also, the use of the word "parallel" is in the 216 | same spirit as other nodejs async control flow libraries, such as 217 | https://github.com/caolan/async#parallel, https://github.com/creationix/step 218 | and https://github.com/tj/co#yieldables This library does not offer true 219 | parallelism using multiple threads/workers/fibers, or by spawning multiple 220 | processes. 221 | -------------------------------------------------------------------------------- /lib/contextProxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Proxy class for test and suite contexts. 3 | * 4 | * @param {Spec} [spec] 5 | */ 6 | function ContextProxy(spec) { 7 | this.spec = spec; 8 | this.config = {}; 9 | } 10 | 11 | /** 12 | * Set test timeout. 13 | * 14 | * @param {number} ms 15 | * @return {ContextProxy} 16 | */ 17 | ContextProxy.prototype.timeout = function(ms) { 18 | this.config.timeout = ms; 19 | if (!this.spec) return this; 20 | this.spec.timeout = setTimeout(function() { 21 | var error = new Error('timeout of ' + ms + 'ms exceeded. Ensure the ' + 22 | 'done() callback is being called in this test.'); 23 | error.stack = null; 24 | throw error; 25 | }, ms); 26 | return this; 27 | }; 28 | 29 | /** 30 | * Set test slowness threshold. 31 | * 32 | * @param {number} ms 33 | * @return {ContextProxy} 34 | */ 35 | ContextProxy.prototype.slow = function(ms) { 36 | this.config.slow = ms; 37 | return this; 38 | }; 39 | 40 | /** 41 | * Mark a test as skipped. 42 | * 43 | * @return {ContextProxy} 44 | */ 45 | ContextProxy.prototype.skip = function() { 46 | this.config.skip = true; 47 | return this; 48 | }; 49 | 50 | /** 51 | * Overrides original context behavior and configuration. 52 | * 53 | * @param {Context} ctx 54 | */ 55 | ContextProxy.prototype.patchContext = function(ctx) { 56 | if (this.spec && this.spec.timeout) { 57 | ctx.timeout(0); 58 | } else if (this.config.timeout) { 59 | ctx.timeout(this.config.timeout); 60 | } 61 | 62 | if (this.config.slow) { 63 | ctx.slow(this.config.slow); 64 | } 65 | 66 | if (this.config.skip) { 67 | ctx.skip(); 68 | } 69 | 70 | // Derive correct runnable duration 71 | if (this.spec && ctx.runnable) { 72 | var spec = this.spec; 73 | var originalDuration = ctx.runnable().duration; 74 | delete ctx.runnable().duration; 75 | Object.defineProperty(ctx.runnable(), 'duration', { 76 | get: function() { 77 | return spec.duration || originalDuration; 78 | }, 79 | set: function(duration) { 80 | originalDuration = duration; 81 | }, 82 | enumerable: true, 83 | configurable: true 84 | }); 85 | } 86 | }; 87 | 88 | module.exports = ContextProxy; 89 | -------------------------------------------------------------------------------- /lib/parallel.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var domain = require('domain'); 3 | var ContextProxy = require('./contextProxy'); 4 | var hookTypes = ['before', 'beforeEach', 'afterEach', 'after']; 5 | var Semaphore = require('semaphore'); 6 | var semaphore; 7 | 8 | /** 9 | * Generates a suite for parallel execution of individual specs. While each 10 | * spec is ran in parallel, specs resolve in series, leading to deterministic 11 | * output. Compatible with both callbacks and promises. Supports hooks, pending 12 | * or skipped specs/suites via parallel.skip() and it.skip(), but not nested 13 | * suites. parallel.only() and it.only() may be used to only wait on the 14 | * specified specs and suites. Runnable contexts are bound, so this.skip() 15 | * and this.timeout() may be used from within a spec. parallel.disable() 16 | * may be invoked to use mocha's default test behavior, and parallel.enable() 17 | * will re-enable the module. parallel.limit(n) can be used to limit the number 18 | * of specs running simultaneously. 19 | * 20 | * @example 21 | * parallel('setTimeout', function() { 22 | * it('test1', function(done) { 23 | * setTimeout(done, 500); 24 | * }); 25 | * it('test2', function(done) { 26 | * setTimeout(done, 500); 27 | * }); 28 | * }); 29 | * 30 | * @param {string} name Name of the function 31 | * @param {function} fn The test suite's body 32 | */ 33 | function parallel(name, fn) { 34 | _parallel(name, fn); 35 | } 36 | 37 | /** 38 | * Whether or not to enable parallel. If false, specs will be ran using 39 | * mocha's default suite behavior. 40 | * 41 | * @var {bool} 42 | */ 43 | var enabled = true; 44 | 45 | /** 46 | * Private function invoked by parallel. 47 | * 48 | * @param {string} name Name of the function 49 | * @param {function} fn The suite or test body 50 | * @param {string} [key] One of 'skip' or 'only' 51 | */ 52 | function _parallel(name, fn, key) { 53 | if (!enabled) { 54 | return (key) ? describe[key](name, fn) : describe(name, fn); 55 | } 56 | 57 | var specs = []; 58 | var hooks = {}; 59 | var restoreIt = patchIt(specs); 60 | var restoreHooks = patchHooks(hooks); 61 | var restoreUncaught; 62 | var parentHooks; 63 | var parallelCtx = new ContextProxy(); 64 | fn.call(parallelCtx); 65 | 66 | restoreIt(); 67 | restoreHooks(); 68 | 69 | hookTypes.forEach(function(key) { 70 | hooks[key] = hooks[key] || function() { 71 | return Promise.resolve(); 72 | }; 73 | }); 74 | 75 | var runSpec = function(spec) { 76 | // beforeEach/spec/afterEach are grouped as a cancellable promise 77 | // and ran as part of a domain 78 | domain.create().on('error', function(err) { 79 | spec.error = err; 80 | spec.promise.cancel(err); 81 | }).run(function() { 82 | if (spec.skip) { 83 | if (semaphore) semaphore.leave(); 84 | spec.promise = Promise.resolve(); 85 | return; 86 | } 87 | 88 | process.nextTick(function() { 89 | spec.promise = parentHooks.beforeEach() 90 | .cancellable() 91 | .then(hooks.beforeEach) 92 | .then(spec.getPromise) 93 | .then(function() { 94 | clearTimeout(spec.timeout); 95 | }) 96 | .then(hooks.afterEach) 97 | .then(parentHooks.afterEach) 98 | .finally(function() { 99 | if (semaphore) semaphore.leave(); 100 | }); 101 | }); 102 | }); 103 | }; 104 | 105 | var run = function() { 106 | // If it.only() was used, only invoke that subset of specs 107 | var onlySpecs = specs.filter(function(spec) { 108 | return spec.only; 109 | }); 110 | 111 | if (onlySpecs.length) { 112 | specs = onlySpecs; 113 | } 114 | 115 | specs.forEach(function(spec) { 116 | if (!semaphore) return runSpec(spec); 117 | 118 | semaphore.take(function() { 119 | runSpec(spec) 120 | }); 121 | }); 122 | }; 123 | 124 | (key ? describe[key] : describe)(name, function() { 125 | parallelCtx.patchContext(this); 126 | var parentContext = this; 127 | if (!specs.length) return; 128 | 129 | parentHooks = getParentHooks(parentContext); 130 | 131 | specs.forEach(function(spec) { 132 | if (spec.skip) { 133 | return it.skip(spec.name); 134 | } 135 | 136 | (spec.only ? it.only : it)(spec.name, function() { 137 | if (spec.error) throw spec.error; 138 | spec.ctx.patchContext(this); 139 | return spec.promise.then(function() { 140 | if (spec.error) throw spec.error; 141 | }); 142 | }); 143 | }); 144 | 145 | before(function() { 146 | disableEachHooks(parentContext); 147 | // Before hook exceptions are handled by mocha 148 | return hooks.before().then(function() { 149 | restoreUncaught = patchUncaught(); 150 | run(); 151 | }); 152 | }); 153 | 154 | after(function() { 155 | enableEachHooks(parentContext); 156 | // After hook errors are handled by mocha 157 | if (restoreUncaught) restoreUncaught(); 158 | return hooks.after(); 159 | }); 160 | }); 161 | } 162 | 163 | /** 164 | * Wrapper for mocha's describe.only() 165 | * 166 | * @param {string} name 167 | * @param {function} fn 168 | */ 169 | parallel.only = function(name, fn) { 170 | _parallel(name, fn, 'only'); 171 | }; 172 | 173 | /** 174 | * Wrapper for mocha's describe.skip() 175 | * 176 | * @param {string} name 177 | * @param {function} fn 178 | */ 179 | parallel.skip = function(name, fn) { 180 | _parallel(name, fn, 'skip'); 181 | }; 182 | 183 | /** 184 | * Limit the number of specs running simultaneously. 185 | * 186 | * @param {int} n 187 | */ 188 | parallel.limit = function(n) { 189 | n = parseInt(n, 10); 190 | if (n) semaphore = Semaphore(n); 191 | }; 192 | 193 | /** 194 | * Re-enables parallel if previously disabled. 195 | */ 196 | parallel.enable = function() { 197 | enabled = true; 198 | }; 199 | 200 | /** 201 | * Disables parallel, falling back to mocha's default test functionality. 202 | */ 203 | parallel.disable = function() { 204 | enabled = false; 205 | }; 206 | 207 | /** 208 | * Patches the global it() function used by mocha, and returns a function that 209 | * restores the original behavior when invoked. 210 | * 211 | * @param {Spec[]} specs Array on which to push specs 212 | * @returns {function} Function that restores the original it() behavior 213 | */ 214 | function patchIt(specs) { 215 | var original = it; 216 | var restore = function() { 217 | it = original; 218 | xit = it.skip; 219 | }; 220 | 221 | var createSpec = function(name, fn, opts) { 222 | opts = opts || {}; 223 | 224 | var spec = { 225 | name: name, 226 | getPromise: function() { 227 | var start = Date.now(); 228 | return createWrapper(fn, spec.ctx)().then(function(duration) { 229 | spec.duration = duration; 230 | }); 231 | }, 232 | duration: 0, 233 | only: opts.only || null, 234 | skip: opts.skip || null, 235 | timeout: null, 236 | error: null, 237 | promise: null 238 | }; 239 | 240 | spec.ctx = new ContextProxy(spec); 241 | specs.push(spec); 242 | }; 243 | 244 | it = function it(name, fn) { 245 | createSpec(name, fn, {skip: !fn}); 246 | }; 247 | 248 | xit = it.skip = function skip(name, fn) { 249 | createSpec(name, fn, {skip: true}); 250 | }; 251 | 252 | it.only = function only(name, fn) { 253 | createSpec(name, fn, {only: true}); 254 | }; 255 | 256 | return restore; 257 | } 258 | 259 | /** 260 | * Patches the global hook functions used by mocha, and returns a function 261 | * that restores the original behavior when invoked. 262 | * 263 | * @param {object} hooks Object on which to add hooks 264 | * @returns {function} Function that restores the original it() behavior 265 | */ 266 | function patchHooks(hooks) { 267 | var original = {}; 268 | var restore; 269 | 270 | hookTypes.map(function(key) { 271 | original[key] = global[key]; 272 | 273 | global[key] = function(title, fn) { 274 | // Hooks accept an optional title, though they're 275 | // ignored here for simplicity 276 | if (!fn) fn = title; 277 | hooks[key] = createWrapper(fn); 278 | }; 279 | }); 280 | 281 | restore = function() { 282 | hookTypes.forEach(function(key) { 283 | global[key] = original[key]; 284 | }); 285 | }; 286 | 287 | return restore; 288 | } 289 | 290 | /** 291 | * Returns a wrapper for a given runnable's fn, including specs or hooks. 292 | * Optionally binds the function handler to the passed context. Resolves 293 | * with the duration of the fn. 294 | * 295 | * @param {function} fn 296 | * @param {function} [ctx] 297 | * @returns {function} 298 | */ 299 | function createWrapper(fn, ctx) { 300 | return function() { 301 | return new Promise(function(resolve, reject) { 302 | var start = Date.now(); 303 | 304 | var cb = function(err) { 305 | if (err) return reject(err); 306 | resolve(Date.now() - start); 307 | }; 308 | 309 | // Wrap generator functions 310 | if (fn && fn.constructor.name === 'GeneratorFunction') { 311 | fn = Promise.coroutine(fn); 312 | } 313 | 314 | var res = fn.call(ctx || this, cb); 315 | 316 | // Synchronous spec, or using promises rather than callbacks 317 | if (!fn.length || (res && res.then)) { 318 | Promise.resolve(res).then(function() { 319 | resolve(Date.now() - start); 320 | }, reject); 321 | } 322 | }); 323 | }; 324 | } 325 | 326 | /** 327 | * Wraps the existing hooks into a series of promises. Returns an object with 328 | * two functions: beforeEach and afterEach. Each function returns a promise 329 | * that resolves once all parents hooks have completed, recursively. 330 | * 331 | * @param {Context} context 332 | * @returns {object} 333 | */ 334 | function getParentHooks(context) { 335 | var getOrderedHooks = function(context, type) { 336 | var hooks = [].concat(context['_' + type]).map(function(hook) { 337 | return createWrapper(hook.fn); 338 | }); 339 | 340 | if (!context.parent) return hooks; 341 | 342 | return hooks.concat(getOrderedHooks(context.parent, type)); 343 | }; 344 | 345 | return ['beforeEach', 'afterEach'].reduce(function(res, type) { 346 | var array = getOrderedHooks(context, type); 347 | // parent beforeEach runs before child beforeEach 348 | if (type === 'beforeEach') { 349 | array.reverse(); 350 | } 351 | 352 | res[type] = function() { 353 | var promise = Promise.resolve(); 354 | array.forEach(function(hook) { 355 | promise = promise.then(hook()); 356 | }); 357 | 358 | return promise; 359 | }; 360 | return res; 361 | }, {}); 362 | } 363 | 364 | /** 365 | * Given a mocha context object, recursively disables all beforeEach/afterEach 366 | * hooks for all parents suites. Necessary override when the parent hooks are 367 | * invoked. 368 | * 369 | * @param {Context} context 370 | */ 371 | function disableEachHooks(context) { 372 | context._disabledBeforeEach = context._beforeEach; 373 | context._disabledAfterEach = context._afterEach; 374 | context._beforeEach = []; 375 | context._afterEach = []; 376 | 377 | if (!context.parent) return; 378 | disableEachHooks(context.parent); 379 | } 380 | 381 | /** 382 | * Given a mocha context object, recursively re-enables all beforeEach and 383 | * afterEach hooks. 384 | * 385 | * @param {Context} context 386 | */ 387 | function enableEachHooks(context) { 388 | context._beforeEach= context._disabledBeforeEach; 389 | context._afterEach = context._disabledAfterEach; 390 | delete context._disabledBeforeEach; 391 | delete context._disabledAfterEach; 392 | 393 | if (!context.parent) return; 394 | disableEachHooks(context.parent); 395 | } 396 | 397 | /** 398 | * Removes mocha's uncaughtException handler, allowing exceptions to be handled 399 | * by domains during parallel spec execution, and all others to terminate the 400 | * process. 401 | * 402 | * @returns {function} Function that restores mocha's uncaughtException listener 403 | */ 404 | function patchUncaught() { 405 | var name = 'uncaughtException'; 406 | var originalListener = process.listeners(name).pop(); 407 | 408 | if (originalListener) { 409 | process.removeListener(name, originalListener); 410 | } 411 | 412 | return function() { 413 | if (!originalListener) return; 414 | process.on(name, originalListener); 415 | }; 416 | } 417 | 418 | Promise.onPossiblyUnhandledRejection(function() { 419 | // Stop bluebird from printing the unhandled rejections 420 | }); 421 | 422 | module.exports = parallel; 423 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha.parallel", 3 | "version": "0.15.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 11 | }, 12 | "bluebird": { 13 | "version": "2.11.0", 14 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", 15 | "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" 16 | }, 17 | "brace-expansion": { 18 | "version": "1.1.11", 19 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 20 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 21 | "requires": { 22 | "balanced-match": "1.0.0", 23 | "concat-map": "0.0.1" 24 | } 25 | }, 26 | "browser-stdout": { 27 | "version": "1.3.1", 28 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 29 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" 30 | }, 31 | "commander": { 32 | "version": "2.15.1", 33 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 34 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" 35 | }, 36 | "concat-map": { 37 | "version": "0.0.1", 38 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 39 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 40 | }, 41 | "debug": { 42 | "version": "3.1.0", 43 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 44 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 45 | "requires": { 46 | "ms": "2.0.0" 47 | } 48 | }, 49 | "diff": { 50 | "version": "3.5.0", 51 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 52 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" 53 | }, 54 | "dirmap": { 55 | "version": "0.0.2", 56 | "resolved": "https://registry.npmjs.org/dirmap/-/dirmap-0.0.2.tgz", 57 | "integrity": "sha1-f4Cn2ZLZ/q2talqww90okesF5+Q=", 58 | "dev": true 59 | }, 60 | "escape-string-regexp": { 61 | "version": "1.0.5", 62 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 63 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 64 | }, 65 | "fs.realpath": { 66 | "version": "1.0.0", 67 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 68 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 69 | }, 70 | "glob": { 71 | "version": "7.1.2", 72 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 73 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 74 | "requires": { 75 | "fs.realpath": "1.0.0", 76 | "inflight": "1.0.6", 77 | "inherits": "2.0.3", 78 | "minimatch": "3.0.4", 79 | "once": "1.4.0", 80 | "path-is-absolute": "1.0.1" 81 | } 82 | }, 83 | "growl": { 84 | "version": "1.10.5", 85 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 86 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" 87 | }, 88 | "has-flag": { 89 | "version": "3.0.0", 90 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 91 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 92 | }, 93 | "he": { 94 | "version": "1.1.1", 95 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 96 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" 97 | }, 98 | "inflight": { 99 | "version": "1.0.6", 100 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 101 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 102 | "requires": { 103 | "once": "1.4.0", 104 | "wrappy": "1.0.2" 105 | } 106 | }, 107 | "inherits": { 108 | "version": "2.0.3", 109 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 110 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 111 | }, 112 | "minimatch": { 113 | "version": "3.0.4", 114 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 115 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 116 | "requires": { 117 | "brace-expansion": "1.1.11" 118 | } 119 | }, 120 | "minimist": { 121 | "version": "0.0.8", 122 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 123 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 124 | }, 125 | "mkdirp": { 126 | "version": "0.5.1", 127 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 128 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 129 | "requires": { 130 | "minimist": "0.0.8" 131 | } 132 | }, 133 | "mocha": { 134 | "version": "5.2.0", 135 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 136 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 137 | "requires": { 138 | "browser-stdout": "1.3.1", 139 | "commander": "2.15.1", 140 | "debug": "3.1.0", 141 | "diff": "3.5.0", 142 | "escape-string-regexp": "1.0.5", 143 | "glob": "7.1.2", 144 | "growl": "1.10.5", 145 | "he": "1.1.1", 146 | "minimatch": "3.0.4", 147 | "mkdirp": "0.5.1", 148 | "supports-color": "5.4.0" 149 | } 150 | }, 151 | "ms": { 152 | "version": "2.0.0", 153 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 154 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 155 | }, 156 | "once": { 157 | "version": "1.4.0", 158 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 159 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 160 | "requires": { 161 | "wrappy": "1.0.2" 162 | } 163 | }, 164 | "path-is-absolute": { 165 | "version": "1.0.1", 166 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 167 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 168 | }, 169 | "semaphore": { 170 | "version": "1.1.0", 171 | "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", 172 | "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" 173 | }, 174 | "supports-color": { 175 | "version": "5.4.0", 176 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 177 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 178 | "requires": { 179 | "has-flag": "3.0.0" 180 | } 181 | }, 182 | "wrappy": { 183 | "version": "1.0.2", 184 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 185 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha.parallel", 3 | "version": "0.15.6", 4 | "description": "Run async mocha specs in parallel", 5 | "main": "lib/parallel.js", 6 | "scripts": { 7 | "test": "mocha spec/spec.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/danielstjules/mocha.parallel.git" 12 | }, 13 | "keywords": [ 14 | "mocha", 15 | "parallel", 16 | "async", 17 | "test", 18 | "spec" 19 | ], 20 | "author": "Daniel St. Jules ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/danielstjules/mocha.parallel/issues" 24 | }, 25 | "homepage": "https://github.com/danielstjules/mocha.parallel", 26 | "peerDependencies": { 27 | "mocha": ">=2.2.5" 28 | }, 29 | "dependencies": { 30 | "bluebird": "^2.9.34", 31 | "semaphore": "^1.0.5" 32 | }, 33 | "devDependencies": { 34 | "dirmap": "0.0.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/fixtures/assertionFailure.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert'); 3 | 4 | parallel('suite', function() { 5 | it('test1', function(done) { 6 | setTimeout(done, 100); 7 | }); 8 | 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | assert.equal(true, false); 12 | done(); 13 | }, 100); 14 | }); 15 | 16 | it('test3', function(done) { 17 | setTimeout(done, 100); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /spec/fixtures/contextProxy.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert'); 3 | 4 | parallel('suite', function() { 5 | this.timeout(3000); 6 | this.slow(2600); 7 | 8 | it('test1', function(done) { 9 | this.timeout(100); 10 | setTimeout(done, 500); 11 | }); 12 | 13 | it('test2', function(done) { 14 | this.skip(); 15 | setTimeout(done, 500); 16 | }); 17 | 18 | it('test3', function(done) { 19 | this.slow(200); 20 | setTimeout(done, 500); 21 | }); 22 | 23 | it('test4', function(done) { 24 | setTimeout(done, 2500); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /spec/fixtures/contextSkip.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 500); 6 | }); 7 | 8 | it('test2', function() { 9 | this.skip(); 10 | }); 11 | 12 | it('test3', function(done) { 13 | setTimeout(done, 500); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /spec/fixtures/contextTimeout.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | describe('parent', function() { 4 | this.timeout(0); 5 | 6 | parallel('suite', function() { 7 | it('test1', function(done) { 8 | setTimeout(done, 500); 9 | }); 10 | 11 | it('test2', function(done) { 12 | this.timeout(100); 13 | setTimeout(done, 500); 14 | }); 15 | 16 | it('test3', function(done) { 17 | setTimeout(done, 500); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /spec/fixtures/defaultTimeout.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var Promise = require('bluebird'); 3 | 4 | // Based on issue #13 5 | parallel('suite', function() { 6 | it('test1', function() { 7 | return Promise.delay(2500); 8 | }); 9 | 10 | it('test2', function() { 11 | return Promise.delay(2500); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /spec/fixtures/delay.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var Promise = require('bluebird'); 3 | 4 | parallel('delays', function() { 5 | it('test1', function(done) { 6 | setTimeout(done, 500); 7 | }); 8 | 9 | it('test2', function(done) { 10 | setTimeout(done, 500); 11 | }); 12 | 13 | it('test3', function() { 14 | return Promise.delay(500); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /spec/fixtures/disable.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | parallel.disable(); 3 | 4 | parallel('disable', function() { 5 | after(function() { 6 | parallel.enable(); 7 | }); 8 | 9 | it('test1', function(done) { 10 | setTimeout(done, 500); 11 | }); 12 | 13 | it('test2', function(done) { 14 | setTimeout(done, 500); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /spec/fixtures/failure.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 100); 6 | }); 7 | 8 | it('test2', function(done) { 9 | setTimeout(function() { 10 | return done(new Error('Expected error')); 11 | }, 100); 12 | }); 13 | 14 | it('test3', function(done) { 15 | setTimeout(done, 100); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /spec/fixtures/hooks.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert'); 3 | 4 | parallel('hooks', function() { 5 | var i = 0; 6 | 7 | before(function(done) { 8 | setTimeout(function() { 9 | assert.equal(i, 0); 10 | i++; 11 | done(); 12 | }, 100); 13 | }); 14 | 15 | beforeEach('hook with title', function(done) { 16 | setTimeout(function() { 17 | i++; 18 | done(); 19 | }, 50); 20 | }); 21 | 22 | afterEach(function(done) { 23 | setTimeout(function() { 24 | i++; 25 | done(); 26 | }, 50); 27 | }); 28 | 29 | after(function(done) { 30 | // Incremented by before, 2x beforeEach, 2x afterEach 31 | setTimeout(function() { 32 | assert.equal(i, 5); 33 | done(); 34 | }, 50); 35 | }); 36 | 37 | it('test1', function(done) { 38 | // Incremented by before and 2x beforeEach 39 | setTimeout(function() { 40 | assert.equal(i, 3); 41 | done(); 42 | }, 1000); 43 | }); 44 | 45 | it('test2', function(done) { 46 | // Incremented by before, 2x beforeEach 47 | setTimeout(function() { 48 | assert.equal(i, 3); 49 | done(); 50 | }, 1000); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /spec/fixtures/hooksExample.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert'); 3 | 4 | describe('suite', function() { 5 | var i = 0; 6 | 7 | beforeEach(function(done) { 8 | // Invoked twice, before either spec starts 9 | i++; 10 | done(); 11 | }); 12 | 13 | parallel('hooks', function() { 14 | beforeEach(function(done) { 15 | // Invoked twice, before either spec starts 16 | i++; 17 | done(); 18 | }); 19 | 20 | it('test1', function(done) { 21 | // Incremented by 4x beforeEach 22 | setTimeout(function() { 23 | assert.equal(i, 4); 24 | done(); 25 | }, 1000); 26 | }); 27 | 28 | it('test2', function(done) { 29 | // Incremented by 4x beforeEach 30 | setTimeout(function() { 31 | assert.equal(i, 4); 32 | done(); 33 | }, 1000); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /spec/fixtures/limit.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var Promise = require('bluebird'); 3 | 4 | parallel.limit(2); 5 | 6 | parallel('suite', function() { 7 | it('test1', function(done) { 8 | setTimeout(done, 500); 9 | }); 10 | 11 | it('test2', function(done) { 12 | setTimeout(done, 500); 13 | }); 14 | 15 | it('test3', function() { 16 | return Promise.delay(500); 17 | }); 18 | 19 | it('test4', function() { 20 | return Promise.delay(500); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /spec/fixtures/multiple.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var Promise = require('bluebird'); 3 | 4 | parallel('suite1', function() { 5 | it('test1', function(done) { 6 | setTimeout(done, 500); 7 | }); 8 | 9 | it('test2', function(done) { 10 | setTimeout(done, 500); 11 | }); 12 | }); 13 | 14 | parallel('suite2', function() { 15 | it('test1', function(done) { 16 | setTimeout(done, 500); 17 | }); 18 | 19 | it('test2', function(done) { 20 | setTimeout(done, 500); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /spec/fixtures/only.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 500); 6 | }); 7 | 8 | it.only('test2', function(done) { 9 | setTimeout(done, 500); 10 | }); 11 | 12 | it('test3', function(done) { 13 | console.log('should not run'); 14 | setTimeout(done, 500); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /spec/fixtures/parallelOnly.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite1', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 500); 6 | }); 7 | 8 | it('test2', function(done) { 9 | setTimeout(done, 500); 10 | }); 11 | 12 | it('test3', function(done) { 13 | setTimeout(done, 500); 14 | }); 15 | }); 16 | 17 | parallel.only('suite2', function() { 18 | it('test1', function(done) { 19 | setTimeout(done, 500); 20 | }); 21 | 22 | it('test2', function(done) { 23 | setTimeout(done, 500); 24 | }); 25 | 26 | it('test3', function(done) { 27 | setTimeout(done, 500); 28 | }); 29 | }); 30 | 31 | parallel('suite3', function() { 32 | it('test1', function(done) { 33 | setTimeout(done, 500); 34 | }); 35 | 36 | it('test2', function(done) { 37 | setTimeout(done, 500); 38 | }); 39 | 40 | it('test3', function(done) { 41 | setTimeout(done, 500); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /spec/fixtures/parallelSkip.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel.skip('suite', function() { 4 | it('test1', function(done) { 5 | console.log('should not be printed'); 6 | setTimeout(done, 3000); 7 | }); 8 | }); 9 | 10 | parallel('suite', function() { 11 | it('test4', function(done) { 12 | setTimeout(done, 500); 13 | }); 14 | 15 | it('test5', function(done) { 16 | setTimeout(done, 500); 17 | }); 18 | 19 | it('test6', function(done) { 20 | setTimeout(done, 500); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /spec/fixtures/parentHooks.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert'); 3 | 4 | describe('suiteA', function() { 5 | var i = 0; 6 | 7 | before(function(done) { 8 | setTimeout(function() { 9 | assert.equal(i, 0); 10 | i++; 11 | done(); 12 | }, 100); 13 | }); 14 | 15 | beforeEach(function(done) { 16 | process.stdout.write('suiteABeforeEach, '); 17 | setTimeout(function() { 18 | i++; 19 | done(); 20 | }, 50); 21 | }); 22 | 23 | describe('suiteB', function() { 24 | beforeEach(function(done) { 25 | process.stdout.write('suiteBBeforeEach, '); 26 | setTimeout(function() { 27 | i++; 28 | done(); 29 | }, 50); 30 | }); 31 | 32 | parallel('hooks', function() { 33 | beforeEach(function(done) { 34 | process.stdout.write('childBeforeEach, '); 35 | setTimeout(function() { 36 | i++; 37 | done(); 38 | }, 50); 39 | }); 40 | 41 | it('test1', function(done) { 42 | process.stdout.write('test1, '); 43 | // Incremented by before and 6x beforeEach 44 | setTimeout(function() { 45 | assert.equal(i, 7); 46 | done(); 47 | }, 1000); 48 | }); 49 | 50 | it('test2', function(done) { 51 | process.stdout.write('test2\n'); 52 | // Incremented by before, 6x beforeEach 53 | setTimeout(function() { 54 | assert.equal(i, 7); 55 | done(); 56 | }, 1000); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /spec/fixtures/promiseRejection.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var Promise = require('bluebird'); 3 | var assert = require('assert'); 4 | 5 | parallel('suite', function() { 6 | it('should throw an AssertionError', function() { 7 | return Promise.resolve().then(function() { 8 | assert(false) 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /spec/fixtures/rootHooks.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | var assert = require('assert') 3 | 4 | var i = 0; 5 | 6 | before('root before', function() { 7 | console.log('root before') 8 | i++; 9 | }); 10 | 11 | beforeEach('root beforeEach', function() { 12 | console.log('root beforeEach') 13 | i++; 14 | }); 15 | 16 | afterEach('root afterEach', function() { 17 | console.log('root afterEach') 18 | i++; 19 | }); 20 | 21 | after('root after', function() { 22 | console.log('root after') 23 | i++; 24 | }); 25 | 26 | parallel('first suite', function() { 27 | before(function() { 28 | console.log('parallel before') 29 | i++; 30 | }); 31 | 32 | beforeEach('hook with title', function() { 33 | console.log('parallel beforeEach') 34 | i++; 35 | }); 36 | 37 | afterEach(function() { 38 | console.log('parallel afterEach') 39 | i++; 40 | }); 41 | 42 | after(function() { 43 | console.log('parallel after') 44 | i++; 45 | }); 46 | 47 | it('test', function() { 48 | assert.equal(i, 4) 49 | }); 50 | }); 51 | 52 | parallel('second suite', function() { 53 | before(function() { 54 | console.log('parallel before') 55 | i++; 56 | }); 57 | 58 | beforeEach('hook with title', function() { 59 | console.log('parallel beforeEach') 60 | i++; 61 | }); 62 | 63 | afterEach(function() { 64 | console.log('parallel afterEach') 65 | i++; 66 | }); 67 | 68 | after(function() { 69 | console.log('parallel after') 70 | i++; 71 | }); 72 | 73 | it('test', function() { 74 | assert.equal(i, 10) 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /spec/fixtures/skip.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 500); 6 | }); 7 | 8 | it.skip('test2', function(done) { 9 | console.log('should not appear'); 10 | setTimeout(done, 500); 11 | }); 12 | 13 | it('test3', function(done) { 14 | setTimeout(done, 500); 15 | }); 16 | 17 | it('skip4') 18 | }); 19 | -------------------------------------------------------------------------------- /spec/fixtures/sync.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('suite', function() { 4 | beforeEach(function() { 5 | // sync 6 | }); 7 | 8 | it('test1', function(done) { 9 | setTimeout(done, 500); 10 | }); 11 | 12 | it('test2', function() { 13 | // sync 14 | }); 15 | 16 | it('test3', function(done) { 17 | setTimeout(done, 500); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /spec/fixtures/syncTime.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | /** 4 | * This fixture exists in reference to issue #30. The following test suite 5 | * previously reported: 6 | * 7 | * suite 8 | * ✓ a (110ms) 9 | * ✓ b (109ms) 10 | * ✓ c (52ms) 11 | * 12 | * but now correctly reports: 13 | * 14 | * suite 15 | * ✓ a 16 | * ✓ b (60ms) 17 | * ✓ c (46ms) 18 | */ 19 | parallel('suite', function() { 20 | this.slow(10); 21 | 22 | it('a', function(done) { 23 | done(); 24 | }); 25 | 26 | it('b', function(done) { 27 | var a; 28 | for (var i = 0; i <= 100000000; i++){ 29 | a = i; 30 | } 31 | done(); 32 | }); 33 | 34 | it('c', function(done) { 35 | var a; 36 | for (var i = 0; i <= 100000000; i++){ 37 | a = i; 38 | } 39 | done(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /spec/fixtures/uncaughtException.js: -------------------------------------------------------------------------------- 1 | var parallel = require('../../lib/parallel'); 2 | 3 | parallel('uncaught', function() { 4 | it('test1', function(done) { 5 | setTimeout(done, 500); 6 | }); 7 | 8 | it('test2', function(done) { 9 | setTimeout(function() { 10 | // Thrown while test1 is executing 11 | throw new Error('test'); 12 | }, 100); 13 | }); 14 | 15 | it('test3', function(done) { 16 | setTimeout(done, 500); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /spec/spec.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var assert = require('assert'); 3 | var path = require('path'); 4 | var dirmap = require('dirmap'); 5 | var fixtures = dirmap(path.resolve(__dirname, './fixtures'), true); 6 | var path = require('path'); 7 | 8 | describe('parallel', function() { 9 | this.timeout(4000); 10 | 11 | it('runs specs in parallel', function(done) { 12 | run(fixtures.delay, function(err, stdout, stderr) { 13 | if (err) return done(err); 14 | 15 | assert(!stderr.length); 16 | assert(stdout.indexOf('3 passing') !== -1); 17 | assert(stdout.indexOf('delays') !== -1); 18 | assertSubstrings(stdout, ['✓ test1', '✓ test2', '✓ test3']); 19 | assert(getRuntime(stdout) < 600); 20 | 21 | done(); 22 | }); 23 | }); 24 | 25 | it('can limit the number of specs running simultaneously', function(done) { 26 | run(fixtures.limit, function(err, stdout, stderr) { 27 | if (err) return done(err); 28 | 29 | assert(!stderr.length); 30 | assert(stdout.indexOf('4 passing') !== -1); 31 | assertSubstrings(stdout, ['✓ test1', '✓ test2', '✓ test3', '✓ test4']); 32 | assert.equal(getRuntime(stdout), 1); 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | it('isolates parallel suite execution', function(done) { 39 | run(fixtures.multiple, function(err, stdout, stderr) { 40 | if (err) return done(err); 41 | 42 | assert(!stderr.length); 43 | assert(stdout.indexOf('4 passing') !== -1); 44 | assertOneSecond(stdout); 45 | 46 | done(); 47 | }); 48 | }); 49 | 50 | it('supports synchronous hooks/specs', function(done) { 51 | run(fixtures.sync, function(err, stdout, stderr) { 52 | if (err) return done(err); 53 | 54 | assert(!stderr.length); 55 | assert(stdout.indexOf('3 passing') !== -1); 56 | 57 | done(); 58 | }); 59 | }); 60 | 61 | it('supports all mocha hooks', function(done) { 62 | run(fixtures.hooks, function(err, stdout, stderr) { 63 | if (err) return done(err); 64 | 65 | assert(!stderr.length); 66 | assert(stdout.indexOf('2 passing') !== -1); 67 | assertOneSecond(stdout); 68 | 69 | done(); 70 | }); 71 | }); 72 | 73 | it('correctly supports root hooks', function(done) { 74 | run(fixtures.rootHooks, function(err, stdout, stderr) { 75 | if (err) return done(err); 76 | 77 | assert(!stderr.length); 78 | assert(stdout.indexOf('2 passing') !== -1); 79 | 80 | done(); 81 | }); 82 | }); 83 | 84 | it('supports parent hooks', function(done) { 85 | var hookStr = 'suiteABeforeEach, suiteBBeforeEach, suiteABeforeEach, ' + 86 | 'suiteBBeforeEach, childBeforeEach, childBeforeEach'; 87 | 88 | run(fixtures.parentHooks, function(err, stdout, stderr) { 89 | if (err) return done(err); 90 | 91 | assert(!stderr.length); 92 | assert(stdout.indexOf('2 passing') !== -1); 93 | assert(stdout.indexOf(hookStr) !== -1); 94 | assertOneSecond(stdout); 95 | 96 | done(); 97 | }); 98 | }); 99 | 100 | it('correctly handles the readme example', function(done) { 101 | run(fixtures.hooksExample, function(err, stdout, stderr) { 102 | if (err) return done(err); 103 | 104 | assert(!stderr.length); 105 | assert(stdout.indexOf('2 passing') !== -1); 106 | assertOneSecond(stdout); 107 | 108 | done(); 109 | }); 110 | }); 111 | 112 | it('correctly handles spec failures', function(done) { 113 | run(fixtures.failure, function(err, stdout, stderr) { 114 | assert(err); 115 | assert(!stderr.length); 116 | 117 | stdout = stdout.replace(/\n\s+/g, ' '); // remove new lines 118 | assertSubstrings(stdout, [ 119 | '2 passing', 120 | '1 failing', 121 | '1) suite test2:', 122 | 'Error: Expected error', 123 | 'fixtures/failure.js:10' 124 | ]); 125 | 126 | done(); 127 | }); 128 | }); 129 | 130 | it('handles async assertion errors', function(done) { 131 | run(fixtures.assertionFailure, function(err, stdout, stderr) { 132 | assert(err); 133 | assert(!stderr.length); 134 | 135 | stdout = stdout.replace(/\n\s+/g, ' '); // remove new lines 136 | assertSubstrings(stdout, [ 137 | '2 passing', 138 | '1 failing', 139 | '1) suite test2:', 140 | 'true == false', 141 | 'fixtures/assertionFailure.js:11' 142 | ]); 143 | 144 | done(); 145 | }); 146 | }); 147 | 148 | it('links uncaught exceptions to the spec that threw them', function(done) { 149 | run(fixtures.uncaughtException, function(err, stdout, stderr) { 150 | assert(err); 151 | assert(!stderr.length); 152 | 153 | stdout = stdout.replace(/\n\s+/g, ' '); // remove new lines 154 | assertSubstrings(stdout, [ 155 | '2 passing', 156 | '1 failing', 157 | '1) uncaught test2:', 158 | 'fixtures/uncaughtException.js:11' 159 | ]); 160 | 161 | done(); 162 | }); 163 | }); 164 | 165 | it('supports it.skip for pending specs', function(done) { 166 | run(fixtures.skip, function(err, stdout, stderr) { 167 | if (err) return done(err); 168 | 169 | assert(!stderr.length); 170 | assert(stdout.indexOf('2 passing') !== -1); 171 | assert(stdout.indexOf('2 pending') !== -1); 172 | assert(stdout.indexOf('should not appear') === -1); 173 | 174 | done(); 175 | }); 176 | }); 177 | 178 | it('supports parallel.skip for pending suites', function(done) { 179 | run(fixtures.parallelSkip, function(err, stdout, stderr) { 180 | if (err) return done(err); 181 | 182 | assert(!stderr.length); 183 | assert(stdout.indexOf('should not be printed') === -1); 184 | assert(stdout.indexOf('3 passing') !== -1); 185 | assert(stdout.indexOf('1 pending') !== -1); 186 | 187 | done(); 188 | }); 189 | }); 190 | 191 | it('supports it.only for specs', function(done) { 192 | run(fixtures.only, function(err, stdout, stderr) { 193 | if (err) return done(err); 194 | 195 | assert(!stderr.length); 196 | assert(stdout.indexOf('should not run') === -1); 197 | assert(stdout.indexOf('1 passing') !== -1); 198 | 199 | done(); 200 | }); 201 | }); 202 | 203 | it('supports parallel.only for suites', function(done) { 204 | run( fixtures.parallelOnly, function(err, stdout, stderr) { 205 | if (err) return done(err); 206 | 207 | assert(!stderr.length); 208 | assert(stdout.indexOf('3 passing') !== -1); 209 | 210 | done(); 211 | }); 212 | }); 213 | 214 | it('supports this.skip() from a spec', function(done) { 215 | run(fixtures.contextSkip, function(err, stdout, stderr) { 216 | if (err) return done(err); 217 | 218 | assert(!stderr.length); 219 | assert(stdout.indexOf('2 passing') !== -1); 220 | assert(stdout.indexOf('1 pending') !== -1); 221 | 222 | done(); 223 | }); 224 | }); 225 | 226 | it('supports this.timeout() from a spec', function(done) { 227 | run(fixtures.contextTimeout, function(err, stdout, stderr) { 228 | assert(err); 229 | assert(!stderr.length); 230 | 231 | stdout = stdout.replace(/\n\s+/g, ' '); // remove new lines 232 | assertSubstrings(stdout, [ 233 | '2 passing', 234 | '1 failing', 235 | '1) parent suite test2:', 236 | 'timeout of 100ms exceeded. Ensure the done()', 237 | 'callback is being called in this test.' 238 | ]); 239 | 240 | done(); 241 | }); 242 | }); 243 | 244 | it('supports parallel.disable() for disabling functionality', function(done) { 245 | run(fixtures.disable, function(err, stdout, stderr) { 246 | if (err) return done(err); 247 | 248 | assert(!stderr.length); 249 | assertSubstrings(stdout, ['2 passing', 'disable', '✓ test1', '✓ test2']); 250 | assert.equal(getRuntime(stdout), 1); 251 | 252 | done(); 253 | }); 254 | }); 255 | 256 | 257 | it('supports timeout/slow/skip from specs and suites', function(done) { 258 | run(fixtures.contextProxy, function(err, stdout, stderr) { 259 | stdout = stdout.replace(/\n\s+/g, ' '); // remove new lines 260 | assert(err); 261 | assert(!stderr.length); 262 | assert(stdout.indexOf('2 passing') !== -1); 263 | assert(stdout.indexOf('1 pending') !== -1); 264 | assert(stdout.indexOf('1 failing') !== -1); 265 | assert(stdout.indexOf('1) suite test1:') !== -1); 266 | assert(stdout.indexOf('timeout of 100ms exceeded') !== -1); 267 | 268 | done(); 269 | }); 270 | }); 271 | 272 | it('correctly handles default timeout', function(done) { 273 | run(fixtures.defaultTimeout, function(err, stdout, stderr) { 274 | stdout = stdout.replace(/\n\s+/g, ' ').toLowerCase(); 275 | assert(err); 276 | assert(!stderr.length); 277 | assert(stdout.indexOf('0 passing') !== -1); 278 | assert(stdout.indexOf('2 failing') !== -1); 279 | assert(stdout.indexOf('1) suite test1:') !== -1); 280 | assert(stdout.indexOf('2) suite test2:') !== -1); 281 | var i = stdout.indexOf('timeout of 2000ms exceeded'); 282 | assert(i !== -1); 283 | i = stdout.indexOf('timeout of 2000ms exceeded', i + 1); 284 | assert(i !== -1); 285 | 286 | done(); 287 | }); 288 | }); 289 | 290 | it('correctly reports duration for synchronous tests', function(done) { 291 | run(fixtures.syncTime, function(err, stdout, stderr) { 292 | if (err) return done(err); 293 | 294 | assert(!stderr.length); 295 | assert.equal(stdout.indexOf('a ('), -1); 296 | done(); 297 | }); 298 | }); 299 | 300 | it('correctly handles promise rejections', function(done) { 301 | run(fixtures.promiseRejection, function(err, stdout, stderr) { 302 | assert(err); 303 | assert(stdout.indexOf('false == true') !== -1); 304 | done(); 305 | }); 306 | }); 307 | }); 308 | 309 | /** 310 | * Runs mocha with the supplied argumentss, and passes the resulting stdout and 311 | * stderr to the callback. 312 | * 313 | * @param {...string} args 314 | * @param {function} fn 315 | */ 316 | function run() { 317 | var bin = path.resolve(__dirname, '../node_modules/.bin/mocha'); 318 | var args = Array.prototype.slice.call(arguments); 319 | var fn = args.pop(); 320 | var cmd = [bin].concat(args).join(' ') + ' --no-colors'; 321 | exec(cmd, fn); 322 | } 323 | 324 | /** 325 | * Returns mocha's runtime given the stdout of a run. 326 | * 327 | * @param {string} str 328 | * @returns {int} 329 | */ 330 | function getRuntime(str) { 331 | var timeStr = str.match(/passing \((\d+)\w+\)/)[1]; 332 | return parseInt(timeStr, 10); 333 | } 334 | 335 | /** 336 | * Asserts that each substring is present in the supplied string. 337 | * 338 | * @param {string} str 339 | * @param {string[]} substrings 340 | */ 341 | function assertSubstrings(str, substrings) { 342 | substrings.forEach(function(substring) { 343 | assert(str.indexOf(substring) !== -1, str + 344 | ' - string does not contain: ' + substring); 345 | }); 346 | } 347 | 348 | /** 349 | * Asserts that a given test suite ran for one second, given mocha's stdout. 350 | * 351 | * @param {string} stdout 352 | */ 353 | function assertOneSecond(stdout) { 354 | var timeStr = stdout.match(/passing \((\d+)s\)/)[1]; 355 | assert(parseInt(timeStr, 10) === 1); 356 | } 357 | --------------------------------------------------------------------------------