run-check-coverage.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. Copyright 2012-2015, Yahoo Inc.
  3. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
  4. */
  5. const path = require('path');
  6. const fs = require('fs');
  7. const libCoverage = require('istanbul-lib-coverage');
  8. const filesFor = require('./file-matcher').filesFor;
  9. const inputError = require('./input-error');
  10. const isAbsolute =
  11. path.isAbsolute ||
  12. function(file) {
  13. return path.resolve(file) === path.normalize(file);
  14. };
  15. function removeFiles(origMap, root, files) {
  16. const filesObj = {};
  17. const ret = libCoverage.createCoverageMap();
  18. // Create lookup table.
  19. files.forEach(file => {
  20. filesObj[file] = true;
  21. });
  22. origMap.files().forEach(key => {
  23. // Exclude keys will always be relative, but covObj keys can be absolute or relative
  24. let excludeKey = isAbsolute(key) ? path.relative(root, key) : key;
  25. // Also normalize for files that start with `./`, etc.
  26. excludeKey = path.normalize(excludeKey);
  27. if (filesObj[excludeKey] !== true) {
  28. ret.addFileCoverage(origMap.fileCoverageFor(key));
  29. }
  30. });
  31. return ret;
  32. }
  33. function run(config, opts, callback) {
  34. if (!callback && typeof opts === 'function') {
  35. callback = opts;
  36. opts = {};
  37. }
  38. opts = opts || {};
  39. const root = opts.root || config.instrumentation.root() || process.cwd();
  40. const includePattern = opts.include || '**/coverage*.json';
  41. const errors = [];
  42. const check = function(name, thresholds, actuals) {
  43. ['statements', 'branches', 'lines', 'functions'].forEach(key => {
  44. const actual = actuals[key].pct;
  45. const actualUncovered = actuals[key].total - actuals[key].covered;
  46. const threshold = thresholds[key];
  47. if (threshold < 0) {
  48. if (threshold * -1 < actualUncovered) {
  49. errors.push(
  50. 'ERROR: Uncovered count for ' +
  51. key +
  52. ' (' +
  53. actualUncovered +
  54. ') exceeds ' +
  55. name +
  56. ' threshold (' +
  57. -1 * threshold +
  58. ')'
  59. );
  60. }
  61. } else {
  62. if (actual < threshold) {
  63. errors.push(
  64. 'ERROR: Coverage for ' +
  65. key +
  66. ' (' +
  67. actual +
  68. '%) does not meet ' +
  69. name +
  70. ' threshold (' +
  71. threshold +
  72. '%)'
  73. );
  74. }
  75. }
  76. });
  77. };
  78. const makeMap = function(files, callback) {
  79. const coverageMap = libCoverage.createCoverageMap();
  80. if (files.length === 0) {
  81. return callback(
  82. inputError.create('ERROR: No coverage files found.')
  83. );
  84. }
  85. files.forEach(file => {
  86. const coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
  87. coverageMap.merge(coverageObject);
  88. });
  89. return callback(null, coverageMap);
  90. };
  91. const processFiles = function(coverageMap, callback) {
  92. const thresholds = {
  93. global: {
  94. statements: config.check.global.statements || 0,
  95. branches: config.check.global.branches || 0,
  96. lines: config.check.global.lines || 0,
  97. functions: config.check.global.functions || 0,
  98. excludes: config.check.global.excludes || []
  99. },
  100. each: {
  101. statements: config.check.each.statements || 0,
  102. branches: config.check.each.branches || 0,
  103. lines: config.check.each.lines || 0,
  104. functions: config.check.each.functions || 0,
  105. excludes: config.check.each.excludes || []
  106. }
  107. };
  108. const globalResults = removeFiles(
  109. coverageMap,
  110. root,
  111. thresholds.global.excludes
  112. );
  113. const eachResults = removeFiles(
  114. coverageMap,
  115. root,
  116. thresholds.each.excludes
  117. );
  118. if (config.verbose) {
  119. console.error('Compare actuals against thresholds');
  120. console.error(
  121. JSON.stringify(
  122. {
  123. global: globalResults,
  124. each: eachResults,
  125. thresholds
  126. },
  127. undefined,
  128. 4
  129. )
  130. );
  131. }
  132. check('global', thresholds.global, globalResults.getCoverageSummary());
  133. eachResults.files().forEach(key => {
  134. const summary = eachResults.fileCoverageFor(key).toSummary();
  135. check('per-file' + ' (' + key + ') ', thresholds.each, summary);
  136. });
  137. const finalError = errors.length === 0 ? null : errors.join('\n');
  138. return callback(finalError);
  139. };
  140. filesFor(
  141. {
  142. root,
  143. includes: [includePattern]
  144. },
  145. (err, files) => {
  146. if (err) {
  147. return callback(err);
  148. }
  149. makeMap(files, (err, map) => {
  150. if (err) {
  151. return callback(err);
  152. }
  153. return processFiles(map, callback);
  154. });
  155. }
  156. );
  157. }
  158. module.exports = {
  159. run
  160. };