react / wstein / node_modules / jest-cli / node_modules / istanbul / lib / command / check-coverage.js
80680 views/*1Copyright (c) 2012, Yahoo! Inc. All rights reserved.2Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.3*/45var nopt = require('nopt'),6path = require('path'),7fs = require('fs'),8Collector = require('../collector'),9formatOption = require('../util/help-formatter').formatOption,10util = require('util'),11utils = require('../object-utils'),12filesFor = require('../util/file-matcher').filesFor,13Command = require('./index'),14configuration = require('../config');1516function isAbsolute(file) {17if (path.isAbsolute) {18return path.isAbsolute(file);19}2021return path.resolve(file) === path.normalize(file);22}2324function CheckCoverageCommand() {25Command.call(this);26}2728function removeFiles(covObj, root, files) {29var filesObj = {},30obj = {};3132// Create lookup table.33files.forEach(function (file) {34filesObj[file] = true;35});3637Object.keys(covObj).forEach(function (key) {38// Exclude keys will always be relative, but covObj keys can be absolute or relative39var excludeKey = isAbsolute(key) ? path.relative(root, key) : key;40// Also normalize for files that start with `./`, etc.41excludeKey = path.normalize(excludeKey);42if (filesObj[excludeKey] !== true) {43obj[key] = covObj[key];44}45});4647return obj;48}4950CheckCoverageCommand.TYPE = 'check-coverage';51util.inherits(CheckCoverageCommand, Command);5253Command.mix(CheckCoverageCommand, {54synopsis: function () {55return "checks overall/per-file coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise";56},5758usage: function () {59console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' +60[61formatOption('--statements <threshold>', 'global statement coverage threshold'),62formatOption('--functions <threshold>', 'global function coverage threshold'),63formatOption('--branches <threshold>', 'global branch coverage threshold'),64formatOption('--lines <threshold>', 'global line coverage threshold')65].join('\n\n') + '\n');6667console.error('\n\n');6869console.error('Thresholds, when specified as a positive number are taken to be the minimum percentage required.');70console.error('When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.\n');71console.error('For example, --statements 90 implies minimum statement coverage is 90%.');72console.error(' --statements -10 implies that no more than 10 uncovered statements are allowed\n');73console.error('Per-file thresholds can be specified via a configuration file.\n');74console.error('<include-pattern> is a fileset pattern that can be used to select one or more coverage files ' +75'for merge. This defaults to "**/coverage*.json"');7677console.error('\n');78},7980run: function (args, callback) {8182var template = {83config: path,84root: path,85statements: Number,86lines: Number,87branches: Number,88functions: Number,89verbose: Boolean90},91opts = nopt(template, { v : '--verbose' }, args, 0),92// Translate to config opts.93config = configuration.loadFile(opts.config, {94verbose: opts.verbose,95check: {96global: {97statements: opts.statements,98lines: opts.lines,99branches: opts.branches,100functions: opts.functions101}102}103}),104includePattern = '**/coverage*.json',105root,106collector = new Collector(),107errors = [];108109if (opts.argv.remain.length > 0) {110includePattern = opts.argv.remain[0];111}112113root = opts.root || process.cwd();114filesFor({115root: root,116includes: [ includePattern ]117}, function (err, files) {118if (err) { throw err; }119if (files.length === 0) {120return callback('ERROR: No coverage files found.');121}122files.forEach(function (file) {123var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));124collector.add(coverageObject);125});126var thresholds = {127global: {128statements: config.check.global.statements || 0,129branches: config.check.global.branches || 0,130lines: config.check.global.lines || 0,131functions: config.check.global.functions || 0,132excludes: config.check.global.excludes || []133},134each: {135statements: config.check.each.statements || 0,136branches: config.check.each.branches || 0,137lines: config.check.each.lines || 0,138functions: config.check.each.functions || 0,139excludes: config.check.each.excludes || []140}141},142rawCoverage = collector.getFinalCoverage(),143globalResults = utils.summarizeCoverage(removeFiles(rawCoverage, root, thresholds.global.excludes)),144eachResults = removeFiles(rawCoverage, root, thresholds.each.excludes);145146// Summarize per-file results and mutate original results.147Object.keys(eachResults).forEach(function (key) {148eachResults[key] = utils.summarizeFileCoverage(eachResults[key]);149});150151if (config.verbose) {152console.log('Compare actuals against thresholds');153console.log(JSON.stringify({ global: globalResults, each: eachResults, thresholds: thresholds }, undefined, 4));154}155156function check(name, thresholds, actuals) {157[158"statements",159"branches",160"lines",161"functions"162].forEach(function (key) {163var actual = actuals[key].pct,164actualUncovered = actuals[key].total - actuals[key].covered,165threshold = thresholds[key];166167if (threshold < 0) {168if (threshold * -1 < actualUncovered) {169errors.push('ERROR: Uncovered count for ' + key + ' (' + actualUncovered +170') exceeds ' + name + ' threshold (' + -1 * threshold + ')');171}172} else {173if (actual < threshold) {174errors.push('ERROR: Coverage for ' + key + ' (' + actual +175'%) does not meet ' + name + ' threshold (' + threshold + '%)');176}177}178});179}180181check("global", thresholds.global, globalResults);182183Object.keys(eachResults).forEach(function (key) {184check("per-file" + " (" + key + ") ", thresholds.each, eachResults[key]);185});186187return callback(errors.length === 0 ? null : errors.join("\n"));188});189}190});191192module.exports = CheckCoverageCommand;193194195196197