coverage_reporter.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. function _path() {
  7. const data = _interopRequireDefault(require('path'));
  8. _path = function _path() {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _jestUtil() {
  14. const data = require('jest-util');
  15. _jestUtil = function _jestUtil() {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _istanbulLibReport() {
  21. const data = _interopRequireDefault(require('istanbul-lib-report'));
  22. _istanbulLibReport = function _istanbulLibReport() {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _istanbulReports() {
  28. const data = _interopRequireDefault(require('istanbul-reports'));
  29. _istanbulReports = function _istanbulReports() {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _chalk() {
  35. const data = _interopRequireDefault(require('chalk'));
  36. _chalk = function _chalk() {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _istanbulLibCoverage() {
  42. const data = _interopRequireDefault(require('istanbul-lib-coverage'));
  43. _istanbulLibCoverage = function _istanbulLibCoverage() {
  44. return data;
  45. };
  46. return data;
  47. }
  48. function _istanbulLibSourceMaps() {
  49. const data = _interopRequireDefault(require('istanbul-lib-source-maps'));
  50. _istanbulLibSourceMaps = function _istanbulLibSourceMaps() {
  51. return data;
  52. };
  53. return data;
  54. }
  55. function _jestWorker() {
  56. const data = _interopRequireDefault(require('jest-worker'));
  57. _jestWorker = function _jestWorker() {
  58. return data;
  59. };
  60. return data;
  61. }
  62. function _glob() {
  63. const data = _interopRequireDefault(require('glob'));
  64. _glob = function _glob() {
  65. return data;
  66. };
  67. return data;
  68. }
  69. var _base_reporter = _interopRequireDefault(require('./base_reporter'));
  70. function _interopRequireDefault(obj) {
  71. return obj && obj.__esModule ? obj : {default: obj};
  72. }
  73. function _objectSpread(target) {
  74. for (var i = 1; i < arguments.length; i++) {
  75. var source = arguments[i] != null ? arguments[i] : {};
  76. var ownKeys = Object.keys(source);
  77. if (typeof Object.getOwnPropertySymbols === 'function') {
  78. ownKeys = ownKeys.concat(
  79. Object.getOwnPropertySymbols(source).filter(function(sym) {
  80. return Object.getOwnPropertyDescriptor(source, sym).enumerable;
  81. })
  82. );
  83. }
  84. ownKeys.forEach(function(key) {
  85. _defineProperty(target, key, source[key]);
  86. });
  87. }
  88. return target;
  89. }
  90. function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  91. try {
  92. var info = gen[key](arg);
  93. var value = info.value;
  94. } catch (error) {
  95. reject(error);
  96. return;
  97. }
  98. if (info.done) {
  99. resolve(value);
  100. } else {
  101. Promise.resolve(value).then(_next, _throw);
  102. }
  103. }
  104. function _asyncToGenerator(fn) {
  105. return function() {
  106. var self = this,
  107. args = arguments;
  108. return new Promise(function(resolve, reject) {
  109. var gen = fn.apply(self, args);
  110. function _next(value) {
  111. asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
  112. }
  113. function _throw(err) {
  114. asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
  115. }
  116. _next(undefined);
  117. });
  118. };
  119. }
  120. function _defineProperty(obj, key, value) {
  121. if (key in obj) {
  122. Object.defineProperty(obj, key, {
  123. value: value,
  124. enumerable: true,
  125. configurable: true,
  126. writable: true
  127. });
  128. } else {
  129. obj[key] = value;
  130. }
  131. return obj;
  132. }
  133. const FAIL_COLOR = _chalk().default.bold.red;
  134. const RUNNING_TEST_COLOR = _chalk().default.bold.dim;
  135. class CoverageReporter extends _base_reporter.default {
  136. constructor(globalConfig, options) {
  137. super();
  138. _defineProperty(this, '_coverageMap', void 0);
  139. _defineProperty(this, '_globalConfig', void 0);
  140. _defineProperty(this, '_sourceMapStore', void 0);
  141. _defineProperty(this, '_options', void 0);
  142. this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
  143. this._globalConfig = globalConfig;
  144. this._sourceMapStore = _istanbulLibSourceMaps().default.createSourceMapStore();
  145. this._options = options || {};
  146. }
  147. onTestResult(_test, testResult, _aggregatedResults) {
  148. if (testResult.coverage) {
  149. this._coverageMap.merge(testResult.coverage);
  150. }
  151. const sourceMaps = testResult.sourceMaps;
  152. if (sourceMaps) {
  153. Object.keys(sourceMaps).forEach(sourcePath => {
  154. let inputSourceMap;
  155. try {
  156. const coverage = this._coverageMap.fileCoverageFor(sourcePath);
  157. inputSourceMap = coverage.toJSON().inputSourceMap;
  158. } finally {
  159. if (inputSourceMap) {
  160. this._sourceMapStore.registerMap(sourcePath, inputSourceMap);
  161. } else {
  162. this._sourceMapStore.registerURL(
  163. sourcePath,
  164. sourceMaps[sourcePath]
  165. );
  166. }
  167. }
  168. });
  169. }
  170. }
  171. onRunComplete(contexts, aggregatedResults) {
  172. var _this = this;
  173. return _asyncToGenerator(function*() {
  174. yield _this._addUntestedFiles(_this._globalConfig, contexts);
  175. const _this$_sourceMapStore = _this._sourceMapStore.transformCoverage(
  176. _this._coverageMap
  177. ),
  178. map = _this$_sourceMapStore.map,
  179. sourceFinder = _this$_sourceMapStore.sourceFinder;
  180. try {
  181. const reportContext = _istanbulLibReport().default.createContext({
  182. dir: _this._globalConfig.coverageDirectory,
  183. sourceFinder
  184. });
  185. const coverageReporters = _this._globalConfig.coverageReporters || [];
  186. if (!_this._globalConfig.useStderr && coverageReporters.length < 1) {
  187. coverageReporters.push('text-summary');
  188. }
  189. const tree = _istanbulLibReport().default.summarizers.pkg(map);
  190. coverageReporters.forEach(reporter => {
  191. tree.visit(
  192. _istanbulReports().default.create(reporter, {}),
  193. reportContext
  194. );
  195. });
  196. aggregatedResults.coverageMap = map;
  197. } catch (e) {
  198. console.error(
  199. _chalk().default.red(`
  200. Failed to write coverage reports:
  201. ERROR: ${e.toString()}
  202. STACK: ${e.stack}
  203. `)
  204. );
  205. }
  206. _this._checkThreshold(_this._globalConfig, map);
  207. })();
  208. }
  209. _addUntestedFiles(globalConfig, contexts) {
  210. var _this2 = this;
  211. return _asyncToGenerator(function*() {
  212. const files = [];
  213. contexts.forEach(context => {
  214. const config = context.config;
  215. if (
  216. globalConfig.collectCoverageFrom &&
  217. globalConfig.collectCoverageFrom.length
  218. ) {
  219. context.hasteFS
  220. .matchFilesWithGlob(
  221. globalConfig.collectCoverageFrom,
  222. config.rootDir
  223. )
  224. .forEach(filePath =>
  225. files.push({
  226. config,
  227. path: filePath
  228. })
  229. );
  230. }
  231. });
  232. if (!files.length) {
  233. return;
  234. }
  235. if (_jestUtil().isInteractive) {
  236. process.stderr.write(
  237. RUNNING_TEST_COLOR('Running coverage on untested files...')
  238. );
  239. }
  240. let worker;
  241. if (_this2._globalConfig.maxWorkers <= 1) {
  242. worker = require('./coverage_worker');
  243. } else {
  244. worker = new (_jestWorker()).default(
  245. require.resolve('./coverage_worker'),
  246. {
  247. exposedMethods: ['worker'],
  248. maxRetries: 2,
  249. numWorkers: _this2._globalConfig.maxWorkers
  250. }
  251. );
  252. }
  253. const instrumentation = files.map(
  254. /*#__PURE__*/
  255. (function() {
  256. var _ref = _asyncToGenerator(function*(fileObj) {
  257. const filename = fileObj.path;
  258. const config = fileObj.config;
  259. if (!_this2._coverageMap.data[filename] && 'worker' in worker) {
  260. try {
  261. const result = yield worker.worker({
  262. config,
  263. globalConfig,
  264. options: _objectSpread({}, _this2._options, {
  265. changedFiles:
  266. _this2._options.changedFiles &&
  267. Array.from(_this2._options.changedFiles)
  268. }),
  269. path: filename
  270. });
  271. if (result) {
  272. _this2._coverageMap.addFileCoverage(result.coverage);
  273. if (result.sourceMapPath) {
  274. _this2._sourceMapStore.registerURL(
  275. filename,
  276. result.sourceMapPath
  277. );
  278. }
  279. }
  280. } catch (error) {
  281. console.error(
  282. _chalk().default.red(
  283. [
  284. `Failed to collect coverage from ${filename}`,
  285. `ERROR: ${error.message}`,
  286. `STACK: ${error.stack}`
  287. ].join('\n')
  288. )
  289. );
  290. }
  291. }
  292. });
  293. return function(_x) {
  294. return _ref.apply(this, arguments);
  295. };
  296. })()
  297. );
  298. try {
  299. yield Promise.all(instrumentation);
  300. } catch (err) {
  301. // Do nothing; errors were reported earlier to the console.
  302. }
  303. if (_jestUtil().isInteractive) {
  304. (0, _jestUtil().clearLine)(process.stderr);
  305. }
  306. if (worker && 'end' in worker && typeof worker.end === 'function') {
  307. worker.end();
  308. }
  309. })();
  310. }
  311. _checkThreshold(globalConfig, map) {
  312. if (globalConfig.coverageThreshold) {
  313. function check(name, thresholds, actuals) {
  314. return ['statements', 'branches', 'lines', 'functions'].reduce(
  315. (errors, key) => {
  316. const actual = actuals[key].pct;
  317. const actualUncovered = actuals[key].total - actuals[key].covered;
  318. const threshold = thresholds[key];
  319. if (threshold != null) {
  320. if (threshold < 0) {
  321. if (threshold * -1 < actualUncovered) {
  322. errors.push(
  323. `Jest: Uncovered count for ${key} (${actualUncovered})` +
  324. `exceeds ${name} threshold (${-1 * threshold})`
  325. );
  326. }
  327. } else if (actual < threshold) {
  328. errors.push(
  329. `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`
  330. );
  331. }
  332. }
  333. return errors;
  334. },
  335. []
  336. );
  337. }
  338. const THRESHOLD_GROUP_TYPES = {
  339. GLOB: 'glob',
  340. GLOBAL: 'global',
  341. PATH: 'path'
  342. };
  343. const coveredFiles = map.files();
  344. const thresholdGroups = Object.keys(globalConfig.coverageThreshold);
  345. const groupTypeByThresholdGroup = {};
  346. const filesByGlob = {};
  347. const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce(
  348. (files, file) => {
  349. const pathOrGlobMatches = thresholdGroups.reduce(
  350. (agg, thresholdGroup) => {
  351. const absoluteThresholdGroup = _path().default.resolve(
  352. thresholdGroup
  353. ); // The threshold group might be a path:
  354. if (file.indexOf(absoluteThresholdGroup) === 0) {
  355. groupTypeByThresholdGroup[thresholdGroup] =
  356. THRESHOLD_GROUP_TYPES.PATH;
  357. return agg.concat([[file, thresholdGroup]]);
  358. } // If the threshold group is not a path it might be a glob:
  359. // Note: glob.sync is slow. By memoizing the files matching each glob
  360. // (rather than recalculating it for each covered file) we save a tonne
  361. // of execution time.
  362. if (filesByGlob[absoluteThresholdGroup] === undefined) {
  363. filesByGlob[absoluteThresholdGroup] = _glob()
  364. .default.sync(absoluteThresholdGroup)
  365. .map(filePath => _path().default.resolve(filePath));
  366. }
  367. if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) {
  368. groupTypeByThresholdGroup[thresholdGroup] =
  369. THRESHOLD_GROUP_TYPES.GLOB;
  370. return agg.concat([[file, thresholdGroup]]);
  371. }
  372. return agg;
  373. },
  374. []
  375. );
  376. if (pathOrGlobMatches.length > 0) {
  377. return files.concat(pathOrGlobMatches);
  378. } // Neither a glob or a path? Toss it in global if there's a global threshold:
  379. if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) {
  380. groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] =
  381. THRESHOLD_GROUP_TYPES.GLOBAL;
  382. return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]);
  383. } // A covered file that doesn't have a threshold:
  384. return files.concat([[file, undefined]]);
  385. },
  386. []
  387. );
  388. const getFilesInThresholdGroup = thresholdGroup =>
  389. coveredFilesSortedIntoThresholdGroup
  390. .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup)
  391. .map(fileAndGroup => fileAndGroup[0]);
  392. function combineCoverage(filePaths) {
  393. return filePaths
  394. .map(filePath => map.fileCoverageFor(filePath))
  395. .reduce((combinedCoverage, nextFileCoverage) => {
  396. if (combinedCoverage === undefined || combinedCoverage === null) {
  397. return nextFileCoverage.toSummary();
  398. }
  399. return combinedCoverage.merge(nextFileCoverage.toSummary());
  400. }, undefined);
  401. }
  402. let errors = [];
  403. thresholdGroups.forEach(thresholdGroup => {
  404. switch (groupTypeByThresholdGroup[thresholdGroup]) {
  405. case THRESHOLD_GROUP_TYPES.GLOBAL: {
  406. const coverage = combineCoverage(
  407. getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL)
  408. );
  409. if (coverage) {
  410. errors = errors.concat(
  411. check(
  412. thresholdGroup,
  413. globalConfig.coverageThreshold[thresholdGroup],
  414. coverage
  415. )
  416. );
  417. }
  418. break;
  419. }
  420. case THRESHOLD_GROUP_TYPES.PATH: {
  421. const coverage = combineCoverage(
  422. getFilesInThresholdGroup(thresholdGroup)
  423. );
  424. if (coverage) {
  425. errors = errors.concat(
  426. check(
  427. thresholdGroup,
  428. globalConfig.coverageThreshold[thresholdGroup],
  429. coverage
  430. )
  431. );
  432. }
  433. break;
  434. }
  435. case THRESHOLD_GROUP_TYPES.GLOB:
  436. getFilesInThresholdGroup(thresholdGroup).forEach(
  437. fileMatchingGlob => {
  438. errors = errors.concat(
  439. check(
  440. fileMatchingGlob,
  441. globalConfig.coverageThreshold[thresholdGroup],
  442. map.fileCoverageFor(fileMatchingGlob).toSummary()
  443. )
  444. );
  445. }
  446. );
  447. break;
  448. default:
  449. // If the file specified by path is not found, error is returned.
  450. if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
  451. errors = errors.concat(
  452. `Jest: Coverage data for ${thresholdGroup} was not found.`
  453. );
  454. }
  455. // Sometimes all files in the coverage data are matched by
  456. // PATH and GLOB threshold groups in which case, don't error when
  457. // the global threshold group doesn't match any files.
  458. }
  459. });
  460. errors = errors.filter(
  461. err => err !== undefined && err !== null && err.length > 0
  462. );
  463. if (errors.length > 0) {
  464. this.log(`${FAIL_COLOR(errors.join('\n'))}`);
  465. this._setError(new Error(errors.join('\n')));
  466. }
  467. }
  468. } // Only exposed for the internal runner. Should not be used
  469. getCoverageMap() {
  470. return this._coverageMap;
  471. }
  472. }
  473. exports.default = CoverageReporter;