Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

karma-coverage

Package Overview
Dependencies
Maintainers
3
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

karma-coverage - npm Package Compare versions

Comparing version 0.4.0 to 0.4.1

lib/coverage-map.js

18

CHANGELOG.md

@@ -0,1 +1,10 @@

<a name"0.4.1"></a>
### 0.4.1 (2015-06-09)
#### Features
* **preprocessor:** Add sourcemap support ([de3b738b](https://github.com/karma-runner/karma-coverage/commit/de3b738b), closes [#109](https://github.com/karma-runner/karma-coverage/issues/109))
* **reporter:** add check coverage thresholds ([bc63b158](https://github.com/karma-runner/karma-coverage/commit/bc63b158), closes [#21](https://github.com/karma-runner/karma-coverage/issues/21))
<a name"0.4.0"></a>

@@ -16,11 +25,6 @@ ## 0.4.0 (2015-06-09)

*
Karma is no longer a `peerDependency` so it needs to be installed
manually.
* Karma is no longer a `peerDependency` so it needs to be installed
manually. Ref https://github.com/karma-runner/integration-tests/issues/5 ([eebcc989](https://github.com/karma-runner/karma-coverage/commit/eebcc989))
Ref https://github.com/karma-runner/integration-tests/issues/5
([eebcc989](https://github.com/karma-runner/karma-coverage/commit/eebcc989))
<a name"0.3.1"></a>

@@ -27,0 +31,0 @@ ### 0.3.1 (2015-06-09)

@@ -0,1 +1,7 @@

// karma-coverage
// ==============
//
// Main entry point for the karma-coverage module.
// Exposes the preprocessor and reporter plugins.
module.exports = {

@@ -2,0 +8,0 @@ 'preprocessor:coverage': ['factory', require('./preprocessor')],

@@ -1,20 +0,54 @@

var istanbul = require('istanbul'),
minimatch = require('minimatch'),
globalSourceCache = require('./sourceCache'),
extend = require('util')._extend,
coverageMap = require('./coverageMap')
// Coverage Preprocessor
// =====================
//
// Depends on the the reporter to generate an actual report
var createCoveragePreprocessor = function (logger, basePath, reporters, coverageReporter) {
// Dependencies
// ------------
var istanbul = require('istanbul')
var minimatch = require('minimatch')
var SourceMapConsumer = require('source-map').SourceMapConsumer
var SourceMapGenerator = require('source-map').SourceMapGenerator
var globalSourceCache = require('./source-cache')
var extend = require('util')._extend
var coverageMap = require('./coverage-map')
// Regexes
// -------
var coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g
// Preprocessor creator function
function createCoveragePreprocessor (logger, helper, basePath, reporters, coverageReporter) {
var _ = helper._
var log = logger.create('preprocessor.coverage')
var instrumenterOverrides = (coverageReporter && coverageReporter.instrumenter) || {}
var instrumenters = extend({istanbul: istanbul}, (coverageReporter && coverageReporter.instrumenters))
var sourceCache = globalSourceCache.getByBasePath(basePath)
var includeAllSources = coverageReporter && coverageReporter.includeAllSources === true
var instrumentersOptions = Object.keys(instrumenters).reduce(function getInstumenterOptions (memo, instrumenterName) {
memo[instrumenterName] = (coverageReporter && coverageReporter.instrumenterOptions && coverageReporter.instrumenterOptions[instrumenterName]) || {}
return memo
}, {})
// Options
// -------
var instrumenterOverrides = {}
var instrumenters = {istanbul: istanbul}
var includeAllSources = false
if (coverageReporter) {
instrumenterOverrides = coverageReporter.instrumenter
instrumenters = extend({istanbul: istanbul}, coverageReporter.instrumenters)
includeAllSources = coverageReporter.includeAllSources === true
}
var sourceCache = globalSourceCache.get(basePath)
var instrumentersOptions = _.reduce(instrumenters, function getInstumenterOptions (memo, instrument, name) {
memo[name] = {}
if (coverageReporter && coverageReporter.instrumenterOptions) {
memo[name] = coverageReporter.instrumenterOptions[name]
}
return memo
}, {})
// if coverage reporter is not used, do not preprocess the files
if (reporters.indexOf('coverage') === -1) {
if (!_.includes(reporters, 'coverage')) {
return function (content, _, done) {

@@ -27,12 +61,11 @@ done(content)

function checkInstrumenters () {
var literal
for (var pattern in instrumenterOverrides) {
literal = String(instrumenterOverrides[pattern])
if (Object.keys(instrumenters).indexOf(literal) < 0) {
return _.reduce(instrumenterOverrides, function (acc, literal, pattern) {
if (!_.contains(_.keys(instrumenters), String(literal))) {
log.error('Unknown instrumenter: %s', literal)
return false
}
}
return true
return acc
}, true)
}
if (!checkInstrumenters()) {

@@ -51,10 +84,28 @@ return function (content, _, done) {

for (var pattern in instrumenterOverrides) {
_.forEach(instrumenterOverrides, function (literal, pattern) {
if (minimatch(file.originalPath, pattern, {dot: true})) {
instrumenterLiteral = String(instrumenterOverrides[pattern])
instrumenterLiteral = String(literal)
}
})
var InstrumenterConstructor = instrumenters[instrumenterLiteral].Instrumenter
var constructOptions = instrumentersOptions[instrumenterLiteral] || {}
var codeGenerationOptions = null
if (file.sourceMap) {
log.debug('Enabling source map generation for "%s".', file.originalPath)
codeGenerationOptions = extend({
format: {
compact: !constructOptions.noCompact
},
sourceMap: file.sourceMap.file,
sourceMapWithCode: true,
file: file.path
}, constructOptions.codeGenerationOptions || {})
}
var instrumenter = new instrumenters[instrumenterLiteral].Instrumenter(instrumentersOptions[instrumenterLiteral] || {})
var options = extend({}, constructOptions)
options = extend(options, {codeGenerationOptions: codeGenerationOptions})
var instrumenter = new InstrumenterConstructor(options)
instrumenter.instrument(content, jsPath, function (err, instrumentedCode) {

@@ -65,2 +116,11 @@ if (err) {

if (file.sourceMap && instrumenter.lastSourceMap()) {
log.debug('Adding source map to instrumented file for "%s".', file.originalPath)
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(instrumenter.lastSourceMap().toString()))
generator.applySourceMap(new SourceMapConsumer(file.sourceMap))
file.sourceMap = JSON.parse(generator.toString())
instrumentedCode += '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
instrumentedCode += new Buffer(JSON.stringify(file.sourceMap)).toString('base64') + '\n'
}
// remember the actual immediate instrumented JS for given original path

@@ -70,3 +130,2 @@ sourceCache[jsPath] = content

if (includeAllSources) {
var coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g
var coverageObjMatch = coverageObjRegex.exec(instrumentedCode)

@@ -88,2 +147,3 @@

'logger',
'helper',
'config.basePath',

@@ -90,0 +150,0 @@ 'config.reporters',

@@ -0,32 +1,29 @@

// Coverage Reporter
// Part of this code is based on [1], which is licensed under the New BSD License.
// For more information see the See the accompanying LICENSE-istanbul file for terms.
//
// [1]: https://github.com/gotwarlost/istanbul/blob/master/lib/command/check-coverage.js
// =====================
//
// Generates the report
// Dependencies
// ------------
var path = require('path')
var util = require('util')
var istanbul = require('istanbul')
var globalSourceCache = require('./sourceCache')
var coverageMap = require('./coverageMap')
var minimatch = require('minimatch')
var Store = istanbul.Store
var globalSourceCache = require('./source-cache')
var coverageMap = require('./coverage-map')
var SourceCacheStore = require('./source-cache-store')
var SourceCacheStore = function (opts) {
Store.call(this, opts)
opts = opts || {}
this.sourceCache = opts.sourceCache
function isAbsolute (file) {
if (path.isAbsolute) {
return path.isAbsolute(file)
}
return path.resolve(file) === path.normalize(file)
}
SourceCacheStore.TYPE = 'sourceCacheLookup'
util.inherits(SourceCacheStore, Store)
Store.mix(SourceCacheStore, {
keys: function () {
throw new Error('Not implemented')
},
get: function (key) {
return this.sourceCache[key]
},
hasKey: function (key) {
return this.sourceCache.hasOwnProperty(key)
},
set: function (key, contents) {
throw new Error('Not applicable')
}
})
// TODO(vojta): inject only what required (config.basePath, config.coverageReporter)

@@ -36,6 +33,15 @@ var CoverageReporter = function (rootConfig, helper, logger) {

var log = logger.create('coverage')
// Instance variables
// ------------------
this.adapters = []
// Options
// -------
var config = rootConfig.coverageReporter || {}
var basePath = rootConfig.basePath
var reporters = config.reporters
var sourceCache = globalSourceCache.getByBasePath(basePath)
var sourceCache = globalSourceCache.get(basePath)
var includeAllSources = config.includeAllSources === true

@@ -51,3 +57,2 @@

this.adapters = []
var collectors

@@ -69,5 +74,6 @@ var pendingFileWritings = 0

if (pendingFileWritings <= 0) {
Object.keys(collectors).forEach(function (key) {
collectors[key].dispose()
_.forEach(collectors, function (collector) {
collector.dispose()
})
fileWritingFinished()

@@ -77,12 +83,114 @@ }

/**
* Generate the output directory from the `coverageReporter.dir` and
* `coverageReporter.subdir` options.
*
* @param {String} browserName - The browser name
* @param {String} dir - The given option
* @param {String|Function} subdir - The given option
*
* @return {String} - The output directory
*/
function normalize (key) {
// Exclude keys will always be relative, but covObj keys can be absolute or relative
var excludeKey = isAbsolute(key) ? path.relative(basePath, key) : key
// Also normalize for files that start with `./`, etc.
excludeKey = path.normalize(excludeKey)
return excludeKey
}
function removeFiles (covObj, patterns) {
var obj = {}
Object.keys(covObj).forEach(function (key) {
// Do any patterns match the resolved key
var found = patterns.some(function (pattern) {
return minimatch(normalize(key), pattern, {dot: true})
})
// if no patterns match, keep the key
if (!found) {
obj[key] = covObj[key]
}
})
return obj
}
function overrideThresholds (key, overrides) {
var thresholds = {}
// First match wins
Object.keys(overrides).some(function (pattern) {
if (minimatch(normalize(key), pattern, {dot: true})) {
thresholds = overrides[pattern]
return true
}
})
return thresholds
}
function checkCoverage (browser, collector) {
var defaultThresholds = {
global: {
statements: 0,
branches: 0,
lines: 0,
functions: 0,
excludes: []
},
each: {
statements: 0,
branches: 0,
lines: 0,
functions: 0,
excludes: [],
overrides: {}
}
}
var thresholds = helper.merge({}, defaultThresholds, config.check)
var rawCoverage = collector.getFinalCoverage()
var globalResults = istanbul.utils.summarizeCoverage(removeFiles(rawCoverage, thresholds.global.excludes))
var eachResults = removeFiles(rawCoverage, thresholds.each.excludes)
// Summarize per-file results and mutate original results.
Object.keys(eachResults).forEach(function (key) {
eachResults[key] = istanbul.utils.summarizeFileCoverage(eachResults[key])
})
var coverageFailed = false
function check (name, thresholds, actuals) {
[
'statements',
'branches',
'lines',
'functions'
].forEach(function (key) {
var actual = actuals[key].pct
var actualUncovered = actuals[key].total - actuals[key].covered
var threshold = thresholds[key]
if (threshold < 0) {
if (threshold * -1 < actualUncovered) {
coverageFailed = true
log.error(browser.name + ': Uncovered count for ' + key + ' (' + actualUncovered +
') exceeds ' + name + ' threshold (' + -1 * threshold + ')')
}
} else {
if (actual < threshold) {
coverageFailed = true
log.error(browser.name + ': Coverage for ' + key + ' (' + actual +
'%) does not meet ' + name + ' threshold (' + threshold + '%)')
}
}
})
}
check('global', thresholds.global, globalResults)
Object.keys(eachResults).forEach(function (key) {
var keyThreshold = helper.merge(thresholds.each, overrideThresholds(key, thresholds.each.overrides))
check('per-file' + ' (' + key + ') ', keyThreshold, eachResults[key])
})
return coverageFailed
}
// Generate the output directory from the `coverageReporter.dir` and
// `coverageReporter.subdir` options.
function generateOutputDir (browserName, dir, subdir) {

@@ -92,3 +200,3 @@ dir = dir || 'coverage'

if (typeof subdir === 'function') {
if (_.isFunction(subdir)) {
subdir = subdir(browserName)

@@ -111,5 +219,6 @@ }

collectors[browser.id] = new istanbul.Collector()
if (includeAllSources) {
collectors[browser.id].add(coverageMap.get())
}
if (!includeAllSources) return
collectors[browser.id].add(coverageMap.get())
}

@@ -120,52 +229,68 @@

if (!collector) {
return
}
if (!collector) return
if (!result || !result.coverage) return
if (result && result.coverage) {
collector.add(result.coverage)
}
collector.add(result.coverage)
}
this.onSpecComplete = function (browser, result) {
if (result.coverage) {
collectors[browser.id].add(result.coverage)
}
if (!result.coverage) return
collectors[browser.id].add(result.coverage)
}
this.onRunComplete = function (browsers) {
this.onRunComplete = function (browsers, results) {
var checkedCoverage = {}
reporters.forEach(function (reporterConfig) {
browsers.forEach(function (browser) {
var collector = collectors[browser.id]
if (collector) {
pendingFileWritings++
var mainDir = reporterConfig.dir || config.dir
var subDir = reporterConfig.subdir || config.subdir
var simpleOutputDir = generateOutputDir(browser.name, mainDir, subDir)
var resolvedOutputDir = path.resolve(basePath, simpleOutputDir)
if (!collector) {
return
}
var outputDir = helper.normalizeWinPath(resolvedOutputDir)
var options = helper.merge({
sourceStore: _.isEmpty(sourceCache) ? null : new SourceCacheStore({
sourceCache: sourceCache
})
}, config, reporterConfig, {
dir: outputDir
})
var reporter = istanbul.Report.create(reporterConfig.type || 'html', options)
// If reporting to console, skip directory creation
if (reporterConfig.type && reporterConfig.type.match(/^(text|text-summary)$/) && typeof reporterConfig.file === 'undefined') {
writeReport(reporter, collector)
return
// If config.check is defined, check coverage levels for each browser
if (config.hasOwnProperty('check') && !checkedCoverage[browser.id]) {
checkedCoverage[browser.id] = true
var coverageFailed = checkCoverage(browser, collector)
if (coverageFailed) {
if (results) {
results.exitCode = 1
}
}
}
helper.mkdirIfNotExists(outputDir, function () {
log.debug('Writing coverage to %s', outputDir)
writeReport(reporter, collector)
disposeCollectors()
})
pendingFileWritings++
var mainDir = reporterConfig.dir || config.dir
var subDir = reporterConfig.subdir || config.subdir
var simpleOutputDir = generateOutputDir(browser.name, mainDir, subDir)
var resolvedOutputDir = path.resolve(basePath, simpleOutputDir)
var outputDir = helper.normalizeWinPath(resolvedOutputDir)
var sourceStore = _.isEmpty(sourceCache) ? null : new SourceCacheStore({
sourceCache: sourceCache
})
var options = helper.merge({
sourceStore: sourceStore
}, config, reporterConfig, {
dir: outputDir
})
var reporter = istanbul.Report.create(reporterConfig.type || 'html', options)
// If reporting to console, skip directory creation
var isConsole = reporterConfig.type && reporterConfig.type.match(/^(text|text-summary)$/)
var hasNoFile = _.isUndefined(reporterConfig.file)
if (isConsole && hasNoFile) {
writeReport(reporter, collector)
return
}
helper.mkdirIfNotExists(outputDir, function () {
log.debug('Writing coverage to %s', outputDir)
writeReport(reporter, collector)
disposeCollectors()
})
})

@@ -172,0 +297,0 @@ })

{
"name": "karma-coverage",
"version": "0.4.0",
"version": "0.4.1",
"description": "A Karma plugin. Generate code coverage.",

@@ -22,5 +22,6 @@ "main": "lib/index.js",

"dependencies": {
"istanbul": "^0.3.0",
"istanbul": "^0.3.15",
"dateformat": "^1.0.6",
"minimatch": "^2.0.8"
"minimatch": "^2.0.8",
"source-map": "^0.4.2"
},

@@ -51,8 +52,7 @@ "license": "MIT",

"Maksim Ryzhikov <rv.maksim@gmail.com>",
"Nick Malaguti <nmalaguti@palantir.com>",
"Allen Bierbaum <abierbaum@gmail.com>",
"Matt Winchester <m_winche@yahoo.com>",
"Wei Kin Huang <weikin.huang04@gmail.com>",
"Douglas Duteil <douglasduteil@gmail.com>",
"Wei Kin Huang <weikin.huang04@gmail.com>",
"Michael Stramel <m.stramel89@gmail.com>",
"Nick Malaguti <nmalaguti@palantir.com>",
"Nick Matantsev <nick.matantsev@gmail.com>",

@@ -79,4 +79,5 @@ "Petar Manev <petar.manev2010@gmail.com>",

"Maciej Rzepiński <maciej.rzepinski@gmail.com>",
"Mark Ethan Trostler <mark@zzo.com>"
"Mark Ethan Trostler <mark@zzo.com>",
"Michael Stramel <m.stramel89@gmail.com>"
]
}

@@ -193,2 +193,41 @@ # karma-coverage

#### check
**Type:** Object
**Description:** This will be used to configure minimum threshold enforcement for coverage results. If the thresholds are not met, karma will return failure. Thresholds, when specified as a positive number are taken to be the minimum percentage required. When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.
For example, `statements: 90` implies minimum statement coverage is 90%. `statements: -10` implies that no more than 10 uncovered statements are allowed.
`global` applies to all files together and `each` on a per-file basis. A list of files or patterns can be excluded from enforcement via the `exclude` property. On a per-file or pattern basis, per-file thresholds can be overridden via the `overrides` property.
```javascript
coverageReporter: {
check: {
global: {
statements: 50,
branches: 50,
functions: 50,
lines: 50,
excludes: [
'foo/bar/**/*.js'
]
},
each: {
statements: 50,
branches: 50,
functions: 50,
lines: 50,
excludes: [
'other/directory/**/*.js'
],
overrides: {
'baz/component/**/*.js': {
statements: 98
}
}
}
}
}
```
#### watermarks

@@ -195,0 +234,0 @@ **Type:** Object

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc