/* Copyright 2012-2015, Yahoo Inc. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ const path = require('path'); const fs = require('fs'); const libCoverage = require('istanbul-lib-coverage'); const filesFor = require('./file-matcher').filesFor; const inputError = require('./input-error'); const isAbsolute = path.isAbsolute || function(file) { return path.resolve(file) === path.normalize(file); }; function removeFiles(origMap, root, files) { const filesObj = {}; const ret = libCoverage.createCoverageMap(); // Create lookup table. files.forEach(file => { filesObj[file] = true; }); origMap.files().forEach(key => { // Exclude keys will always be relative, but covObj keys can be absolute or relative let excludeKey = isAbsolute(key) ? path.relative(root, key) : key; // Also normalize for files that start with `./`, etc. excludeKey = path.normalize(excludeKey); if (filesObj[excludeKey] !== true) { ret.addFileCoverage(origMap.fileCoverageFor(key)); } }); return ret; } function run(config, opts, callback) { if (!callback && typeof opts === 'function') { callback = opts; opts = {}; } opts = opts || {}; const root = opts.root || config.instrumentation.root() || process.cwd(); const includePattern = opts.include || '**/coverage*.json'; const errors = []; const check = function(name, thresholds, actuals) { ['statements', 'branches', 'lines', 'functions'].forEach(key => { const actual = actuals[key].pct; const actualUncovered = actuals[key].total - actuals[key].covered; const threshold = thresholds[key]; if (threshold < 0) { if (threshold * -1 < actualUncovered) { errors.push( 'ERROR: Uncovered count for ' + key + ' (' + actualUncovered + ') exceeds ' + name + ' threshold (' + -1 * threshold + ')' ); } } else { if (actual < threshold) { errors.push( 'ERROR: Coverage for ' + key + ' (' + actual + '%) does not meet ' + name + ' threshold (' + threshold + '%)' ); } } }); }; const makeMap = function(files, callback) { const coverageMap = libCoverage.createCoverageMap(); if (files.length === 0) { return callback( inputError.create('ERROR: No coverage files found.') ); } files.forEach(file => { const coverageObject = JSON.parse(fs.readFileSync(file, 'utf8')); coverageMap.merge(coverageObject); }); return callback(null, coverageMap); }; const processFiles = function(coverageMap, callback) { const thresholds = { global: { statements: config.check.global.statements || 0, branches: config.check.global.branches || 0, lines: config.check.global.lines || 0, functions: config.check.global.functions || 0, excludes: config.check.global.excludes || [] }, each: { statements: config.check.each.statements || 0, branches: config.check.each.branches || 0, lines: config.check.each.lines || 0, functions: config.check.each.functions || 0, excludes: config.check.each.excludes || [] } }; const globalResults = removeFiles( coverageMap, root, thresholds.global.excludes ); const eachResults = removeFiles( coverageMap, root, thresholds.each.excludes ); if (config.verbose) { console.error('Compare actuals against thresholds'); console.error( JSON.stringify( { global: globalResults, each: eachResults, thresholds }, undefined, 4 ) ); } check('global', thresholds.global, globalResults.getCoverageSummary()); eachResults.files().forEach(key => { const summary = eachResults.fileCoverageFor(key).toSummary(); check('per-file' + ' (' + key + ') ', thresholds.each, summary); }); const finalError = errors.length === 0 ? null : errors.join('\n'); return callback(finalError); }; filesFor( { root, includes: [includePattern] }, (err, files) => { if (err) { return callback(err); } makeMap(files, (err, map) => { if (err) { return callback(err); } return processFiles(map, callback); }); } ); } module.exports = { run };