You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
668 lines
18 KiB
668 lines
18 KiB
'use strict'; |
|
|
|
Object.defineProperty(exports, '__esModule', { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
function path() { |
|
const data = _interopRequireWildcard(require('path')); |
|
|
|
path = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _v8Coverage() { |
|
const data = require('@bcoe/v8-coverage'); |
|
|
|
_v8Coverage = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _chalk() { |
|
const data = _interopRequireDefault(require('chalk')); |
|
|
|
_chalk = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _glob() { |
|
const data = _interopRequireDefault(require('glob')); |
|
|
|
_glob = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function fs() { |
|
const data = _interopRequireWildcard(require('graceful-fs')); |
|
|
|
fs = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _istanbulLibCoverage() { |
|
const data = _interopRequireDefault(require('istanbul-lib-coverage')); |
|
|
|
_istanbulLibCoverage = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _istanbulLibReport() { |
|
const data = _interopRequireDefault(require('istanbul-lib-report')); |
|
|
|
_istanbulLibReport = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _istanbulLibSourceMaps() { |
|
const data = _interopRequireDefault(require('istanbul-lib-source-maps')); |
|
|
|
_istanbulLibSourceMaps = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _istanbulReports() { |
|
const data = _interopRequireDefault(require('istanbul-reports')); |
|
|
|
_istanbulReports = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _v8ToIstanbul() { |
|
const data = _interopRequireDefault(require('v8-to-istanbul')); |
|
|
|
_v8ToIstanbul = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _jestUtil() { |
|
const data = require('jest-util'); |
|
|
|
_jestUtil = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _jestWorker() { |
|
const data = require('jest-worker'); |
|
|
|
_jestWorker = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
var _BaseReporter = _interopRequireDefault(require('./BaseReporter')); |
|
|
|
var _getWatermarks = _interopRequireDefault(require('./getWatermarks')); |
|
|
|
function _interopRequireDefault(obj) { |
|
return obj && obj.__esModule ? obj : {default: obj}; |
|
} |
|
|
|
function _getRequireWildcardCache(nodeInterop) { |
|
if (typeof WeakMap !== 'function') return null; |
|
var cacheBabelInterop = new WeakMap(); |
|
var cacheNodeInterop = new WeakMap(); |
|
return (_getRequireWildcardCache = function (nodeInterop) { |
|
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; |
|
})(nodeInterop); |
|
} |
|
|
|
function _interopRequireWildcard(obj, nodeInterop) { |
|
if (!nodeInterop && obj && obj.__esModule) { |
|
return obj; |
|
} |
|
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) { |
|
return {default: obj}; |
|
} |
|
var cache = _getRequireWildcardCache(nodeInterop); |
|
if (cache && cache.has(obj)) { |
|
return cache.get(obj); |
|
} |
|
var newObj = {}; |
|
var hasPropertyDescriptor = |
|
Object.defineProperty && Object.getOwnPropertyDescriptor; |
|
for (var key in obj) { |
|
if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) { |
|
var desc = hasPropertyDescriptor |
|
? Object.getOwnPropertyDescriptor(obj, key) |
|
: null; |
|
if (desc && (desc.get || desc.set)) { |
|
Object.defineProperty(newObj, key, desc); |
|
} else { |
|
newObj[key] = obj[key]; |
|
} |
|
} |
|
} |
|
newObj.default = obj; |
|
if (cache) { |
|
cache.set(obj, newObj); |
|
} |
|
return newObj; |
|
} |
|
|
|
function _defineProperty(obj, key, value) { |
|
if (key in obj) { |
|
Object.defineProperty(obj, key, { |
|
value: value, |
|
enumerable: true, |
|
configurable: true, |
|
writable: true |
|
}); |
|
} else { |
|
obj[key] = value; |
|
} |
|
return obj; |
|
} |
|
|
|
const FAIL_COLOR = _chalk().default.bold.red; |
|
|
|
const RUNNING_TEST_COLOR = _chalk().default.bold.dim; |
|
|
|
class CoverageReporter extends _BaseReporter.default { |
|
constructor(globalConfig, options) { |
|
super(); |
|
|
|
_defineProperty(this, '_coverageMap', void 0); |
|
|
|
_defineProperty(this, '_globalConfig', void 0); |
|
|
|
_defineProperty(this, '_sourceMapStore', void 0); |
|
|
|
_defineProperty(this, '_options', void 0); |
|
|
|
_defineProperty(this, '_v8CoverageResults', void 0); |
|
|
|
this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({}); |
|
this._globalConfig = globalConfig; |
|
this._sourceMapStore = |
|
_istanbulLibSourceMaps().default.createSourceMapStore(); |
|
this._v8CoverageResults = []; |
|
this._options = options || {}; |
|
} |
|
|
|
onTestResult(_test, testResult) { |
|
if (testResult.v8Coverage) { |
|
this._v8CoverageResults.push(testResult.v8Coverage); |
|
|
|
return; |
|
} |
|
|
|
if (testResult.coverage) { |
|
this._coverageMap.merge(testResult.coverage); |
|
} |
|
} |
|
|
|
async onRunComplete(contexts, aggregatedResults) { |
|
await this._addUntestedFiles(contexts); |
|
const {map, reportContext} = await this._getCoverageResult(); |
|
|
|
try { |
|
const coverageReporters = this._globalConfig.coverageReporters || []; |
|
|
|
if (!this._globalConfig.useStderr && coverageReporters.length < 1) { |
|
coverageReporters.push('text-summary'); |
|
} |
|
|
|
coverageReporters.forEach(reporter => { |
|
let additionalOptions = {}; |
|
|
|
if (Array.isArray(reporter)) { |
|
[reporter, additionalOptions] = reporter; |
|
} |
|
|
|
_istanbulReports() |
|
.default.create(reporter, { |
|
maxCols: process.stdout.columns || Infinity, |
|
...additionalOptions |
|
}) |
|
.execute(reportContext); |
|
}); |
|
aggregatedResults.coverageMap = map; |
|
} catch (e) { |
|
console.error( |
|
_chalk().default.red(` |
|
Failed to write coverage reports: |
|
ERROR: ${e.toString()} |
|
STACK: ${e.stack} |
|
`) |
|
); |
|
} |
|
|
|
this._checkThreshold(map); |
|
} |
|
|
|
async _addUntestedFiles(contexts) { |
|
const files = []; |
|
contexts.forEach(context => { |
|
const config = context.config; |
|
|
|
if ( |
|
this._globalConfig.collectCoverageFrom && |
|
this._globalConfig.collectCoverageFrom.length |
|
) { |
|
context.hasteFS |
|
.matchFilesWithGlob( |
|
this._globalConfig.collectCoverageFrom, |
|
config.rootDir |
|
) |
|
.forEach(filePath => |
|
files.push({ |
|
config, |
|
path: filePath |
|
}) |
|
); |
|
} |
|
}); |
|
|
|
if (!files.length) { |
|
return; |
|
} |
|
|
|
if (_jestUtil().isInteractive) { |
|
process.stderr.write( |
|
RUNNING_TEST_COLOR('Running coverage on untested files...') |
|
); |
|
} |
|
|
|
let worker; |
|
|
|
if (this._globalConfig.maxWorkers <= 1) { |
|
worker = require('./CoverageWorker'); |
|
} else { |
|
worker = new (_jestWorker().Worker)(require.resolve('./CoverageWorker'), { |
|
exposedMethods: ['worker'], |
|
maxRetries: 2, |
|
numWorkers: this._globalConfig.maxWorkers |
|
}); |
|
} |
|
|
|
const instrumentation = files.map(async fileObj => { |
|
const filename = fileObj.path; |
|
const config = fileObj.config; |
|
|
|
const hasCoverageData = this._v8CoverageResults.some(v8Res => |
|
v8Res.some(innerRes => innerRes.result.url === filename) |
|
); |
|
|
|
if ( |
|
!hasCoverageData && |
|
!this._coverageMap.data[filename] && |
|
'worker' in worker |
|
) { |
|
try { |
|
const result = await worker.worker({ |
|
config, |
|
globalConfig: this._globalConfig, |
|
options: { |
|
...this._options, |
|
changedFiles: |
|
this._options.changedFiles && |
|
Array.from(this._options.changedFiles), |
|
sourcesRelatedToTestsInChangedFiles: |
|
this._options.sourcesRelatedToTestsInChangedFiles && |
|
Array.from(this._options.sourcesRelatedToTestsInChangedFiles) |
|
}, |
|
path: filename |
|
}); |
|
|
|
if (result) { |
|
if (result.kind === 'V8Coverage') { |
|
this._v8CoverageResults.push([ |
|
{ |
|
codeTransformResult: undefined, |
|
result: result.result |
|
} |
|
]); |
|
} else { |
|
this._coverageMap.addFileCoverage(result.coverage); |
|
} |
|
} |
|
} catch (error) { |
|
console.error( |
|
_chalk().default.red( |
|
[ |
|
`Failed to collect coverage from ${filename}`, |
|
`ERROR: ${error.message}`, |
|
`STACK: ${error.stack}` |
|
].join('\n') |
|
) |
|
); |
|
} |
|
} |
|
}); |
|
|
|
try { |
|
await Promise.all(instrumentation); |
|
} catch { |
|
// Do nothing; errors were reported earlier to the console. |
|
} |
|
|
|
if (_jestUtil().isInteractive) { |
|
(0, _jestUtil().clearLine)(process.stderr); |
|
} |
|
|
|
if (worker && 'end' in worker && typeof worker.end === 'function') { |
|
await worker.end(); |
|
} |
|
} |
|
|
|
_checkThreshold(map) { |
|
const {coverageThreshold} = this._globalConfig; |
|
|
|
if (coverageThreshold) { |
|
function check(name, thresholds, actuals) { |
|
return ['statements', 'branches', 'lines', 'functions'].reduce( |
|
(errors, key) => { |
|
const actual = actuals[key].pct; |
|
const actualUncovered = actuals[key].total - actuals[key].covered; |
|
const threshold = thresholds[key]; |
|
|
|
if (threshold !== undefined) { |
|
if (threshold < 0) { |
|
if (threshold * -1 < actualUncovered) { |
|
errors.push( |
|
`Jest: Uncovered count for ${key} (${actualUncovered}) ` + |
|
`exceeds ${name} threshold (${-1 * threshold})` |
|
); |
|
} |
|
} else if (actual < threshold) { |
|
errors.push( |
|
`Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%` |
|
); |
|
} |
|
} |
|
|
|
return errors; |
|
}, |
|
[] |
|
); |
|
} |
|
|
|
const THRESHOLD_GROUP_TYPES = { |
|
GLOB: 'glob', |
|
GLOBAL: 'global', |
|
PATH: 'path' |
|
}; |
|
const coveredFiles = map.files(); |
|
const thresholdGroups = Object.keys(coverageThreshold); |
|
const groupTypeByThresholdGroup = {}; |
|
const filesByGlob = {}; |
|
const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce( |
|
(files, file) => { |
|
const pathOrGlobMatches = thresholdGroups.reduce( |
|
(agg, thresholdGroup) => { |
|
const absoluteThresholdGroup = path().resolve(thresholdGroup); // The threshold group might be a path: |
|
|
|
if (file.indexOf(absoluteThresholdGroup) === 0) { |
|
groupTypeByThresholdGroup[thresholdGroup] = |
|
THRESHOLD_GROUP_TYPES.PATH; |
|
return agg.concat([[file, thresholdGroup]]); |
|
} // If the threshold group is not a path it might be a glob: |
|
// Note: glob.sync is slow. By memoizing the files matching each glob |
|
// (rather than recalculating it for each covered file) we save a tonne |
|
// of execution time. |
|
|
|
if (filesByGlob[absoluteThresholdGroup] === undefined) { |
|
filesByGlob[absoluteThresholdGroup] = _glob() |
|
.default.sync(absoluteThresholdGroup) |
|
.map(filePath => path().resolve(filePath)); |
|
} |
|
|
|
if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) { |
|
groupTypeByThresholdGroup[thresholdGroup] = |
|
THRESHOLD_GROUP_TYPES.GLOB; |
|
return agg.concat([[file, thresholdGroup]]); |
|
} |
|
|
|
return agg; |
|
}, |
|
[] |
|
); |
|
|
|
if (pathOrGlobMatches.length > 0) { |
|
return files.concat(pathOrGlobMatches); |
|
} // Neither a glob or a path? Toss it in global if there's a global threshold: |
|
|
|
if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) { |
|
groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] = |
|
THRESHOLD_GROUP_TYPES.GLOBAL; |
|
return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]); |
|
} // A covered file that doesn't have a threshold: |
|
|
|
return files.concat([[file, undefined]]); |
|
}, |
|
[] |
|
); |
|
|
|
const getFilesInThresholdGroup = thresholdGroup => |
|
coveredFilesSortedIntoThresholdGroup |
|
.filter(fileAndGroup => fileAndGroup[1] === thresholdGroup) |
|
.map(fileAndGroup => fileAndGroup[0]); |
|
|
|
function combineCoverage(filePaths) { |
|
return filePaths |
|
.map(filePath => map.fileCoverageFor(filePath)) |
|
.reduce((combinedCoverage, nextFileCoverage) => { |
|
if (combinedCoverage === undefined || combinedCoverage === null) { |
|
return nextFileCoverage.toSummary(); |
|
} |
|
|
|
return combinedCoverage.merge(nextFileCoverage.toSummary()); |
|
}, undefined); |
|
} |
|
|
|
let errors = []; |
|
thresholdGroups.forEach(thresholdGroup => { |
|
switch (groupTypeByThresholdGroup[thresholdGroup]) { |
|
case THRESHOLD_GROUP_TYPES.GLOBAL: { |
|
const coverage = combineCoverage( |
|
getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL) |
|
); |
|
|
|
if (coverage) { |
|
errors = errors.concat( |
|
check( |
|
thresholdGroup, |
|
coverageThreshold[thresholdGroup], |
|
coverage |
|
) |
|
); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case THRESHOLD_GROUP_TYPES.PATH: { |
|
const coverage = combineCoverage( |
|
getFilesInThresholdGroup(thresholdGroup) |
|
); |
|
|
|
if (coverage) { |
|
errors = errors.concat( |
|
check( |
|
thresholdGroup, |
|
coverageThreshold[thresholdGroup], |
|
coverage |
|
) |
|
); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case THRESHOLD_GROUP_TYPES.GLOB: |
|
getFilesInThresholdGroup(thresholdGroup).forEach( |
|
fileMatchingGlob => { |
|
errors = errors.concat( |
|
check( |
|
fileMatchingGlob, |
|
coverageThreshold[thresholdGroup], |
|
map.fileCoverageFor(fileMatchingGlob).toSummary() |
|
) |
|
); |
|
} |
|
); |
|
break; |
|
|
|
default: |
|
// If the file specified by path is not found, error is returned. |
|
if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) { |
|
errors = errors.concat( |
|
`Jest: Coverage data for ${thresholdGroup} was not found.` |
|
); |
|
} |
|
|
|
// Sometimes all files in the coverage data are matched by |
|
// PATH and GLOB threshold groups in which case, don't error when |
|
// the global threshold group doesn't match any files. |
|
} |
|
}); |
|
errors = errors.filter( |
|
err => err !== undefined && err !== null && err.length > 0 |
|
); |
|
|
|
if (errors.length > 0) { |
|
this.log(`${FAIL_COLOR(errors.join('\n'))}`); |
|
|
|
this._setError(new Error(errors.join('\n'))); |
|
} |
|
} |
|
} |
|
|
|
async _getCoverageResult() { |
|
if (this._globalConfig.coverageProvider === 'v8') { |
|
const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)( |
|
this._v8CoverageResults.map(cov => ({ |
|
result: cov.map(r => r.result) |
|
})) |
|
); |
|
const fileTransforms = new Map(); |
|
|
|
this._v8CoverageResults.forEach(res => |
|
res.forEach(r => { |
|
if (r.codeTransformResult && !fileTransforms.has(r.result.url)) { |
|
fileTransforms.set(r.result.url, r.codeTransformResult); |
|
} |
|
}) |
|
); |
|
|
|
const transformedCoverage = await Promise.all( |
|
mergedCoverages.result.map(async res => { |
|
var _fileTransform$wrappe; |
|
|
|
const fileTransform = fileTransforms.get(res.url); |
|
let sourcemapContent = undefined; |
|
|
|
if ( |
|
fileTransform !== null && |
|
fileTransform !== void 0 && |
|
fileTransform.sourceMapPath && |
|
fs().existsSync(fileTransform.sourceMapPath) |
|
) { |
|
sourcemapContent = JSON.parse( |
|
fs().readFileSync(fileTransform.sourceMapPath, 'utf8') |
|
); |
|
} |
|
|
|
const converter = (0, _v8ToIstanbul().default)( |
|
res.url, |
|
(_fileTransform$wrappe = |
|
fileTransform === null || fileTransform === void 0 |
|
? void 0 |
|
: fileTransform.wrapperLength) !== null && |
|
_fileTransform$wrappe !== void 0 |
|
? _fileTransform$wrappe |
|
: 0, |
|
fileTransform && sourcemapContent |
|
? { |
|
originalSource: fileTransform.originalCode, |
|
source: fileTransform.code, |
|
sourceMap: { |
|
sourcemap: { |
|
file: res.url, |
|
...sourcemapContent |
|
} |
|
} |
|
} |
|
: { |
|
source: fs().readFileSync(res.url, 'utf8') |
|
} |
|
); |
|
await converter.load(); |
|
converter.applyCoverage(res.functions); |
|
const istanbulData = converter.toIstanbul(); |
|
converter.destroy(); |
|
return istanbulData; |
|
}) |
|
); |
|
|
|
const map = _istanbulLibCoverage().default.createCoverageMap({}); |
|
|
|
transformedCoverage.forEach(res => map.merge(res)); |
|
|
|
const reportContext = _istanbulLibReport().default.createContext({ |
|
coverageMap: map, |
|
dir: this._globalConfig.coverageDirectory, |
|
watermarks: (0, _getWatermarks.default)(this._globalConfig) |
|
}); |
|
|
|
return { |
|
map, |
|
reportContext |
|
}; |
|
} |
|
|
|
const map = await this._sourceMapStore.transformCoverage(this._coverageMap); |
|
|
|
const reportContext = _istanbulLibReport().default.createContext({ |
|
coverageMap: map, |
|
dir: this._globalConfig.coverageDirectory, |
|
sourceFinder: this._sourceMapStore.sourceFinder, |
|
watermarks: (0, _getWatermarks.default)(this._globalConfig) |
|
}); |
|
|
|
return { |
|
map, |
|
reportContext |
|
}; |
|
} |
|
} |
|
|
|
exports.default = CoverageReporter; |
|
|
|
_defineProperty(CoverageReporter, 'filename', __filename);
|
|
|