CoverageReporter.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. function path() {
  7. const data = _interopRequireWildcard(require('path'));
  8. path = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _v8Coverage() {
  14. const data = require('@bcoe/v8-coverage');
  15. _v8Coverage = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _chalk() {
  21. const data = _interopRequireDefault(require('chalk'));
  22. _chalk = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _glob() {
  28. const data = _interopRequireDefault(require('glob'));
  29. _glob = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function fs() {
  35. const data = _interopRequireWildcard(require('graceful-fs'));
  36. fs = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _istanbulLibCoverage() {
  42. const data = _interopRequireDefault(require('istanbul-lib-coverage'));
  43. _istanbulLibCoverage = function () {
  44. return data;
  45. };
  46. return data;
  47. }
  48. function _istanbulLibReport() {
  49. const data = _interopRequireDefault(require('istanbul-lib-report'));
  50. _istanbulLibReport = function () {
  51. return data;
  52. };
  53. return data;
  54. }
  55. function _istanbulLibSourceMaps() {
  56. const data = _interopRequireDefault(require('istanbul-lib-source-maps'));
  57. _istanbulLibSourceMaps = function () {
  58. return data;
  59. };
  60. return data;
  61. }
  62. function _istanbulReports() {
  63. const data = _interopRequireDefault(require('istanbul-reports'));
  64. _istanbulReports = function () {
  65. return data;
  66. };
  67. return data;
  68. }
  69. function _v8ToIstanbul() {
  70. const data = _interopRequireDefault(require('v8-to-istanbul'));
  71. _v8ToIstanbul = function () {
  72. return data;
  73. };
  74. return data;
  75. }
  76. function _jestUtil() {
  77. const data = require('jest-util');
  78. _jestUtil = function () {
  79. return data;
  80. };
  81. return data;
  82. }
  83. function _jestWorker() {
  84. const data = require('jest-worker');
  85. _jestWorker = function () {
  86. return data;
  87. };
  88. return data;
  89. }
  90. var _BaseReporter = _interopRequireDefault(require('./BaseReporter'));
  91. var _getWatermarks = _interopRequireDefault(require('./getWatermarks'));
  92. function _interopRequireDefault(obj) {
  93. return obj && obj.__esModule ? obj : {default: obj};
  94. }
  95. function _getRequireWildcardCache(nodeInterop) {
  96. if (typeof WeakMap !== 'function') return null;
  97. var cacheBabelInterop = new WeakMap();
  98. var cacheNodeInterop = new WeakMap();
  99. return (_getRequireWildcardCache = function (nodeInterop) {
  100. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  101. })(nodeInterop);
  102. }
  103. function _interopRequireWildcard(obj, nodeInterop) {
  104. if (!nodeInterop && obj && obj.__esModule) {
  105. return obj;
  106. }
  107. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  108. return {default: obj};
  109. }
  110. var cache = _getRequireWildcardCache(nodeInterop);
  111. if (cache && cache.has(obj)) {
  112. return cache.get(obj);
  113. }
  114. var newObj = {};
  115. var hasPropertyDescriptor =
  116. Object.defineProperty && Object.getOwnPropertyDescriptor;
  117. for (var key in obj) {
  118. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  119. var desc = hasPropertyDescriptor
  120. ? Object.getOwnPropertyDescriptor(obj, key)
  121. : null;
  122. if (desc && (desc.get || desc.set)) {
  123. Object.defineProperty(newObj, key, desc);
  124. } else {
  125. newObj[key] = obj[key];
  126. }
  127. }
  128. }
  129. newObj.default = obj;
  130. if (cache) {
  131. cache.set(obj, newObj);
  132. }
  133. return newObj;
  134. }
  135. function _defineProperty(obj, key, value) {
  136. if (key in obj) {
  137. Object.defineProperty(obj, key, {
  138. value: value,
  139. enumerable: true,
  140. configurable: true,
  141. writable: true
  142. });
  143. } else {
  144. obj[key] = value;
  145. }
  146. return obj;
  147. }
  148. const FAIL_COLOR = _chalk().default.bold.red;
  149. const RUNNING_TEST_COLOR = _chalk().default.bold.dim;
  150. class CoverageReporter extends _BaseReporter.default {
  151. constructor(globalConfig, options) {
  152. super();
  153. _defineProperty(this, '_coverageMap', void 0);
  154. _defineProperty(this, '_globalConfig', void 0);
  155. _defineProperty(this, '_sourceMapStore', void 0);
  156. _defineProperty(this, '_options', void 0);
  157. _defineProperty(this, '_v8CoverageResults', void 0);
  158. this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
  159. this._globalConfig = globalConfig;
  160. this._sourceMapStore =
  161. _istanbulLibSourceMaps().default.createSourceMapStore();
  162. this._v8CoverageResults = [];
  163. this._options = options || {};
  164. }
  165. onTestResult(_test, testResult) {
  166. if (testResult.v8Coverage) {
  167. this._v8CoverageResults.push(testResult.v8Coverage);
  168. return;
  169. }
  170. if (testResult.coverage) {
  171. this._coverageMap.merge(testResult.coverage);
  172. }
  173. }
  174. async onRunComplete(contexts, aggregatedResults) {
  175. await this._addUntestedFiles(contexts);
  176. const {map, reportContext} = await this._getCoverageResult();
  177. try {
  178. const coverageReporters = this._globalConfig.coverageReporters || [];
  179. if (!this._globalConfig.useStderr && coverageReporters.length < 1) {
  180. coverageReporters.push('text-summary');
  181. }
  182. coverageReporters.forEach(reporter => {
  183. let additionalOptions = {};
  184. if (Array.isArray(reporter)) {
  185. [reporter, additionalOptions] = reporter;
  186. }
  187. _istanbulReports()
  188. .default.create(reporter, {
  189. maxCols: process.stdout.columns || Infinity,
  190. ...additionalOptions
  191. })
  192. .execute(reportContext);
  193. });
  194. aggregatedResults.coverageMap = map;
  195. } catch (e) {
  196. console.error(
  197. _chalk().default.red(`
  198. Failed to write coverage reports:
  199. ERROR: ${e.toString()}
  200. STACK: ${e.stack}
  201. `)
  202. );
  203. }
  204. this._checkThreshold(map);
  205. }
  206. async _addUntestedFiles(contexts) {
  207. const files = [];
  208. contexts.forEach(context => {
  209. const config = context.config;
  210. if (
  211. this._globalConfig.collectCoverageFrom &&
  212. this._globalConfig.collectCoverageFrom.length
  213. ) {
  214. context.hasteFS
  215. .matchFilesWithGlob(
  216. this._globalConfig.collectCoverageFrom,
  217. config.rootDir
  218. )
  219. .forEach(filePath =>
  220. files.push({
  221. config,
  222. path: filePath
  223. })
  224. );
  225. }
  226. });
  227. if (!files.length) {
  228. return;
  229. }
  230. if (_jestUtil().isInteractive) {
  231. process.stderr.write(
  232. RUNNING_TEST_COLOR('Running coverage on untested files...')
  233. );
  234. }
  235. let worker;
  236. if (this._globalConfig.maxWorkers <= 1) {
  237. worker = require('./CoverageWorker');
  238. } else {
  239. worker = new (_jestWorker().Worker)(require.resolve('./CoverageWorker'), {
  240. exposedMethods: ['worker'],
  241. maxRetries: 2,
  242. numWorkers: this._globalConfig.maxWorkers
  243. });
  244. }
  245. const instrumentation = files.map(async fileObj => {
  246. const filename = fileObj.path;
  247. const config = fileObj.config;
  248. const hasCoverageData = this._v8CoverageResults.some(v8Res =>
  249. v8Res.some(innerRes => innerRes.result.url === filename)
  250. );
  251. if (
  252. !hasCoverageData &&
  253. !this._coverageMap.data[filename] &&
  254. 'worker' in worker
  255. ) {
  256. try {
  257. const result = await worker.worker({
  258. config,
  259. globalConfig: this._globalConfig,
  260. options: {
  261. ...this._options,
  262. changedFiles:
  263. this._options.changedFiles &&
  264. Array.from(this._options.changedFiles),
  265. sourcesRelatedToTestsInChangedFiles:
  266. this._options.sourcesRelatedToTestsInChangedFiles &&
  267. Array.from(this._options.sourcesRelatedToTestsInChangedFiles)
  268. },
  269. path: filename
  270. });
  271. if (result) {
  272. if (result.kind === 'V8Coverage') {
  273. this._v8CoverageResults.push([
  274. {
  275. codeTransformResult: undefined,
  276. result: result.result
  277. }
  278. ]);
  279. } else {
  280. this._coverageMap.addFileCoverage(result.coverage);
  281. }
  282. }
  283. } catch (error) {
  284. console.error(
  285. _chalk().default.red(
  286. [
  287. `Failed to collect coverage from ${filename}`,
  288. `ERROR: ${error.message}`,
  289. `STACK: ${error.stack}`
  290. ].join('\n')
  291. )
  292. );
  293. }
  294. }
  295. });
  296. try {
  297. await Promise.all(instrumentation);
  298. } catch {
  299. // Do nothing; errors were reported earlier to the console.
  300. }
  301. if (_jestUtil().isInteractive) {
  302. (0, _jestUtil().clearLine)(process.stderr);
  303. }
  304. if (worker && 'end' in worker && typeof worker.end === 'function') {
  305. await worker.end();
  306. }
  307. }
  308. _checkThreshold(map) {
  309. const {coverageThreshold} = this._globalConfig;
  310. if (coverageThreshold) {
  311. function check(name, thresholds, actuals) {
  312. return ['statements', 'branches', 'lines', 'functions'].reduce(
  313. (errors, key) => {
  314. const actual = actuals[key].pct;
  315. const actualUncovered = actuals[key].total - actuals[key].covered;
  316. const threshold = thresholds[key];
  317. if (threshold !== undefined) {
  318. if (threshold < 0) {
  319. if (threshold * -1 < actualUncovered) {
  320. errors.push(
  321. `Jest: Uncovered count for ${key} (${actualUncovered}) ` +
  322. `exceeds ${name} threshold (${-1 * threshold})`
  323. );
  324. }
  325. } else if (actual < threshold) {
  326. errors.push(
  327. `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`
  328. );
  329. }
  330. }
  331. return errors;
  332. },
  333. []
  334. );
  335. }
  336. const THRESHOLD_GROUP_TYPES = {
  337. GLOB: 'glob',
  338. GLOBAL: 'global',
  339. PATH: 'path'
  340. };
  341. const coveredFiles = map.files();
  342. const thresholdGroups = Object.keys(coverageThreshold);
  343. const groupTypeByThresholdGroup = {};
  344. const filesByGlob = {};
  345. const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce(
  346. (files, file) => {
  347. const pathOrGlobMatches = thresholdGroups.reduce(
  348. (agg, thresholdGroup) => {
  349. const absoluteThresholdGroup = path().resolve(thresholdGroup); // The threshold group might be a path:
  350. if (file.indexOf(absoluteThresholdGroup) === 0) {
  351. groupTypeByThresholdGroup[thresholdGroup] =
  352. THRESHOLD_GROUP_TYPES.PATH;
  353. return agg.concat([[file, thresholdGroup]]);
  354. } // If the threshold group is not a path it might be a glob:
  355. // Note: glob.sync is slow. By memoizing the files matching each glob
  356. // (rather than recalculating it for each covered file) we save a tonne
  357. // of execution time.
  358. if (filesByGlob[absoluteThresholdGroup] === undefined) {
  359. filesByGlob[absoluteThresholdGroup] = _glob()
  360. .default.sync(absoluteThresholdGroup)
  361. .map(filePath => path().resolve(filePath));
  362. }
  363. if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) {
  364. groupTypeByThresholdGroup[thresholdGroup] =
  365. THRESHOLD_GROUP_TYPES.GLOB;
  366. return agg.concat([[file, thresholdGroup]]);
  367. }
  368. return agg;
  369. },
  370. []
  371. );
  372. if (pathOrGlobMatches.length > 0) {
  373. return files.concat(pathOrGlobMatches);
  374. } // Neither a glob or a path? Toss it in global if there's a global threshold:
  375. if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) {
  376. groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] =
  377. THRESHOLD_GROUP_TYPES.GLOBAL;
  378. return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]);
  379. } // A covered file that doesn't have a threshold:
  380. return files.concat([[file, undefined]]);
  381. },
  382. []
  383. );
  384. const getFilesInThresholdGroup = thresholdGroup =>
  385. coveredFilesSortedIntoThresholdGroup
  386. .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup)
  387. .map(fileAndGroup => fileAndGroup[0]);
  388. function combineCoverage(filePaths) {
  389. return filePaths
  390. .map(filePath => map.fileCoverageFor(filePath))
  391. .reduce((combinedCoverage, nextFileCoverage) => {
  392. if (combinedCoverage === undefined || combinedCoverage === null) {
  393. return nextFileCoverage.toSummary();
  394. }
  395. return combinedCoverage.merge(nextFileCoverage.toSummary());
  396. }, undefined);
  397. }
  398. let errors = [];
  399. thresholdGroups.forEach(thresholdGroup => {
  400. switch (groupTypeByThresholdGroup[thresholdGroup]) {
  401. case THRESHOLD_GROUP_TYPES.GLOBAL: {
  402. const coverage = combineCoverage(
  403. getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL)
  404. );
  405. if (coverage) {
  406. errors = errors.concat(
  407. check(
  408. thresholdGroup,
  409. coverageThreshold[thresholdGroup],
  410. coverage
  411. )
  412. );
  413. }
  414. break;
  415. }
  416. case THRESHOLD_GROUP_TYPES.PATH: {
  417. const coverage = combineCoverage(
  418. getFilesInThresholdGroup(thresholdGroup)
  419. );
  420. if (coverage) {
  421. errors = errors.concat(
  422. check(
  423. thresholdGroup,
  424. coverageThreshold[thresholdGroup],
  425. coverage
  426. )
  427. );
  428. }
  429. break;
  430. }
  431. case THRESHOLD_GROUP_TYPES.GLOB:
  432. getFilesInThresholdGroup(thresholdGroup).forEach(
  433. fileMatchingGlob => {
  434. errors = errors.concat(
  435. check(
  436. fileMatchingGlob,
  437. coverageThreshold[thresholdGroup],
  438. map.fileCoverageFor(fileMatchingGlob).toSummary()
  439. )
  440. );
  441. }
  442. );
  443. break;
  444. default:
  445. // If the file specified by path is not found, error is returned.
  446. if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
  447. errors = errors.concat(
  448. `Jest: Coverage data for ${thresholdGroup} was not found.`
  449. );
  450. }
  451. // Sometimes all files in the coverage data are matched by
  452. // PATH and GLOB threshold groups in which case, don't error when
  453. // the global threshold group doesn't match any files.
  454. }
  455. });
  456. errors = errors.filter(
  457. err => err !== undefined && err !== null && err.length > 0
  458. );
  459. if (errors.length > 0) {
  460. this.log(`${FAIL_COLOR(errors.join('\n'))}`);
  461. this._setError(new Error(errors.join('\n')));
  462. }
  463. }
  464. }
  465. async _getCoverageResult() {
  466. if (this._globalConfig.coverageProvider === 'v8') {
  467. const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)(
  468. this._v8CoverageResults.map(cov => ({
  469. result: cov.map(r => r.result)
  470. }))
  471. );
  472. const fileTransforms = new Map();
  473. this._v8CoverageResults.forEach(res =>
  474. res.forEach(r => {
  475. if (r.codeTransformResult && !fileTransforms.has(r.result.url)) {
  476. fileTransforms.set(r.result.url, r.codeTransformResult);
  477. }
  478. })
  479. );
  480. const transformedCoverage = await Promise.all(
  481. mergedCoverages.result.map(async res => {
  482. var _fileTransform$wrappe;
  483. const fileTransform = fileTransforms.get(res.url);
  484. let sourcemapContent = undefined;
  485. if (
  486. fileTransform !== null &&
  487. fileTransform !== void 0 &&
  488. fileTransform.sourceMapPath &&
  489. fs().existsSync(fileTransform.sourceMapPath)
  490. ) {
  491. sourcemapContent = JSON.parse(
  492. fs().readFileSync(fileTransform.sourceMapPath, 'utf8')
  493. );
  494. }
  495. const converter = (0, _v8ToIstanbul().default)(
  496. res.url,
  497. (_fileTransform$wrappe =
  498. fileTransform === null || fileTransform === void 0
  499. ? void 0
  500. : fileTransform.wrapperLength) !== null &&
  501. _fileTransform$wrappe !== void 0
  502. ? _fileTransform$wrappe
  503. : 0,
  504. fileTransform && sourcemapContent
  505. ? {
  506. originalSource: fileTransform.originalCode,
  507. source: fileTransform.code,
  508. sourceMap: {
  509. sourcemap: {
  510. file: res.url,
  511. ...sourcemapContent
  512. }
  513. }
  514. }
  515. : {
  516. source: fs().readFileSync(res.url, 'utf8')
  517. }
  518. );
  519. await converter.load();
  520. converter.applyCoverage(res.functions);
  521. const istanbulData = converter.toIstanbul();
  522. converter.destroy();
  523. return istanbulData;
  524. })
  525. );
  526. const map = _istanbulLibCoverage().default.createCoverageMap({});
  527. transformedCoverage.forEach(res => map.merge(res));
  528. const reportContext = _istanbulLibReport().default.createContext({
  529. coverageMap: map,
  530. dir: this._globalConfig.coverageDirectory,
  531. watermarks: (0, _getWatermarks.default)(this._globalConfig)
  532. });
  533. return {
  534. map,
  535. reportContext
  536. };
  537. }
  538. const map = await this._sourceMapStore.transformCoverage(this._coverageMap);
  539. const reportContext = _istanbulLibReport().default.createContext({
  540. coverageMap: map,
  541. dir: this._globalConfig.coverageDirectory,
  542. sourceFinder: this._sourceMapStore.sourceFinder,
  543. watermarks: (0, _getWatermarks.default)(this._globalConfig)
  544. });
  545. return {
  546. map,
  547. reportContext
  548. };
  549. }
  550. }
  551. exports.default = CoverageReporter;
  552. _defineProperty(CoverageReporter, 'filename', __filename);