karma-parallel
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -17,2 +17,3 @@ // Karma configuration | ||
'karma-jasmine', | ||
'karma-coverage', | ||
require('.') | ||
@@ -40,3 +41,3 @@ ], | ||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter | ||
reporters: ['progress'], | ||
reporters: ['progress', 'coverage'], | ||
@@ -48,2 +49,13 @@ parallelOptions: { | ||
preprocessors: { | ||
// source files, that you wanna generate coverage for | ||
// do not include tests or libraries | ||
// (these files will be instrumented by Istanbul) | ||
'test/**/*.js': ['coverage'] // coverage is loaded from karma-coverage by karma-sharding | ||
}, | ||
// optionally, configure the reporter | ||
coverageReporter: { | ||
type : 'html', | ||
dir : 'coverage/' | ||
}, | ||
@@ -50,0 +62,0 @@ // web server port |
@@ -12,11 +12,14 @@ 'use strict'; | ||
config.nextShardIndex = {}; | ||
config.browserIdAlias = {}; | ||
config.shardStrategy = config.shardStrategy || 'round-robin'; | ||
config.executors = config.executors || require('os').cpus().length - 1; | ||
config.aggregatedReporterTest = ('aggregatedReporterTest' in config) ? config.aggregatedReporterTest : /coverage|istanbul/i; | ||
config.executors = config.executors || Math.max(1, require('os').cpus().length - 1); | ||
return config; | ||
} | ||
function setupMiddleware(fullConfig) { | ||
function setupMiddleware(log, fullConfig) { | ||
// ensure we load our middleware before karma's middleware for sharding | ||
fullConfig.beforeMiddleware = fullConfig.beforeMiddleware ? fullConfig.beforeMiddleware : []; | ||
if (fullConfig.beforeMiddleware.indexOf('parallel') === -1) { | ||
log.debug('adding "parallel" beforeMiddleware to configuration'); | ||
fullConfig.beforeMiddleware.unshift('parallel'); | ||
@@ -32,3 +35,3 @@ } | ||
browsers.push.apply(browsers, shardedBrowsers); | ||
} | ||
} | ||
browsers.sort(); | ||
@@ -39,19 +42,14 @@ } | ||
function handleBrowserRegister(config, browser) { | ||
// TODO: Browser unregister?? | ||
// We likely need to reset our shard indexes and browser-id aliases | ||
// if we restart our browsers or if connections get reset. | ||
function handleBrowserRegister(log, config, browser) { | ||
// Create a alias Id for each browser.name. Used in identifying coverage reports | ||
config.browserIdAlias[browser.name] = config.browserIdAlias[browser.name] || Math.floor(Math.random()*Date.now()); | ||
config.nextShardIndex[browser.name] = config.nextShardIndex[browser.name] || 0; | ||
config.shardIndexMap[browser.id] = config.nextShardIndex[browser.name]; | ||
log.debug(`registering browser id ${browser.id} with aggregated browser id ${config.browserIdAlias[browser.name]} at shard index ${config.shardIndexMap[browser.id]}`); | ||
config.nextShardIndex[browser.name]++; | ||
} | ||
function generateEmitter(emitter, fullConfig, config) { | ||
const originalEmit = emitter.emit; | ||
emitter.emit = function (event, entry) { | ||
switch(event) { | ||
case 'browser_register': | ||
handleBrowserRegister(config, entry); | ||
break; | ||
} | ||
return originalEmit.apply(emitter, arguments); | ||
}; | ||
} | ||
@@ -68,7 +66,5 @@ module.exports = function(/* config */fullConfig, emitter, logger) { | ||
const config = getConfig(fullConfig); | ||
setupMiddleware(fullConfig); | ||
setupMiddleware(log, fullConfig); | ||
setBrowserCount(config, fullConfig.browsers, log); | ||
// Intercepting the file_list_modified event as Vojta Jina describes here: | ||
// https://github.com/karma-runner/karma/issues/851#issuecomment-30290071 | ||
generateEmitter(emitter, fullConfig, config, log); | ||
emitter.on('browser_register', (browser) => handleBrowserRegister(log, config, browser)); | ||
}; |
@@ -5,7 +5,6 @@ 'use strict'; | ||
const parallel = { | ||
module.exports = { | ||
'framework:parallel': ['type', require('./framework')], | ||
'middleware:parallel': ['factory', require('./middleware')] | ||
'middleware:parallel': ['factory', require('./middleware')], | ||
'reporter:parallel-coverage': ['type', require('./reporter')] | ||
}; | ||
module.exports = Object.assign({}, /*coverage, */parallel); |
@@ -7,2 +7,3 @@ 'use strict'; | ||
const fs = require('fs'); | ||
const _ = require('lodash'); | ||
@@ -15,6 +16,8 @@ const karmaParallelScriptName = 'karma-parallelizer.js'; | ||
function setBrowserIdCookie(request, response) { | ||
function setBrowserIdCookie(log, request, response) { | ||
if (request.url.indexOf('/?id=') === 0) { | ||
const id = idParamExtractor.exec(request.url)[1]; | ||
response.setHeader('Set-Cookie', `karmaParallelBrowser.id=${id};`); | ||
const cookie = `karmaParallelBrowser.id=${id};`; | ||
log.debug(`setting cookie "${cookie}"`); | ||
response.setHeader('Set-Cookie', cookie); | ||
} | ||
@@ -28,5 +31,5 @@ } | ||
function writeKarmaSharderInfo(config, request, response) { | ||
function writeKarmaSharderInfo(log, config, request, response) { | ||
const id = getBrowserIdCookie(request); | ||
const payload = { | ||
const payload = JSON.stringify({ | ||
shouldShard: !!id && config.shardIndexMap.hasOwnProperty(id), | ||
@@ -36,15 +39,44 @@ shardIndex: config.shardIndexMap[id], | ||
shardStrategy: config.shardStrategy | ||
}; | ||
}); | ||
log.debug(`interpolating parallel shard data in script. Browser: ${id}. Data: ${payload}`); | ||
response.writeHead(200, {'Content-Type': 'application/javascript'}); | ||
response.end(karmaParallelScript.replace('%KARMA_SHARD_INFO%', JSON.stringify(payload))); | ||
response.end(karmaParallelScript.replace('%KARMA_SHARD_INFO%', payload)); | ||
} | ||
module.exports = function(/* config */fullConfig, /* config.parallelOptions */config) { | ||
function setupCoverageReporters(log, config, reporters) { | ||
// Look for possible coverage reporters and remove them from the reporters list. | ||
// They will get instantiated from our reporter | ||
if (!config.aggregatedReporterTest) { | ||
log.debug('skipping reporter aggregation'); | ||
return; | ||
} | ||
if (_.isRegExp(config.aggregatedReporterTest)) { | ||
config.aggregatedReporterTest = config.aggregatedReporterTest.test.bind(config.aggregatedReporterTest); | ||
} | ||
// Remove our reporter in case it was added explicitly | ||
_.pull(reporters, 'parallel-coverage'); | ||
config.coverageReporters = _.remove(reporters, config.aggregatedReporterTest); | ||
if (!_.isEmpty(config.coverageReporters)) { | ||
log.debug('reporter aggregation setup for ' + config.coverageReporters.join(', ')); | ||
reporters.push('parallel-coverage'); | ||
} else { | ||
log.debug('no reporters found for aggregation'); | ||
} | ||
} | ||
module.exports = function(logger, /* config */fullConfig, /* config.parallelOptions */config) { | ||
const log = logger.create('middleware:parallel'); | ||
setupCoverageReporters(log, config, fullConfig.reporters); | ||
return function (request, response, next) { | ||
// Responsible for finding the id of the browser and saving it as a cookie so all future requests can access it | ||
setBrowserIdCookie(request, response); | ||
setBrowserIdCookie(log, request, response); | ||
// Intercept the request for the actual sharding script so we can interpolate the browser-specific shard data in it | ||
if (request.url.indexOf(karmaParallelScriptName) !== -1) { | ||
return writeKarmaSharderInfo(config, request, response); | ||
return writeKarmaSharderInfo(log, config, request, response); | ||
} | ||
@@ -51,0 +83,0 @@ |
@@ -11,3 +11,5 @@ { | ||
], | ||
"dependencies": {}, | ||
"dependencies": { | ||
"istanbul": "^0.4.5" | ||
}, | ||
"description": "A Karma JS Framework to support sharding tests to run in parallel across multiple browsers", | ||
@@ -20,2 +22,3 @@ "devDependencies": { | ||
"karma-chrome-launcher": "^2.2.0", | ||
"karma-coverage": "1.1.1", | ||
"karma-jasmine": "^1.1.0" | ||
@@ -33,3 +36,3 @@ }, | ||
"karma", | ||
"karma-parllel", | ||
"karma-parallel", | ||
"karma-sharding", | ||
@@ -60,3 +63,3 @@ "karma-plugin" | ||
}, | ||
"version": "0.1.2" | ||
"version": "0.2.0" | ||
} |
@@ -10,3 +10,3 @@ # karma-parallel | ||
> A Karma JS plugin to support sharding tests to run in parallel across multiple browsers. | ||
> A Karma JS plugin to support sharding tests to run in parallel across multiple browsers. Now supporting code coverage! | ||
@@ -16,6 +16,6 @@ # Overview | ||
This is intended to speed up the time it takes to run unit tests by taking advantage of multiple cores. From a single | ||
karma server, multiple instances of a browser are spun up. Each browser downloads all of the spec files, but when a | ||
`describe` block is encountered, the browsers deterministically decide if that block should run in the given browser. | ||
karma server, multiple instances of a browser are spun up. Each browser downloads all of the spec files, but when a | ||
`describe` block is encountered, the browsers deterministically decide if that block should run in the given browser. | ||
This leads to a way to split up unit tests across multiple browsers without changing any build processes. | ||
This leads to a way to split up unit tests across multiple browsers without changing any build processes. | ||
@@ -29,3 +29,3 @@ ## Installation | ||
```bash | ||
npm install karma-parallel --save-dev | ||
npm i karma-parallel --save-dev | ||
``` | ||
@@ -49,8 +49,8 @@ | ||
// NOTE: 'parallel' must be the first framework in the list | ||
frameworks: ['parallel', 'mocha' /* or 'jasmine' */, ], | ||
frameworks: ['parallel', 'mocha' /* or 'jasmine' */], | ||
parallelOptions: { | ||
executors: 4, // Defaults to cpu-count - 1 | ||
shardStrategy: 'round-robin' | ||
// shardStrategy: 'description-length' | ||
// shardStrategy: 'description-length' | ||
} | ||
@@ -67,13 +67,17 @@ }); | ||
`parallelOptions.executors [int=cpu_cores-1]`: The number of browser instances to | ||
use to test. If you test on multiple types of browsers, this spin up the number of | ||
executors for each browser type. | ||
use to test. If you test on multiple types of browsers, this spin up the number of | ||
executors for each browser type. | ||
`parallelOptions.shardStyle [string='round-robin']`: This plugin works by | ||
overriding the test suite `describe()` function. When it encounters a describe, it | ||
must decide if it will skip the tests inside of it, or not. | ||
`parallelOptions.shardStyle [string='round-robin']`: This plugin works by | ||
overriding the test suite `describe()` function. When it encounters a describe, it | ||
must decide if it will skip the tests inside of it, or not. | ||
* The `round-robin` style will only take every `executors` test suite and skip the ones in between. | ||
* The `description-length` deterministically checks the length of the description for each test suite use a modulo of the number of executors. | ||
* The `description-length` deterministically checks the length of the description for each test suite use a modulo of the number of executors. | ||
`parallelOptions.aggregatedReporterTest [(reporter)=>boolean|regex=/coverage|istanbul/i]`: This is an | ||
optional regex or function used to determine if a reporter needs to only received aggregated events from the browser shards. It is used to ensure coverage reporting is accurate | ||
amongst all the shards of a browser. Set to null to disable aggregated reporting. | ||
## Important Notes | ||
@@ -83,9 +87,8 @@ | ||
If this plugin discovers that you have focused some tests (fit, it.only, etc...) in other browser instances, it will add an extra focused test in the current browser instance to limit the running of the tests in the given browser. Similarly, when dividing up the tests, if there are not enough tests for a given browser, it will add an extra test to prevent karma from failing due to no running tests. | ||
If this plugin discovers that you have focused some tests (fit, it.only, etc...) in other browser instances, it will add an extra focused test in the current browser instance to limit the running of the tests in the given browser. Similarly, when dividing up the tests, if there are not enough tests for a given browser, it will add an extra test to prevent karma from failing due to no running tests. | ||
**What about code coverage?** | ||
**Code Coverage** | ||
Currently, code-coverage reports are not supported, although soon they will be. Stay tuned (or make a PR)! | ||
Code coverage support is acheived by aggregating the code coverage reports from each browser into a single coverage report. We accomplish this by wrapping the coverage reporters with an aggregate reporter. By default, we only wrap reporters that pass the test `parallelOptions.aggregatedReporterTest`. It should all *just work*. | ||
---- | ||
@@ -99,11 +102,11 @@ | ||
This similar project works by splitting up the actual spec files across the browser instances. | ||
This similar project works by splitting up the actual spec files across the browser instances. | ||
Pros: | ||
Pros: | ||
* Reduces memory by only loading *some* of the spec files in each browser instance | ||
Cons: | ||
Cons: | ||
* Requires the spec files to reside in separate files, meaning it is not compatible with bundlers such | ||
* Requires the spec files to reside in separate files, meaning it is not compatible with bundlers such | ||
as [`karma-webpack`](https://github.com/webpack-contrib/karma-webpack) or [`karma-browserify`](https://github.com/nikku/karma-browserify) | ||
@@ -110,0 +113,0 @@ |
35426
24
611
111
1
7
+ Addedistanbul@^0.4.5
+ Addedabbrev@1.0.9(transitive)
+ Addedamdefine@1.0.1(transitive)
+ Addedargparse@1.0.10(transitive)
+ Addedasync@1.5.2(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addeddeep-is@0.1.4(transitive)
+ Addedescodegen@1.8.1(transitive)
+ Addedesprima@2.7.34.0.1(transitive)
+ Addedestraverse@1.9.3(transitive)
+ Addedesutils@2.0.3(transitive)
+ Addedfast-levenshtein@2.0.6(transitive)
+ Addedglob@5.0.15(transitive)
+ Addedhandlebars@4.7.8(transitive)
+ Addedhas-flag@1.0.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedistanbul@0.4.5(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedlevn@0.3.0(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedneo-async@2.6.2(transitive)
+ Addednopt@3.0.6(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedoptionator@0.8.3(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedprelude-ls@1.1.2(transitive)
+ Addedresolve@1.1.7(transitive)
+ Addedsource-map@0.2.00.6.1(transitive)
+ Addedsprintf-js@1.0.3(transitive)
+ Addedsupports-color@3.2.3(transitive)
+ Addedtype-check@0.3.2(transitive)
+ Addeduglify-js@3.19.3(transitive)
+ Addedwhich@1.3.1(transitive)
+ Addedword-wrap@1.2.5(transitive)
+ Addedwordwrap@1.0.0(transitive)
+ Addedwrappy@1.0.2(transitive)